The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 http://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 "game_board.hpp"
23 #include "lexical_cast.hpp"
24 #include "log.hpp"
25 #include "resources.hpp"
26 #include "team.hpp"
27 #include "terrain/filter.hpp"
28 #include "units/unit.hpp"
29 #include "units/abilities.hpp"
30 #include "units/filter.hpp"
31 #include "units/map.hpp"
32 #include "filter_context.hpp"
33 #include "formula/callable_objects.hpp"
34 #include "formula/formula.hpp"
37 
38 #include <boost/dynamic_bitset.hpp>
39 #include <boost/algorithm/string/predicate.hpp>
40 
41 static lg::log_domain log_engine("engine");
42 #define ERR_NG LOG_STREAM(err, log_engine)
43 
44 namespace {
45  class temporary_facing
46  {
47  map_location::DIRECTION save_dir_;
49  public:
50  temporary_facing(unit_map::const_iterator u, map_location::DIRECTION new_dir)
51  : save_dir_(u.valid() ? u->facing() : map_location::NDIRECTIONS)
52  , u_(u)
53  {
54  if (u_.valid()) {
55  u_->set_facing(new_dir);
56  }
57  }
58  ~temporary_facing()
59  {
60  if (u_.valid()) {
61  u_->set_facing(save_dir_);
62  }
63  }
64  };
65 }
66 
67 /*
68  *
69  * [abilities]
70  * ...
71  *
72  * [heals]
73  * value=4
74  * max_value=8
75  * cumulative=no
76  * affect_allies=yes
77  * name= _ "heals"
78  * female_name= _ "female^heals"
79  * name_inactive=null
80  * female_name_inactive=null
81  * description= _ "Heals:
82 Allows the unit to heal adjacent friendly units at the beginning of each turn.
83 
84 A unit cared for by a healer may heal up to 4 HP per turn.
85 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."
86  * description_inactive=null
87  *
88  * affect_self=yes
89  * [filter] // SUF
90  * ...
91  * [/filter]
92  * [filter_self] // SUF
93  * ...
94  * [/filter_self]
95  * [filter_adjacent] // SUF
96  * adjacent=n,ne,nw
97  * ...
98  * [/filter_adjacent]
99  * [filter_adjacent_location]
100  * adjacent=n,ne,nw
101  * ...
102  * [/filter_adjacent]
103  * [affect_adjacent]
104  * adjacent=n,ne,nw
105  * [filter] // SUF
106  * ...
107  * [/filter]
108  * [/affect_adjacent]
109  * [affect_adjacent]
110  * adjacent=s,se,sw
111  * [filter] // SUF
112  * ...
113  * [/filter]
114  * [/affect_adjacent]
115  *
116  * [/heals]
117  *
118  * ...
119  * [/abilities]
120  *
121  */
122 
123 
124 namespace {
125 
126 bool affects_side(const config& cfg, const std::vector<team>& teams, size_t side, size_t other_side)
127 {
128  if (side == other_side)
129  return cfg["affect_allies"].to_bool(true);
130  if (teams[side - 1].is_enemy(other_side))
131  return cfg["affect_enemies"].to_bool();
132  else
133  return cfg["affect_allies"].to_bool();
134 }
135 
136 }
137 
138 
139 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc, const display_context& dc) const
140 {
141  for (const config &i : this->abilities_.child_range(tag_name)) {
142  if (ability_active(tag_name, i, loc) &&
143  ability_affects_self(tag_name, i, loc))
144  {
145  return true;
146  }
147  }
148 
149  assert(display::get_singleton());
150  const unit_map& units = display::get_singleton()->get_units();
151 
152  adjacent_loc_array_t adjacent;
153  get_adjacent_tiles(loc,adjacent.data());
154  for(unsigned i = 0; i < adjacent.size(); ++i) {
155  const unit_map::const_iterator it = units.find(adjacent[i]);
156  if (it == units.end() || it->incapacitated())
157  continue;
158  // Abilities may be tested at locations other than the unit's current
159  // location. This is intentional to allow for less messing with the unit
160  // map during calculations, particularly with regards to movement.
161  // Thus, we need to make sure the adjacent unit (*it) is not actually
162  // ourself.
163  if ( &*it == this )
164  continue;
165  for (const config &j : it->abilities_.child_range(tag_name)) {
166  if (affects_side(j, dc.teams(), side(), it->side()) &&
167  it->ability_active(tag_name, j, adjacent[i]) &&
168  ability_affects_adjacent(tag_name, j, i, loc, *it))
169  {
170  return true;
171  }
172  }
173  }
174 
175 
176  return false;
177 }
179 {
180  unit_ability_list res(loc_);
181 
182  for (const config &i : this->abilities_.child_range(tag_name)) {
183  if (ability_active(tag_name, i, loc) &&
184  ability_affects_self(tag_name, i, loc))
185  {
186  res.push_back(unit_ability(&i, loc));
187  }
188  }
189 
190  assert(display::get_singleton());
191  const unit_map& units = display::get_singleton()->get_units();
192 
193  adjacent_loc_array_t adjacent;
194  get_adjacent_tiles(loc,adjacent.data());
195  for(unsigned i = 0; i < adjacent.size(); ++i) {
196  const unit_map::const_iterator it = units.find(adjacent[i]);
197  if (it == units.end() || it->incapacitated())
198  continue;
199  // Abilities may be tested at locations other than the unit's current
200  // location. This is intentional to allow for less messing with the unit
201  // map during calculations, particularly with regards to movement.
202  // Thus, we need to make sure the adjacent unit (*it) is not actually
203  // ourself.
204  if ( &*it == this )
205  continue;
206  for (const config &j : it->abilities_.child_range(tag_name)) {
207  if (affects_side(j, resources::gameboard->teams(), side(), it->side()) &&
208  it->ability_active(tag_name, j, adjacent[i]) &&
209  ability_affects_adjacent(tag_name, j, i, loc, *it))
210  {
211  res.push_back(unit_ability(&j, adjacent[i]));
212  }
213  }
214  }
215 
216 
217  return res;
218 }
219 
220 std::vector<std::string> unit::get_ability_list() const
221 {
222  std::vector<std::string> res;
223 
224  for (const config::any_child &ab : this->abilities_.all_children_range()) {
225  std::string id = ab.cfg["id"];
226  if (!id.empty())
227  res.push_back(std::move(id));
228  }
229  return res;
230 }
231 
232 
233 namespace {
234  // These functions might have wider usefulness than this file, but for now
235  // I'll make them local.
236 
237  /**
238  * Chooses a value from the given config. If the value specified by @a key is
239  * blank, then @a default_key is chosen instead.
240  */
241  inline const config::attribute_value & default_value(
242  const config & cfg, const std::string & key, const std::string & default_key)
243  {
244  const config::attribute_value & value = cfg[key];
245  return !value.blank() ? value : cfg[default_key];
246  }
247 
248  /**
249  * Chooses a value from the given config based on gender. If the value for
250  * the specified gender is blank, then @a default_key is chosen instead.
251  */
252  inline const config::attribute_value & gender_value(
253  const config & cfg, unit_race::GENDER gender, const std::string & male_key,
254  const std::string & female_key, const std::string & default_key)
255  {
256  return default_value(cfg,
257  gender == unit_race::MALE ? male_key : female_key,
258  default_key);
259  }
260 }
261 
262 std::vector<std::tuple<std::string, t_string, t_string, t_string> > unit::ability_tooltips(boost::dynamic_bitset<>* active_list) const
263 {
264  std::vector<std::tuple<std::string, t_string,t_string,t_string> > res;
265  if ( active_list )
266  active_list->clear();
267 
268  for (const config::any_child &ab : this->abilities_.all_children_range())
269  {
270  if ( !active_list || ability_active(ab.key, ab.cfg, loc_) )
271  {
272  const t_string& name =
273  gender_value(ab.cfg, gender_, "name", "female_name", "name").t_str();
274 
275  if (!name.empty()) {
276  res.emplace_back(
277  ab.cfg["id"],
278  ab.cfg["name"].t_str(),
279  name,
280  ab.cfg["description"].t_str() );
281  if ( active_list )
282  active_list->push_back(true);
283  }
284  }
285  else
286  {
287  // See if an inactive name was specified.
288  const config::attribute_value& inactive_value =
289  gender_value(ab.cfg, gender_, "name_inactive",
290  "female_name_inactive", "name_inactive");
291  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
292  gender_value(ab.cfg, gender_, "name", "female_name", "name").t_str();
293 
294  if (!name.empty()) {
295  res.emplace_back(
296  ab.cfg["id"],
297  default_value(ab.cfg, "name_inactive", "name").t_str(),
298  name,
299  default_value(ab.cfg, "description_inactive", "description").t_str() );
300  active_list->push_back(false);
301  }
302  }
303  }
304  return res;
305 }
306 
307 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
308 {
309  bool illuminates = ability == "illuminates";
310 
311  if (const config &afilter = cfg.child("filter"))
312  if ( !unit_filter(vconfig(afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
313  return false;
314 
315  adjacent_loc_array_t adjacent;
316  get_adjacent_tiles(loc,adjacent.data());
317 
318  assert(display::get_singleton());
319  const unit_map& units = display::get_singleton()->get_units();
320 
321  for (const config &i : cfg.child_range("filter_adjacent"))
322  {
323  size_t count = 0;
324  unit_filter ufilt{ vconfig(i) };
325  ufilt.set_use_flat_tod(illuminates);
326  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
327  for (const map_location::DIRECTION index : dirs)
328  {
330  continue;
331  unit_map::const_iterator unit = units.find(adjacent[index]);
332  if (unit == units.end())
333  return false;
334  if (!ufilt(*unit, *this))
335  return false;
336  if (i.has_attribute("is_enemy")) {
338  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
339  continue;
340  }
341  }
342  count++;
343  }
344  if (i["count"].empty() && count != dirs.size()) {
345  return false;
346  }
347  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
348  return false;
349  }
350  }
351 
352  for (const config &i : cfg.child_range("filter_adjacent_location"))
353  {
354  size_t count = 0;
356  adj_filter.flatten(illuminates);
357 
358  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
359  for (const map_location::DIRECTION index : dirs)
360  {
362  continue;
363  }
364  if(!adj_filter.match(adjacent[index])) {
365  return false;
366  }
367  count++;
368  }
369  if (i["count"].empty() && count != dirs.size()) {
370  return false;
371  }
372  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
373  return false;
374  }
375  }
376  return true;
377 }
378 
379 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
380 {
381  bool illuminates = ability == "illuminates";
382 
383  assert(dir >=0 && dir <= 5);
384  map_location::DIRECTION direction = static_cast<map_location::DIRECTION>(dir);
385 
386  for (const config &i : cfg.child_range("affect_adjacent"))
387  {
388  if (i.has_attribute("adjacent")) { //key adjacent defined
389  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
390  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
391  continue;
392  }
393  }
394  const config &filter = i.child("filter");
395  if (!filter || //filter tag given
396  unit_filter(vconfig(filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
397  return true;
398  }
399  }
400  return false;
401 }
402 
403 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
404 {
405  const config &filter = cfg.child("filter_self");
406  bool affect_self = cfg["affect_self"].to_bool(true);
407  if (!filter || !affect_self) return affect_self;
408  return unit_filter(vconfig(filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
409 }
410 
411 bool unit::has_ability_type(const std::string& ability) const
412 {
413  return !abilities_.child_range(ability).empty();
414 }
415 
416 namespace {
417 
418 
419 template<typename T, typename TFuncFormula>
420 class get_ability_value_visitor : public boost::static_visitor<T>
421 {
422 public:
423  // Constructor stores the default value.
424  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
425 
426  T operator()(const boost::blank&) const { return def_; }
427  T operator()(bool) const { return def_; }
428  T operator()(int i) const { return static_cast<T>(i); }
429  T operator()(unsigned long long u) const { return static_cast<T>(u); }
430  T operator()(double d) const { return static_cast<T>(d); }
431  T operator()(const t_string&) const { return def_; }
432  T operator()(const std::string& s) const
433  {
434  if(s.size() >= 2 && s[0] == '(') {
435  return formula_handler_(s);
436  }
437  return lexical_cast_default<T>(s, def_);
438  }
439 
440 private:
441  const T def_;
442  const TFuncFormula& formula_handler_;
443 };
444 template<typename T, typename TFuncFormula>
445 get_ability_value_visitor<T, TFuncFormula> make_get_ability_value_visitor(T def, const TFuncFormula& formula_handler)
446 {
447  return get_ability_value_visitor<T, TFuncFormula>(def, formula_handler);
448 }
449 template<typename T, typename TFuncFormula>
450 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)
451 {
452  return v.apply_visitor(make_get_ability_value_visitor(def, [&](const std::string& s) {
453 
454  try {
455  assert(display::get_singleton());
456  const unit_map& units = display::get_singleton()->get_units();
457 
458  auto u_itor = units.find(sender_loc);
459 
460  if(u_itor == units.end()) {
461  return def;
462  }
463  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
464  u_itor = units.find(receiver_loc);
465  if(u_itor != units.end()) {
466  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*u_itor)));
467  }
468  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
469  } catch(wfl::formula_error& e) {
470  lg::wml_error() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
471  return def;
472  }
473  }));
474 }
475 }
476 
477 template<typename TComp>
478 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
479 {
480  if ( cfgs_.empty() ) {
481  return std::make_pair(def, map_location());
482  }
483  // The returned location is the best non-cumulative one, if any,
484  // the best absolute cumulative one otherwise.
485  map_location best_loc;
486  bool only_cumulative = true;
487  int abs_max = 0;
488  int flat = 0;
489  int stack = 0;
490  for (const unit_ability& p : cfgs_)
491  {
492  int value = get_single_ability_value((*p.first)[key], def, p.second, loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
493  return formula.evaluate(callable).as_int();
494  });
495 
496  if ((*p.first)["cumulative"].to_bool()) {
497  stack += value;
498  if (value < 0) value = -value;
499  if (only_cumulative && !comp(value, abs_max)) {
500  abs_max = value;
501  best_loc = p.second;
502  }
503  } else if (only_cumulative || comp(flat, value)) {
504  only_cumulative = false;
505  flat = value;
506  best_loc = p.second;
507  }
508  }
509  return std::make_pair(flat + stack, best_loc);
510 }
511 
512 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;
513 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;
514 
515 /*
516  *
517  * [special]
518  * [swarm]
519  * name= _ "swarm"
520  * name_inactive= _ ""
521  * description= _ ""
522  * description_inactive= _ ""
523  * cumulative=no
524  * apply_to=self #self,opponent,defender,attacker,both
525  * #active_on=defense # or offense; omitting this means "both"
526  *
527  * swarm_attacks_max=4
528  * swarm_attacks_min=2
529  *
530  * [filter_self] // SUF
531  * ...
532  * [/filter_self]
533  * [filter_opponent] // SUF
534  * [filter_attacker] // SUF
535  * [filter_defender] // SUF
536  * [filter_adjacent] // SAUF
537  * [filter_adjacent_location] // SAUF + locs
538  * [/swarm]
539  * [/special]
540  *
541  */
542 
543 namespace {
544 
545  /**
546  * Gets the children of @parent (which should be the specials for an
547  * attack_type) and places the ones whose tag or id= matches @a id into
548  * @a result.
549  * If @a just_peeking is set to true, then @a result is not touched;
550  * instead the return value is used to indicate if any matching children
551  * were found.
552  *
553  * @returns true if @a just_peeking is true and a match was found;
554  * false otherwise.
555  */
556  bool get_special_children(std::vector<const config*>& result, const config& parent,
557  const std::string& id, bool just_peeking=false) {
558  for (const config::any_child &sp : parent.all_children_range())
559  {
560  if (sp.key == id || sp.cfg["id"] == id) {
561  if(just_peeking) {
562  return true; // peek succeeded; done
563  } else {
564  result.push_back(&sp.cfg);
565  }
566  }
567  }
568  return false;
569  }
570 }
571 
572 /**
573  * Returns whether or not @a *this has a special with a tag or id equal to
574  * @a special. If @a simple_check is set to true, then the check is merely
575  * for being present. Otherwise (the default), the check is for a special
576  * active in the current context (see set_specials_context), including
577  * specials obtained from the opponent's attack.
578  */
579 bool attack_type::get_special_bool(const std::string& special, bool simple_check) const
580 {
581  {
582  std::vector<const config*> list;
583  if ( get_special_children(list, specials_, special, simple_check) ) {
584  return true;
585  }
586  // If we make it to here, then either list.empty() or !simple_check.
587  // So if the list is not empty, then this is not a simple check and
588  // we need to check each special in the list to see if any are active.
589  for (std::vector<const config*>::iterator i = list.begin(), i_end = list.end(); i != i_end; ++i) {
590  if ( special_active(**i, AFFECT_SELF) ) {
591  return true;
592  }
593  }
594  }
595  // Skip checking the opponent's attack?
596  if ( simple_check || !other_attack_ ) {
597  return false;
598  }
599 
600  std::vector<const config*> list;
601  get_special_children(list, other_attack_->specials_, special);
602  for (std::vector<const config*>::iterator i = list.begin(), i_end = list.end(); i != i_end; ++i) {
603  if ( other_attack_->special_active(**i, AFFECT_OTHER) ) {
604  return true;
605  }
606  }
607  return false;
608 }
609 
610 /**
611  * Returns the currently active specials as an ability list, given the current
612  * context (see set_specials_context).
613  */
615 {
616  //log_scope("get_specials");
618  for (const config &i : specials_.child_range(special)) {
619  if ( special_active(i, AFFECT_SELF) )
621  }
622  if (!other_attack_) return res;
623  for (const config &i : other_attack_->specials_.child_range(special)) {
624  if ( other_attack_->special_active(i, AFFECT_OTHER) )
626  }
627  return res;
628 }
629 
630 /**
631  * Returns a vector of names and descriptions for the specials of *this.
632  * Each std::pair in the vector has first = name and second = description.
633  *
634  * This uses either the active or inactive name/description for each special,
635  * based on the current context (see set_specials_context), provided
636  * @a active_list is not nullptr. Otherwise specials are assumed active.
637  * If the appropriate name is empty, the special is skipped.
638  */
639 std::vector<std::pair<t_string, t_string> > attack_type::special_tooltips(
640  boost::dynamic_bitset<>* active_list) const
641 {
642  //log_scope("special_tooltips");
643  std::vector<std::pair<t_string, t_string> > res;
644  if ( active_list )
645  active_list->clear();
646 
648  {
649  if ( !active_list || special_active(sp.cfg, AFFECT_EITHER) ) {
650  const t_string &name = sp.cfg["name"];
651  if (!name.empty()) {
652  res.emplace_back(name, sp.cfg["description"].t_str() );
653  if ( active_list )
654  active_list->push_back(true);
655  }
656  } else {
657  const t_string& name = default_value(sp.cfg, "name_inactive", "name").t_str();
658  if (!name.empty()) {
659  res.emplace_back(name, default_value(sp.cfg, "description_inactive", "description").t_str() );
660  active_list->push_back(false);
661  }
662  }
663  }
664  return res;
665 }
666 
667 /**
668  * Returns a comma-separated string of active names for the specials of *this.
669  * Empty names are skipped.
670  *
671  * This excludes inactive specials if only_active is true. Whether or not a
672  * special is active depends on the current context (see set_specials_context)
673  * and the @a is_backstab parameter.
674  */
675 std::string attack_type::weapon_specials(bool only_active, bool is_backstab) const
676 {
677  //log_scope("weapon_specials");
678  std::string res;
680  {
681  if ( only_active && !special_active(sp.cfg, AFFECT_EITHER, is_backstab) )
682  continue;
683 
684  const std::string& name = sp.cfg["name"].str();
685  if (!name.empty()) {
686  if (!res.empty()) res += ", ";
687  res += name;
688  }
689  }
690 
691  return res;
692 }
693 
694 
695 /**
696  * Sets the context under which specials will be checked for being active.
697  * This version is appropriate if both units in a combat are known.
698  * @param[in] unit_loc The location of the unit with this weapon.
699  * @param[in] other_loc The location of the other unit in the combat.
700  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
701  * @param[in] other_attack The attack used by the other unit.
702  */
704  const map_location& other_loc,
705  bool attacking,
706  const_attack_ptr other_attack) const
707 {
708  self_loc_ = unit_loc;
709  other_loc_ = other_loc;
710  is_attacker_ = attacking;
711  other_attack_ = other_attack;
712  is_for_listing_ = false;
713 }
714 
715 /**
716  * Sets the context under which specials will be checked for being active.
717  * This version is appropriate if there is no specific combat being considered.
718  * @param[in] loc The location of the unit with this weapon.
719  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
720  */
721 void attack_type::set_specials_context(const map_location& loc, bool attacking) const
722 {
723  self_loc_ = loc;
725  is_attacker_ = attacking;
726  other_attack_ = nullptr;
727  is_for_listing_ = false;
728 }
729 
731 {
732  is_for_listing_ = true;
733 }
734 
735 
736 /**
737  * Calculates the number of attacks this weapon has, considering specials.
738  * This returns two numbers because of the swarm special. The actual number of
739  * attacks depends on the unit's health and should be:
740  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
741  * c.f. swarm_blows()
742  */
743 void attack_type::modified_attacks(bool is_backstab, unsigned & min_attacks,
744  unsigned & max_attacks) const
745 {
746  // Apply [attacks].
747  unit_abilities::effect attacks_effect(get_specials("attacks"),
748  num_attacks(), is_backstab);
749  int attacks_value = attacks_effect.get_composite_value();
750  if ( attacks_value < 0 ) {
751  attacks_value = num_attacks();
752  ERR_NG << "negative number of strikes after applying weapon specials" << std::endl;
753  }
754 
755  // Apply [swarm].
756  unit_ability_list swarm_specials = get_specials("swarm");
757  if ( !swarm_specials.empty() ) {
758  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
759  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
760  } else {
761  min_attacks = max_attacks = attacks_value;
762  }
763 }
764 
765 
766 /**
767  * Returns the damage per attack of this weapon, considering specials.
768  */
769 int attack_type::modified_damage(bool is_backstab) const
770 {
771  unit_abilities::effect dmg_effect(get_specials("damage"), damage(), is_backstab);
772  return dmg_effect.get_composite_value();
773 }
774 
775 
776 namespace { // Helpers for attack_type::special_active()
777 
778  /**
779  * Returns whether or not the given special affects the opponent of the unit
780  * with the special.
781  * @param[in] special a weapon special WML structure
782  * @param[in] is_attacker whether or not the unit with the special is the attacker
783  */
784  bool special_affects_opponent(const config& special, bool is_attacker)
785  {
786  //log_scope("special_affects_opponent");
787  const std::string& apply_to = special["apply_to"];
788  if ( apply_to.empty() )
789  return false;
790  if ( apply_to == "both" )
791  return true;
792  if ( apply_to == "opponent" )
793  return true;
794  if ( is_attacker && apply_to == "defender" )
795  return true;
796  if ( !is_attacker && apply_to == "attacker" )
797  return true;
798  return false;
799  }
800 
801  /**
802  * Returns whether or not the given special affects the unit with the special.
803  * @param[in] special a weapon special WML structure
804  * @param[in] is_attacker whether or not the unit with the special is the attacker
805  */
806  bool special_affects_self(const config& special, bool is_attacker)
807  {
808  //log_scope("special_affects_self");
809  const std::string& apply_to = special["apply_to"];
810  if ( apply_to.empty() )
811  return true;
812  if ( apply_to == "both" )
813  return true;
814  if ( apply_to == "self" )
815  return true;
816  if ( is_attacker && apply_to == "attacker" )
817  return true;
818  if ( !is_attacker && apply_to == "defender")
819  return true;
820  return false;
821  }
822 
823  /**
824  * Determines if a unit/weapon combination matches the specified child
825  * (normally a [filter_*] child) of the provided filter.
826  * @param[in] un_it The unit to filter.
827  * @param[in] loc The presumed location of @a un_it.
828  * @param[in] weapon The attack_type to filter.
829  * @param[in] filter The filter containing the child filter to use.
830  * @param[in] child_tag The tag of the child filter to use.
831  */
832  static bool special_unit_matches(const unit_map::const_iterator & un_it,
833  const unit_map::const_iterator & u2,
834  const map_location & loc,
835  const_attack_ptr weapon,
836  const config & filter,
837  const bool for_listing,
838  const std::string & child_tag)
839  {
840  if (for_listing && !loc.valid())
841  // The special's context was set to ignore this unit, so assume we pass.
842  // (This is used by reports.cpp to show active specials when the
843  // opponent is not known. From a player's perspective, the special
844  // is active, in that it can be used, even though the player might
845  // need to select an appropriate opponent.)
846  return true;
847 
848  const config & filter_child = filter.child(child_tag);
849  if ( !filter_child )
850  // The special does not filter on this unit, so we pass.
851  return true;
852 
853  // If the primary unit doesn't exist, there's nothing to match
854  if (!un_it.valid()) {
855  return false;
856  }
857 
858  unit_filter ufilt{vconfig(filter_child)};
859 
860  // If the other unit doesn't exist, try matching without it
861  if (!u2.valid()) {
862  return ufilt.matches(*un_it, loc);
863  }
864 
865  // Check for a unit match.
866  if (!ufilt.matches(*un_it, loc, *u2)) {
867  return false;
868  }
869 
870  // Check for a weapon match.
871  if ( const config & filter_weapon = filter_child.child("filter_weapon") ) {
872  if ( !weapon || !weapon->matches_filter(filter_weapon) )
873  return false;
874  }
875 
876  // Passed.
877  return true;
878  }
879 
880 }//anonymous namespace
881 
882 /**
883  * Returns whether or not the given special is active for the specified unit,
884  * based on the current context (see set_specials_context).
885  * @param[in] special a weapon special WML structure
886  * @param[in] whom specifies which combatant we care about
887  * @param[in] include_backstab false if backstab specials should not be active
888  * (usually true since backstab is usually accounted
889  * for elsewhere)
890  */
891 bool attack_type::special_active(const config& special, AFFECTS whom,
892  bool include_backstab) const
893 {
894  //log_scope("special_active");
895 
896  // Backstab check
897  if ( !include_backstab )
898  if ( special["backstab"].to_bool() )
899  return false;
900 
901  // Does this affect the specified unit?
902  if ( whom == AFFECT_SELF ) {
903  if ( !special_affects_self(special, is_attacker_) )
904  return false;
905  }
906  if ( whom == AFFECT_OTHER ) {
907  if ( !special_affects_opponent(special, is_attacker_) )
908  return false;
909  }
910 
911  // Is this active on attack/defense?
912  const std::string & active_on = special["active_on"];
913  if ( !active_on.empty() ) {
914  if ( is_attacker_ && active_on != "offense" )
915  return false;
916  if ( !is_attacker_ && active_on != "defense" )
917  return false;
918  }
919 
920  // Get the units involved.
921  assert(display::get_singleton());
922  const unit_map& units = display::get_singleton()->get_units();
923 
926 
927  // Make sure they're facing each other.
928  temporary_facing self_facing(self, self_loc_.get_relative_dir(other_loc_));
929  temporary_facing other_facing(other, other_loc_.get_relative_dir(self_loc_));
930 
931  // Translate our context into terms of "attacker" and "defender".
932  unit_map::const_iterator & att = is_attacker_ ? self : other;
933  unit_map::const_iterator & def = is_attacker_ ? other : self;
934  const map_location & att_loc = is_attacker_ ? self_loc_ : other_loc_;
935  const map_location & def_loc = is_attacker_ ? other_loc_ : self_loc_;
936  const_attack_ptr att_weapon = is_attacker_ ? shared_from_this() : other_attack_;
937  const_attack_ptr def_weapon = is_attacker_ ? other_attack_ : shared_from_this();
938 
939  // Filter the units involved.
940  if (!special_unit_matches(self, other, self_loc_, shared_from_this(), special, is_for_listing_, "filter_self"))
941  return false;
942  if (!special_unit_matches(other, self, other_loc_, other_attack_, special, is_for_listing_, "filter_opponent"))
943  return false;
944  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing_, "filter_attacker"))
945  return false;
946  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing_, "filter_defender"))
947  return false;
948 
949  adjacent_loc_array_t adjacent;
950  get_adjacent_tiles(self_loc_, adjacent.data());
951 
952  // Filter the adjacent units.
953  for (const config &i : special.child_range("filter_adjacent"))
954  {
955  size_t count = 0;
956  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
957  unit_filter filter{ vconfig(i) };
958  for (const map_location::DIRECTION index : dirs)
959  {
961  continue;
962  unit_map::const_iterator unit = units.find(adjacent[index]);
963  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
964  return false;
965  if (i.has_attribute("is_enemy")) {
967  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
968  continue;
969  }
970  }
971  count++;
972  }
973  if (i["count"].empty() && count != dirs.size()) {
974  return false;
975  }
976  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
977  return false;
978  }
979  }
980 
981  // Filter the adjacent locations.
982  for (const config &i : special.child_range("filter_adjacent_location"))
983  {
984  size_t count = 0;
985  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
987  for (const map_location::DIRECTION index : dirs)
988  {
990  continue;
991  if(!adj_filter.match(adjacent[index])) {
992  return false;
993  }
994  count++;
995  }
996  if (i["count"].empty() && count != dirs.size()) {
997  return false;
998  }
999  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
1000  return false;
1001  }
1002  }
1003 
1004  return true;
1005 }
1006 
1007 
1008 
1010 {
1011 
1012 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1013 {
1014  type=t;
1015  value=val;
1016  ability=abil;
1017  loc=l;
1018 }
1019 
1020 bool filter_base_matches(const config& cfg, int def)
1021 {
1022  if (const config &apply_filter = cfg.child("filter_base_value")) {
1023  config::attribute_value cond_eq = apply_filter["equals"];
1024  config::attribute_value cond_ne = apply_filter["not_equals"];
1025  config::attribute_value cond_lt = apply_filter["less_than"];
1026  config::attribute_value cond_gt = apply_filter["greater_than"];
1027  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1028  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1029  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1030  (cond_ne.empty() || def != cond_ne.to_int()) &&
1031  (cond_lt.empty() || def < cond_lt.to_int()) &&
1032  (cond_gt.empty() || def > cond_gt.to_int()) &&
1033  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1034  (cond_le.empty() || def <= cond_le.to_int());
1035  }
1036  return true;
1037 }
1038 
1039 effect::effect(const unit_ability_list& list, int def, bool backstab) :
1040  effect_list_(),
1041  composite_value_(0)
1042 {
1043 
1044  int value_set = def;
1045  bool value_is_set = false;
1046  std::map<std::string,individual_effect> values_add;
1047  std::map<std::string,individual_effect> values_mul;
1048  std::map<std::string,individual_effect> values_div;
1049 
1050  individual_effect set_effect;
1051 
1052  for (const unit_ability & ability : list) {
1053  const config& cfg = *ability.first;
1054  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
1055 
1056  if (!cfg["backstab"].blank()) {
1057  lg::wml_error() << "The backstab= key in weapon specials is deprecated; use [filter_adjacent] instead\n";
1058  }
1059 
1060  if (!backstab && cfg["backstab"].to_bool())
1061  continue;
1062  if (!filter_base_matches(cfg, def))
1063  continue;
1064 
1065  if (const config::attribute_value *v = cfg.get("value")) {
1066  int value = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1067  callable.add("base_value", wfl::variant(def));
1068  return formula.evaluate(callable).as_int();
1069  });
1070 
1071  bool cumulative = cfg["cumulative"].to_bool();
1072  if (!value_is_set && !cumulative) {
1073  value_set = value;
1074  set_effect.set(SET, value, ability.first, ability.second);
1075  } else {
1076  if (cumulative) value_set = std::max<int>(value_set, def);
1077  if (value > value_set) {
1078  value_set = value;
1079  set_effect.set(SET, value, ability.first, ability.second);
1080  }
1081  }
1082  value_is_set = true;
1083  }
1084 
1085  if (const config::attribute_value *v = cfg.get("add")) {
1086  int add = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1087  callable.add("base_value", wfl::variant(def));
1088  return formula.evaluate(callable).as_int();
1089  });
1090  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
1091  if(add_effect == values_add.end() || add > add_effect->second.value) {
1092  values_add[effect_id].set(ADD, add, ability.first, ability.second);
1093  }
1094  }
1095  if (const config::attribute_value *v = cfg.get("sub")) {
1096  int sub = - get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1097  callable.add("base_value", wfl::variant(def));
1098  return formula.evaluate(callable).as_int();
1099  });
1100  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
1101  if(sub_effect == values_add.end() || sub > sub_effect->second.value) {
1102  values_add[effect_id].set(ADD, sub, ability.first, ability.second);
1103  }
1104  }
1105  if (const config::attribute_value *v = cfg.get("multiply")) {
1106  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) {
1107  callable.add("base_value", wfl::variant(def));
1108  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1109  }) * 100);
1110  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
1111  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
1112  values_mul[effect_id].set(MUL, multiply, ability.first, ability.second);
1113  }
1114  }
1115  if (const config::attribute_value *v = cfg.get("divide")) {
1116  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) {
1117  callable.add("base_value", wfl::variant(def));
1118  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1119  }) * 100);
1120 
1121  if (divide == 0) {
1122  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id << std::endl;
1123  }
1124  else {
1125  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
1126  if(div_effect == values_div.end() || divide > div_effect->second.value) {
1127  values_div[effect_id].set(DIV, divide, ability.first, ability.second);
1128  }
1129  }
1130  }
1131  }
1132 
1133  if(value_is_set && set_effect.type != NOT_USED) {
1134  effect_list_.push_back(set_effect);
1135  }
1136 
1137  /* Do multiplication with floating point values rather than integers
1138  * We want two places of precision for each multiplier
1139  * Using integers multiplied by 100 to keep precision causes overflow
1140  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
1141  * Avoiding the overflow by dividing after each step introduces rounding errors
1142  * that may vary depending on the order effects are applied
1143  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
1144  */
1145  double multiplier = 1.0;
1146  double divisor = 1.0;
1147  std::map<std::string,individual_effect>::const_iterator e, e_end;
1148  for (e = values_mul.begin(), e_end = values_mul.end(); e != e_end; ++e) {
1149  multiplier *= e->second.value/100.0;
1150  effect_list_.push_back(e->second);
1151  }
1152  for (e = values_div.begin(), e_end = values_div.end(); e != e_end; ++e) {
1153  divisor *= e->second.value/100.0;
1154  effect_list_.push_back(e->second);
1155  }
1156  int addition = 0;
1157  for (e = values_add.begin(), e_end = values_add.end(); e != e_end; ++e) {
1158  addition += e->second.value;
1159  effect_list_.push_back(e->second);
1160  }
1161 
1162  composite_value_ = int((value_set + addition) * multiplier / divisor);
1163 }
1164 
1165 } // end namespace unit_abilities
1166 
const t_string & name() const
Definition: attack_type.hpp:39
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:56
void set_specials_context(const map_location &unit_loc, const map_location &other_loc, bool attacking, const_attack_ptr other_attack) const
Sets the context under which specials will be checked for being active.
Definition: abilities.cpp:703
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:417
unit_iterator end()
Definition: map.hpp:415
#define ERR_NG
Definition: abilities.cpp:42
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:87
bool matches(const config &filter) const
Definition: config.cpp:1173
virtual const display_context & get_disp_context() const =0
This class represents a single unit of a specific type.
Definition: unit.hpp:100
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:126
int modified_damage(bool is_backstab) const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:769
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:639
Variant for storing WML attributes.
std::string filename
Definition: formula.hpp:107
V::result_type apply_visitor(const V &visitor) const
Applies a visitor to the underlying variant.
New lexcical_cast header.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.hpp:303
int as_int() const
Definition: variant.cpp:298
bool empty() const
Tests for an attribute that either was never set or was set to "".
void set(value_modifier t, int val, const config *abil, const map_location &l)
Definition: abilities.cpp:1012
child_itors child_range(config_key_type key)
Definition: config.cpp:360
void set_specials_context_for_listing() const
Definition: abilities.cpp:730
map_location other_loc_
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:403
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:225
#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:123
-file sdl_utils.hpp
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:675
bool empty() const
Definition: config.cpp:828
std::vector< unit_ability > cfgs_
Definition: unit.hpp:93
int num_attacks() const
Definition: attack_type.hpp:50
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1020
bool match(const map_location &loc) const
Definition: filter.hpp:41
bool get_special_bool(const std::string &special, bool simple_check=false) const
Returns whether or not *this has a special with a tag or id equal to special.
Definition: abilities.cpp:579
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:478
std::vector< std::pair< int, int > > parse_ranges(const std::string &str)
std::pair< const config *, map_location > unit_ability
Definition: unit.hpp:51
const map_location & loc() const
Definition: unit.hpp:90
bool valid() const
Definition: location.hpp:74
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:134
filter_context * filter_con
Definition: resources.cpp:23
std::string type
Definition: formula.hpp:105
game_board * gameboard
Definition: resources.cpp:20
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:307
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:411
void flatten(const bool flat_tod=true)
Definition: filter.hpp:70
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:178
map_display and display: classes which take care of displaying the map and game-data on the screen...
effect(const unit_ability_list &list, int def, bool backstab)
Definition: abilities.cpp:1039
std::vector< std::string > get_ability_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:220
config specials_
std::array< map_location, 6 > adjacent_loc_array_t
Definition: location.hpp:130
static const map_location & null_location()
Definition: location.hpp:224
unit_race::GENDER gender_
Definition: unit.hpp:1611
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:379
std::vector< std::tuple< std::string, t_string, t_string, t_string > > ability_tooltips(boost::dynamic_bitset<> *active_list=nullptr) const
Gets the names and descriptions of this unit's abilities.
Definition: abilities.cpp:262
const unit_map & get_units() const
Definition: display.hpp:120
bool special_active(const config &special, AFFECTS whom, 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:891
config abilities_
Definition: unit.hpp:1672
bool blank() const
Tests for an attribute that was never set.
Encapsulates the map of the game.
Definition: location.hpp:42
bool empty() const
Definition: unit.hpp:81
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:59
int to_int(int def=0) const
virtual const std::vector< team > & teams() const =0
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:269
bool is_for_listing_
int as_decimal() const
Returns variant's internal representation of decimal number: ie, 1.234 is represented as 1234...
Definition: variant.cpp:307
mock_party p
static map_location::DIRECTION s
size_t i
Definition: function.cpp:933
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:743
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:44
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:614
bool ability_active(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:307
int damage() const
Definition: attack_type.hpp:49
double t
Definition: astarsearch.cpp:64
bool find(E event, F functor)
Tests whether an event handler is available.
const team & get_team(int side) const
const_attack_ptr other_attack_
A variable-expanding proxy for the config class.
Definition: variable.hpp:42
Standard logging facilities (interface).
Container associating units to locations.
Definition: map.hpp:99
#define e
void push_back(const unit_ability &ability)
Definition: unit.hpp:88
int get_composite_value() const
Definition: abilities.hpp:49
unit_iterator find(size_t id)
Definition: map.cpp:311
bool valid() const
Definition: map.hpp:276
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
map_location loc_
Definition: unit.hpp:1572
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
int side_
Definition: unit.hpp:1609
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:865
bool get_ability_bool(const std::string &tag_name, const map_location &loc, const display_context &dc) const
Checks whether this unit currently possesses or is affected by a given ability.
Definition: abilities.cpp:139
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
int side() const
The side this unit belongs to.
Definition: unit.hpp:244
bool empty() const
Definition: tstring.hpp:182
map_location self_loc_
static lg::log_domain log_engine("engine")