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