The Battle for Wesnoth  1.15.0-dev
attack_type.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.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  * Handle unit-type specific attributes, animations, advancement.
18  */
19 
20 #include "units/attack_type.hpp"
21 #include "formula/callable_objects.hpp"
22 #include "formula/formula.hpp"
23 #include "formula/string_utils.hpp"
25 
26 #include "lexical_cast.hpp"
27 #include "log.hpp"
29 #include "gettext.hpp"
30 #include "utils/math.hpp"
31 
32 #include <cassert>
33 
34 static lg::log_domain log_config("config");
35 #define ERR_CF LOG_STREAM(err, log_config)
36 #define WRN_CF LOG_STREAM(warn, log_config)
37 #define LOG_CONFIG LOG_STREAM(info, log_config)
38 #define DBG_CF LOG_STREAM(debug, log_config)
39 
40 static lg::log_domain log_unit("unit");
41 #define DBG_UT LOG_STREAM(debug, log_unit)
42 #define ERR_UT LOG_STREAM(err, log_unit)
43 
45  self_loc_(),
46  other_loc_(),
47  is_attacker_(false),
48  other_attack_(nullptr),
49  description_(cfg["description"].t_str()),
50  id_(cfg["name"]),
51  type_(cfg["type"]),
52  icon_(cfg["icon"]),
53  range_(cfg["range"]),
54  min_range_(cfg["min_range"].to_int(1)),
55  max_range_(cfg["max_range"].to_int(1)),
56  damage_(cfg["damage"]),
57  num_attacks_(cfg["number"]),
58  attack_weight_(cfg["attack_weight"].to_double(1.0)),
59  defense_weight_(cfg["defense_weight"].to_double(1.0)),
60  accuracy_(cfg["accuracy"]),
61  movement_used_(cfg["movement_used"].to_int(100000)),
62  parry_(cfg["parry"]),
63  specials_(cfg.child_or_empty("specials")),
64  changed_(true)
65 {
66  if (description_.empty())
68 
69  if(icon_.empty()){
70  if (!id_.empty())
71  icon_ = "attacks/" + id_ + ".png";
72  else
73  icon_ = "attacks/blank-attack.png";
74  }
75 }
76 
78 {
79  if(accuracy_ == 0 && parry_ == 0) {
80  return "";
81  }
82 
83  std::ostringstream s;
85 
86  if(parry_ != 0) {
87  s << "/" << utils::signed_percent(parry_);
88  }
89 
90  return s.str();
91 }
92 
93 /**
94  * Returns whether or not *this matches the given @a filter, ignoring the
95  * complexities introduced by [and], [or], and [not].
96  */
97 static bool matches_simple_filter(const attack_type & attack, const config & filter)
98 {
99  const std::vector<std::string>& filter_range = utils::split(filter["range"]);
100  const std::string& filter_damage = filter["damage"];
101  const std::string& filter_attacks = filter["number"];
102  const std::string& filter_accuracy = filter["accuracy"];
103  const std::string& filter_parry = filter["parry"];
104  const std::string& filter_movement = filter["movement_used"];
105  const std::vector<std::string> filter_name = utils::split(filter["name"]);
106  const std::vector<std::string> filter_type = utils::split(filter["type"]);
107  const std::vector<std::string> filter_special = utils::split(filter["special"]);
108  const std::vector<std::string> filter_special_active = utils::split(filter["special_active"]);
109  const std::string filter_formula = filter["formula"];
110 
111  if ( !filter_range.empty() && std::find(filter_range.begin(), filter_range.end(), attack.range()) == filter_range.end() )
112  return false;
113 
114  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges(filter_damage)) )
115  return false;
116 
117  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges(filter_attacks)))
118  return false;
119 
120  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges(filter_accuracy)))
121  return false;
122 
123  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges(filter_parry)))
124  return false;
125 
126  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges(filter_movement)))
127  return false;
128 
129  if ( !filter_name.empty() && std::find(filter_name.begin(), filter_name.end(), attack.id()) == filter_name.end() )
130  return false;
131 
132  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), attack.type()) == filter_type.end() )
133  return false;
134 
135  if(!filter_special.empty()) {
136  bool found = false;
137  for(auto& special : filter_special) {
138  if(attack.get_special_bool(special, true)) {
139  found = true;
140  break;
141  }
142  }
143  if(!found) {
144  return false;
145  }
146  }
147 
148  if(!filter_special_active.empty()) {
149  bool found = false;
150  for(auto& special : filter_special_active) {
151  if(attack.get_special_bool(special, false)) {
152  found = true;
153  break;
154  }
155  }
156  if(!found) {
157  return false;
158  }
159  }
160 
161  if (!filter_formula.empty()) {
162  try {
163  const wfl::attack_type_callable callable(attack);
164  const wfl::formula form(filter_formula, new wfl::gamestate_function_symbol_table);
165  if(!form.evaluate(callable).as_bool()) {
166  return false;
167  }
168  } catch(const wfl::formula_error& e) {
169  lg::wml_error() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
170  // Formulae with syntax errors match nothing
171  return false;
172  }
173  }
174 
175  // Passed all tests.
176  return true;
177 }
178 
179 /**
180  * Returns whether or not *this matches the given @a filter.
181  */
182 bool attack_type::matches_filter(const config& filter) const
183 {
184  // Handle the basic filter.
185  bool matches = matches_simple_filter(*this, filter);
186 
187  // Handle [and], [or], and [not] with in-order precedence
188  for (const config::any_child &condition : filter.all_children_range() )
189  {
190  // Handle [and]
191  if ( condition.key == "and" )
192  matches = matches && matches_filter(condition.cfg);
193 
194  // Handle [or]
195  else if ( condition.key == "or" )
196  matches = matches || matches_filter(condition.cfg);
197 
198  // Handle [not]
199  else if ( condition.key == "not" )
200  matches = matches && !matches_filter(condition.cfg);
201  }
202 
203  return matches;
204 }
205 
206 /**
207  * Modifies *this using the specifications in @a cfg, but only if *this matches
208  * @a cfg viewed as a filter.
209  *
210  * @returns whether or not @c this matched the @a cfg as a filter.
211  */
213 {
214  if( !matches_filter(cfg) )
215  return false;
216 
217  set_changed(true);
218  const std::string& set_name = cfg["set_name"];
219  const t_string& set_desc = cfg["set_description"];
220  const std::string& set_type = cfg["set_type"];
221  const std::string& set_range = cfg["set_range"];
222  const std::string& set_icon = cfg["set_icon"];
223  const std::string& del_specials = cfg["remove_specials"];
224  const config &set_specials = cfg.child("set_specials");
225  const std::string& increase_damage = cfg["increase_damage"];
226  const std::string& set_damage = cfg["set_damage"];
227  const std::string& increase_attacks = cfg["increase_attacks"];
228  const std::string& set_attacks = cfg["set_attacks"];
229  const std::string& set_attack_weight = cfg["attack_weight"];
230  const std::string& set_defense_weight = cfg["defense_weight"];
231  const std::string& increase_accuracy = cfg["increase_accuracy"];
232  const std::string& set_accuracy = cfg["set_accuracy"];
233  const std::string& increase_parry = cfg["increase_parry"];
234  const std::string& set_parry = cfg["set_parry"];
235  const std::string& increase_movement = cfg["increase_movement_used"];
236  const std::string& set_movement = cfg["set_movement_used"];
237  // NB: If you add something here that requires a description,
238  // it needs to be added to describe_modification as well.
239 
240  if(set_name.empty() == false) {
241  id_ = set_name;
242  }
243 
244  if(set_desc.empty() == false) {
245  description_ = set_desc;
246  }
247 
248  if(set_type.empty() == false) {
249  type_ = set_type;
250  }
251 
252  if(set_range.empty() == false) {
253  range_ = set_range;
254  }
255 
256  if(set_icon.empty() == false) {
257  icon_ = set_icon;
258  }
259 
260  if(del_specials.empty() == false) {
261  const std::vector<std::string>& dsl = utils::split(del_specials);
262  config new_specials;
263  for (const config::any_child &vp : specials_.all_children_range()) {
264  std::vector<std::string>::const_iterator found_id =
265  std::find(dsl.begin(), dsl.end(), vp.cfg["id"].str());
266  if (found_id == dsl.end()) {
267  new_specials.add_child(vp.key, vp.cfg);
268  }
269  }
270  specials_ = new_specials;
271  }
272 
273  if (set_specials) {
274  const std::string &mode = set_specials["mode"];
275  if (mode != "append") {
276  specials_.clear();
277  }
278  for (const config::any_child &value : set_specials.all_children_range()) {
279  specials_.add_child(value.key, value.cfg);
280  }
281  }
282 
283  if(set_damage.empty() == false) {
284  damage_ = std::stoi(set_damage);
285  if (damage_ < 0) {
286  damage_ = 0;
287  }
288  }
289 
290  if(increase_damage.empty() == false) {
291  damage_ = utils::apply_modifier(damage_, increase_damage);
292  if(damage_ < 0) {
293  damage_ = 0;
294  }
295  }
296 
297  if(set_attacks.empty() == false) {
298  num_attacks_ = std::stoi(set_attacks);
299  if (num_attacks_ < 0) {
300  num_attacks_ = 0;
301  }
302 
303  }
304 
305  if(increase_attacks.empty() == false) {
306  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
307  }
308 
309  if(set_accuracy.empty() == false) {
310  accuracy_ = std::stoi(set_accuracy);
311  }
312 
313  if(increase_accuracy.empty() == false) {
314  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
315  }
316 
317  if(set_parry.empty() == false) {
318  parry_ = std::stoi(set_parry);
319  }
320 
321  if(increase_parry.empty() == false) {
322  parry_ = utils::apply_modifier(parry_, increase_parry);
323  }
324 
325  if(set_movement.empty() == false) {
326  movement_used_ = std::stoi(set_movement);
327  }
328 
329  if(increase_movement.empty() == false) {
330  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
331  }
332 
333  if(set_attack_weight.empty() == false) {
334  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
335  }
336 
337  if(set_defense_weight.empty() == false) {
338  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
339  }
340 
341  return true;
342 }
343 
344 /**
345  * Trimmed down version of apply_modification(), with no modifications actually
346  * made. This can be used to get a description of the modification(s) specified
347  * by @a cfg (if *this matches cfg as a filter).
348  *
349  * If *description is provided, it will be set to a (translated) description
350  * of the modification(s) applied (currently only changes to the number of
351  * strikes, damage, accuracy, and parry are included in this description).
352  *
353  * @returns whether or not @c this matched the @a cfg as a filter.
354  */
355 bool attack_type::describe_modification(const config& cfg,std::string* description)
356 {
357  if( !matches_filter(cfg) )
358  return false;
359 
360  // Did the caller want the description?
361  if(description != nullptr) {
362  const std::string& increase_damage = cfg["increase_damage"];
363  const std::string& set_damage = cfg["set_damage"];
364  const std::string& increase_attacks = cfg["increase_attacks"];
365  const std::string& set_attacks = cfg["set_attacks"];
366  const std::string& increase_accuracy = cfg["increase_accuracy"];
367  const std::string& set_accuracy = cfg["set_accuracy"];
368  const std::string& increase_parry = cfg["increase_parry"];
369  const std::string& set_parry = cfg["set_parry"];
370  const std::string& increase_movement = cfg["increase_movement_used"];
371  const std::string& set_movement = cfg["set_movement_used"];
372 
373  std::vector<t_string> desc;
374 
375  if(!increase_damage.empty()) {
376  desc.emplace_back(VNGETTEXT(
377  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
378  "<span color=\"$color\">$number_or_percent</span> damage",
379  "<span color=\"$color\">$number_or_percent</span> damage",
380  std::stoi(increase_damage),
381  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "red" : "green"}}));
382  }
383 
384  if(!set_damage.empty()) {
385  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
386  desc.emplace_back(VNGETTEXT(
387  "$number damage",
388  "$number damage",
389  std::stoi(set_damage),
390  {{"number", set_damage}}));
391  }
392 
393  if(!increase_attacks.empty()) {
394  desc.emplace_back(VNGETTEXT(
395  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
396  "<span color=\"$color\">$number_or_percent</span> strike",
397  "<span color=\"$color\">$number_or_percent</span> strikes",
398  std::stoi(increase_attacks),
399  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "red" : "green"}}));
400  }
401 
402  if(!set_attacks.empty()) {
403  desc.emplace_back(VNGETTEXT(
404  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
405  "$number strike",
406  "$number strikes",
407  std::stoi(set_attacks),
408  {{"number", set_attacks}}));
409  }
410 
411  if(!set_accuracy.empty()) {
412  desc.emplace_back(VGETTEXT(
413  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
414  "$number| accuracy",
415  {{"number", set_accuracy}}));
416  }
417 
418  if(!increase_accuracy.empty()) {
419  desc.emplace_back(VGETTEXT(
420  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
421  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
422  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "red" : "green"}}));
423  }
424 
425  if(!set_parry.empty()) {
426  desc.emplace_back(VGETTEXT(
427  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
428  "$number parry",
429  {{"number", set_parry}}));
430  }
431 
432  if(!increase_parry.empty()) {
433  desc.emplace_back(VGETTEXT(
434  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
435  "<span color=\"$color\">$number_or_percent</span> parry",
436  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "red" : "green"}}));
437  }
438 
439  if(!set_movement.empty()) {
440  desc.emplace_back(VNGETTEXT(
441  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
442  "$number movement point",
443  "$number movement points",
444  std::stoi(set_movement),
445  {{"number", set_movement}}));
446  }
447 
448  if(!increase_movement.empty()) {
449  desc.emplace_back(VNGETTEXT(
450  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
451  "<span color=\"$color\">$number_or_percent</span> movement point",
452  "<span color=\"$color\">$number_or_percent</span> movement points",
453  std::stoi(increase_movement),
454  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "red" : "green"}}));
455  }
456 
457  *description = utils::format_conjunct_list("", desc);
458  }
459 
460  return true;
461 }
462 
463 void attack_type::write(config& cfg) const
464 {
465  cfg["description"] = description_;
466  cfg["name"] = id_;
467  cfg["type"] = type_;
468  cfg["icon"] = icon_;
469  cfg["range"] = range_;
470  cfg["min_range"] = min_range_;
471  cfg["max_range"] = max_range_;
472  cfg["damage"] = damage_;
473  cfg["number"] = num_attacks_;
474  cfg["attack_weight"] = attack_weight_;
475  cfg["defense_weight"] = defense_weight_;
476  cfg["accuracy"] = accuracy_;
477  cfg["movement_used"] = movement_used_;
478  cfg["parry"] = parry_;
479  cfg.add_child("specials", specials_);
480 }
bool apply_modification(const config &cfg)
Modifies *this using the specifications in cfg, but only if *this matches cfg viewed as a filter...
std::string icon_
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:423
const std::string & id() const
Definition: attack_type.hpp:41
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::string type_
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:923
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
attack_type(const config &cfg)
Definition: attack_type.cpp:44
std::string filename
Definition: formula.hpp:107
New lexcical_cast header.
int parry() const
Definition: attack_type.hpp:49
static lg::log_domain log_unit("unit")
bool in_ranges(const Cmp c, const std::vector< std::pair< Cmp, Cmp >> &ranges)
Definition: math.hpp:66
#define VNGETTEXT(msgid, msgid_plural, count,...)
const std::string & type() const
Definition: attack_type.hpp:42
void clear()
Definition: config.cpp:865
void set_changed(bool value)
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
int num_attacks() const
Definition: attack_type.hpp:51
double defense_weight_
static bool matches_simple_filter(const attack_type &attack, const config &filter)
Returns whether or not *this matches the given filter, ignoring the complexities introduced by [and]...
Definition: attack_type.cpp:97
void set_name(const t_string &value)
Definition: attack_type.hpp:56
int movement_used() const
Definition: attack_type.hpp:93
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:598
std::vector< std::pair< int, int > > parse_ranges(const std::string &str)
double attack_weight_
std::string type
Definition: formula.hpp:105
bool matches_filter(const config &filter) const
Returns whether or not *this matches the given filter.
std::string range_
General math utility functions.
const std::string & range() const
Definition: attack_type.hpp:44
config specials_
std::string egettext(char const *msgid)
Definition: gettext.cpp:398
void set_defense_weight(double value)
Definition: attack_type.hpp:66
void set_specials(config value)
Definition: attack_type.hpp:67
t_string description_
void set_damage(int value)
Definition: attack_type.hpp:63
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:269
int damage() const
Definition: attack_type.hpp:50
int accuracy() const
Definition: attack_type.hpp:48
static map_location::DIRECTION s
void write(config &cfg) const
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
std::string accuracy_parry_description() const
Definition: attack_type.cpp:77
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
config & add_child(config_key_type key)
Definition: config.cpp:479
bool describe_modification(const config &cfg, std::string *description)
Trimmed down version of apply_modification(), with no modifications actually made.
void set_range(const std::string &value)
Definition: attack_type.hpp:60
void set_parry(int value)
Definition: attack_type.hpp:62
std::string id_
bool empty() const
Definition: tstring.hpp:182
bool find(E event, F functor)
Tests whether an event handler is available.
int apply_modifier(const int number, const std::string &amount, const int minimum)
void set_icon(const std::string &value)
Definition: attack_type.hpp:59
Standard logging facilities (interface).
#define e
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:320
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
void set_accuracy(int value)
Definition: attack_type.hpp:61
void set_attack_weight(double value)
Definition: attack_type.hpp:65
static lg::log_domain log_config("config")
void set_type(const std::string &value)
Definition: attack_type.hpp:58