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