39 #define ERR_CF LOG_STREAM(err, log_config)
40 #define WRN_CF LOG_STREAM(warn, log_config)
41 #define LOG_CONFIG LOG_STREAM(info, log_config)
42 #define DBG_CF LOG_STREAM(debug, log_config)
45 #define DBG_UT LOG_STREAM(debug, log_unit)
46 #define ERR_UT LOG_STREAM(err, log_unit)
49 #define ERR_WML LOG_STREAM(err, log_wml)
52 #define ERR_NG LOG_STREAM(err, log_engine)
57 , description_(
cfg[
"description"].t_str())
61 , range_(
cfg[
"range"])
62 , min_range_(
cfg[
"min_range"].to_int(1))
63 , max_range_(
cfg[
"max_range"].to_int(1))
65 , damage_(
cfg[
"damage"].to_int())
66 , num_attacks_(
cfg[
"number"].to_int())
67 , attack_weight_(
cfg[
"attack_weight"].to_double(1.0))
68 , defense_weight_(
cfg[
"defense_weight"].to_double(1.0))
69 , accuracy_(
cfg[
"accuracy"].to_int())
70 , movement_used_(
cfg[
"movement_used"].to_int(100000))
71 , attacks_used_(
cfg[
"attacks_used"].to_int(1))
72 , parry_(
cfg[
"parry"].to_int())
86 icon_ =
"attacks/blank-attack.png";
125 bool special_checking(
const std::string& special_id,
const std::string& tag_name,
const std::set<std::string>& filter_special,
const std::set<std::string>& filter_special_id,
const std::set<std::string>& filter_special_type)
127 if (!filter_special.empty() && filter_special.count(special_id) == 0 && filter_special.count(tag_name) == 0)
130 if (!filter_special_id.empty() && filter_special_id.count(special_id) == 0)
133 if (!filter_special_type.empty() && filter_special_type.count(tag_name) == 0)
143 if (
range().empty()) {
149 for (
const auto& p_ab :
specials()) {
150 if (special_checking(p_ab->id(), p_ab->tag(), filter_special, filter_special_id, filter_special_type)) {
164 const std::string& filter_min_range =
filter[
"min_range"];
165 const std::string& filter_max_range =
filter[
"max_range"];
166 const std::string& filter_damage =
filter[
"damage"];
167 const std::string& filter_attacks =
filter[
"number"];
168 const std::string& filter_accuracy =
filter[
"accuracy"];
169 const std::string& filter_parry =
filter[
"parry"];
170 const std::string& filter_movement =
filter[
"movement_used"];
171 const std::string& filter_attacks_used =
filter[
"attacks_used"];
176 const std::vector<std::string> filter_special_active =
utils::split(
filter[
"special_active"]);
177 const std::vector<std::string> filter_special_id_active =
utils::split(
filter[
"special_id_active"]);
178 const std::vector<std::string> filter_special_type_active =
utils::split(
filter[
"special_type_active"]);
179 const std::string filter_formula =
filter[
"formula"];
187 if ( !filter_range.empty() && filter_range.count(attack.
range()) == 0 )
208 if(!filter_alignment.empty() && filter_alignment.count(attack.
alignment_str()) == 0)
211 if ( !filter_name.empty() && filter_name.count(attack.
id()) == 0)
214 if (!filter_type.empty()){
219 if(check_if_recursion ==
"damage_type"){
220 if (filter_type.count(attack.
type()) == 0){
231 if ( !filter_base_type.empty() && filter_base_type.count(attack.
type()) == 0 )
234 if(
filter.has_attribute(
"special")) {
238 if(
filter.has_attribute(
"special") ||
filter.has_attribute(
"special_id") ||
filter.has_attribute(
"special_type")) {
244 if(
filter.has_attribute(
"special_active")) {
248 if (!filter_special_type_active.empty()) {
254 if (!filter_special_id_active.empty()) {
260 if (!filter_special_active.empty()) {
261 auto pred = [&](
const std::string& special) {
272 if(
auto sub_filter_special =
filter.optional_child(
"filter_special")) {
278 if (!filter_formula.empty()) {
287 lg::log_to_chat() <<
"Formula error in weapon filter: " <<
e.type <<
" at " <<
e.filename <<
':' <<
e.line <<
")\n";
288 ERR_WML <<
"Formula error in weapon filter: " <<
e.type <<
" at " <<
e.filename <<
':' <<
e.line <<
")";
307 for(
const auto [key, condition_cfg] :
filter.all_children_view() )
311 matches = matches &&
matches_filter(condition_cfg, check_if_recursion);
314 else if ( key ==
"or" )
315 matches = matches ||
matches_filter(condition_cfg, check_if_recursion);
318 else if ( key ==
"not" )
319 matches = matches && !
matches_filter(condition_cfg, check_if_recursion);
329 if((**i).matches_filter(
filter)) {
341 const t_string& set_desc =
cfg[
"set_description"].t_str();
374 if(set_desc.
empty() ==
false) {
394 if(del_specials.
empty() ==
false) {
395 const std::vector<std::string>& dsl =
utils::split(del_specials);
399 new_specials.emplace_back(std::move(p_ab));
406 const std::string &mode = set_specials[
"mode"];
409 "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.");
412 if(mode !=
"append") {
422 for(
const auto [key,
cfg] : set_specials->all_children_view()) {
431 if(increase_min_range.
empty() ==
false) {
439 if(increase_max_range.
empty() ==
false) {
443 if(remove_specials) {
454 if(increase_damage.
empty() ==
false) {
461 if(set_attacks.
empty() ==
false) {
469 if(increase_attacks.
empty() ==
false) {
477 if(increase_accuracy.
empty() ==
false) {
485 if(increase_parry.
empty() ==
false) {
489 if(set_movement.
empty() ==
false) {
493 if(increase_movement.
empty() ==
false) {
501 if(increase_attacks_used.
empty() ==
false) {
534 return {{
"number_or_percent",
utils::print_modifier(attr)}, {
"color", attr.to_int() < 0 ?
"#f00" :
"#0f0"}};
537 std::vector<t_string> desc;
546 if(!increase_min_range.
empty()) {
549 "<span color=\"$color\">$number_or_percent</span> min range",
550 format_modifier(increase_min_range)));
560 if(!increase_max_range.
empty()) {
563 "<span color=\"$color\">$number_or_percent</span> max range",
564 format_modifier(increase_max_range)));
567 if(!increase_damage.
empty()) {
570 "<span color=\"$color\">$number_or_percent</span> damage",
571 "<span color=\"$color\">$number_or_percent</span> damage",
573 format_modifier(increase_damage)));
582 {{
"number", set_damage.str()}}));
585 if(!increase_attacks.empty()) {
588 "<span color=\"$color\">$number_or_percent</span> strike",
589 "<span color=\"$color\">$number_or_percent</span> strikes",
590 increase_attacks.to_int(),
591 format_modifier(increase_attacks)));
594 if(!set_attacks.empty()) {
599 set_attacks.to_int(),
600 {{
"number", set_attacks.str()}}));
603 if(!set_accuracy.empty()) {
607 {{
"number", set_accuracy.str()}}));
610 if(!increase_accuracy.empty()) {
613 "<span color=\"$color\">$number_or_percent|%</span> accuracy",
614 format_modifier(increase_accuracy)));
617 if(!set_parry.empty()) {
621 {{
"number", set_parry.str()}}));
624 if(!increase_parry.empty()) {
627 "<span color=\"$color\">$number_or_percent</span> parry",
628 format_modifier(increase_parry)));
631 if(!set_movement.empty()) {
634 "$number movement point",
635 "$number movement points",
636 set_movement.to_int(),
637 {{
"number", set_movement.str()}}));
640 if(!increase_movement.empty()) {
643 "<span color=\"$color\">$number_or_percent</span> movement point",
644 "<span color=\"$color\">$number_or_percent</span> movement points",
645 increase_movement.to_int(),
646 format_modifier(increase_movement)));
649 if(!set_attacks_used.empty()) {
652 "$number attack used",
653 "$number attacks used",
654 set_attacks_used.to_int(),
655 {{
"number", set_attacks_used.str()}}));
658 if(!increase_attacks_used.empty()) {
661 "<span color=\"$color\">$number_or_percent</span> attack used",
662 "<span color=\"$color\">$number_or_percent</span> attacks used",
663 increase_attacks_used.to_int(),
664 format_modifier(increase_attacks_used)));
707 unsigned& max_attacks)
const
712 if (attacks_value < 0) {
714 ERR_NG <<
"negative number of strikes after applying weapon specials";
719 if (!swarm_specials.
empty()) {
720 min_attacks = std::max<int>(0, swarm_specials.
highest(
"swarm_attacks_min").first);
721 max_attacks = std::max<int>(0, swarm_specials.
highest(
"swarm_attacks_max", attacks_value).first);
724 min_attacks = max_attacks = attacks_value;
730 std::map<std::string, unsigned int> type_count;
731 unsigned int max = 0;
732 for (
auto&
i : damage_type_list) {
734 if (
c.has_attribute(
"replacement_type")) {
735 std::string
type =
c[
"replacement_type"].str();
736 unsigned int count = ++type_count[
type];
743 if (type_count.empty())
return type();
745 std::vector<std::string> type_list;
746 for (
auto&
i : type_count) {
747 if (
i.second == max) {
748 type_list.push_back(
i.first);
752 if (type_list.empty())
return type();
754 return type_list.front();
761 std::map<std::string, int> type_res;
762 int max_res = INT_MIN;
764 for (
auto&
i : damage_type_list) {
766 if (
c.has_attribute(
"alternative_type")) {
767 std::string
type =
c[
"alternative_type"].str();
768 if (type_res.count(
type) == 0) {
769 type_res[
type] = other.un->resistance_value(resistance_list,
type);
770 max_res = std::max(max_res, type_res[
type]);
776 if (type_res.empty())
return {
"", INT_MIN };
778 std::vector<std::string> type_list;
779 for (
auto&
i : type_res) {
780 if (
i.second == max_res) {
781 type_list.push_back(
i.first);
784 if (type_list.empty())
return {
"", INT_MIN };
786 return { type_list.front(), max_res };
804 return !
i.ability().active_on_matches(!is_attacker);
808 int res = other.un ? other.un->resistance_value(resistance_list,
type()) : 100;
809 if (damage_type_list.
empty()) {
810 return {
type(), res };
816 res = replacement_type !=
type() ? other.un->resistance_value(resistance_list, replacement_type) : res;
817 replacement_type = alternative_type.second > res ? alternative_type.first : replacement_type;
818 res = std::max(res, alternative_type.second);
820 return { replacement_type, res };
829 std::set<std::string> alternative_damage_types;
830 if (damage_type_list.
empty()) {
831 return {
type(), alternative_damage_types };
834 for (
auto&
i : damage_type_list) {
836 if (
c.has_attribute(
"alternative_type")) {
837 alternative_damage_types.insert(
c[
"alternative_type"].str());
841 return { replacement_type, alternative_damage_types };
856 int parry = other.at ? other.at->parry() : 0;
867 bool attacking =
true;
887 if (!abil_list.empty() && !overwriters.
empty()) {
915 if (!
filter[
"active"].to_bool()) {
929 std::vector<unit_ability_t::tooltip_info> res;
930 for (
const auto& p_ab :
specials()) {
931 auto name = p_ab->get_name();
932 auto desc = p_ab->get_description();
935 res.AGGREGATE_EMPLACE(
938 p_ab->get_help_topic_id()
std::vector< ability_ptr > ability_vector
static lg::log_domain log_unit("unit")
static lg::log_domain log_engine("engine")
static lg::log_domain log_wml("wml")
static bool matches_simple_filter(const attack_type &attack, const config &filter, const std::string &check_if_recursion)
Returns whether or not *this matches the given filter, ignoring the complexities introduced by [and],...
static lg::log_domain log_config("config")
std::pair< int, map_location > highest(const std::string &key, int def=0) const
std::string alignment_str() const
Returns alignment specified by alignment() for filtering when exist.
void set_min_range(int value)
active_ability_list overwrite_special_overwriter(active_ability_list overwriters) const
Filter a list of abilities or weapon specials, removing any entries that don't own the overwrite_spec...
bool has_active_special_or_ability_id(const std::string &special) const
const std::string & range() const
void set_attacks_used(int value)
int movement_used() const
bool has_special_or_ability_with_filter(const config &filter) const
check if special matche
void set_accuracy(int value)
std::vector< unit_ability_t::tooltip_info > special_tooltips() const
Returns a vector of names and descriptions for the specials of *this.
const std::string & type() const
std::pair< std::string, int > select_alternative_type(const active_ability_list &damage_type_list, const active_ability_list &resistance_list) const
Select best damage type based on highest damage for alternative_type.
bool has_special_or_ability(const std::string &special) const
used for abilities used like weapon and true specials
std::string accuracy_parry_tooltip() const
std::string accuracy_parry_description() const
std::unique_ptr< specials_context_t > fallback_context(const unit_ptr &self=nullptr) const
double modified_damage() const
Returns the damage per attack of this weapon, considering specials.
void apply_effect(const config &cfg)
Applies effect modifications described by cfg.
bool matches_filter(const config &filter, const std::string &check_if_recursion="") const
Returns whether or not *this matches the given filter.
std::string select_replacement_type(const active_ability_list &damage_type_list) const
Select best damage type based on frequency count for replacement_type.
config specials_cfg() const
friend class specials_context_t
void set_defense_weight(double value)
void set_changed(bool value)
active_ability_list get_specials_and_abilities(const std::string &special) const
std::pair< std::string, std::set< std::string > > damage_types() const
Return a type()/replacement_type and a list of alternative_types that should be displayed in the sele...
void set_parry(int value)
void set_attack_weight(double value)
void set_damage(int value)
const t_string & name() const
const std::string & id() const
void set_icon(const std::string &value)
bool has_filter_special_or_ability(const config &filter) const
check if special matche handles the special_(id/type) attributes in weapon filters.
int modified_chance_to_hit(int cth) const
Return the defense value, considering specials.
bool special_active(const unit_ability_t &ab, AFFECTS whom) const
bool attack_empty() const
Returns true if this is a dummy attack_type, for example the placeholder that the unit_attack dialog ...
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
void remove_special_by_filter(const config &filter)
remove special if matche condition
void set_max_range(int value)
specials_context_t * context_
utils::optional< unit_alignments::type > alignment_
attack_type(const config &cfg)
void set_type(const std::string &value)
void write(config &cfg) const
bool overwrite_special_checking(active_ability_list &overwriters, const unit_ability_t &ab) const
Check whether cfg would be overwritten by any element of overwriters.
void set_range(const std::string &value)
int composite_value(const active_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
static std::string describe_effect(const config &cfg)
Generates a description of the effect specified by cfg, if applicable.
const ability_vector & specials() const
void set_attack_alignment(const std::string &value)
void set_name(const t_string &value)
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Variant for storing WML attributes.
int to_int(int def=0) const
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
config & add_child(std::string_view key)
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
auto all_children_view() const
In-order iteration over all children.
bool has_active_special(const attack_type &at, const std::string &tag) const
Returns whether or not *this has a special ability with a tag or id equal to special.
active_ability_list get_abilities_weapons(const std::string &tag, const unit &un) const
bool is_special_active(const specials_combatant &wep, const unit_ability_t &ab, unit_ability_t::affects_t whom) const
Returns whether or not the given special is active for the specified unit, based on the current conte...
specials_combatant attacker
self_and_other_red_t self_and_other(const attack_type &self_att) const
bool has_active_special_id(const attack_type &at, const std::string &id) const
bool has_active_special_matching_filter(const attack_type &at, const config &filter) const
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
active_ability_list get_active_specials(const attack_type &at, const std::string &tag) const
int get_composite_value() const
double get_composite_double_value() const
static void parse_vector(const config &abilities_cfg, ability_vector &res, bool inside_attack)
static ability_ptr create(std::string tag, config cfg, bool inside_attack)
static config add_registry_entries(const config &base_cfg, const std::string ®istry_name, const std::map< std::string, config > ®istry)
const std::map< std::string, config > & specials() const
bool as_bool() const
Returns a boolean state of the variant value.
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Standard logging facilities (interface).
General math utility functions.
bool in_ranges(const Cmp c, const std::vector< std::pair< Cmp, Cmp >> &ranges)
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
std::string egettext(char const *msgid)
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
std::vector< std::pair< int, int > > parse_ranges_int(const std::string &str)
Handles a comma-separated list of inputs to parse_range.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
int apply_modifier(const int number, const std::string &amount, const int minimum)
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::map< std::string, t_string > string_map
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
std::vector< std::string > split(const config_attribute_value &val)
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
std::shared_ptr< unit_ability_t > ability_ptr
std::shared_ptr< unit > unit_ptr
Data typedef for active_ability_list.
const unit_ability_t & ability() const
Encapsulates the map of the game.
The base template for associating string values with enum values.
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
static map_location::direction s
unit_type_data unit_types