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