Skip to content

Commit

Permalink
Solve the infinite recursion problem more completely.
Browse files Browse the repository at this point in the history
By arranging so that the damage_type and all the functions it calls (get_special() etc.) have the no_check parameter, we allow damage_type to be called only once in matches_filter but not a second time. The modified types can thus be checked when filtering the opponent without risk.
  • Loading branch information
newfrenchy83 committed Nov 1, 2023
1 parent 746e6d9 commit 7a6d5e8
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 48 deletions.
8 changes: 0 additions & 8 deletions data/schema/filters/weapon.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,3 @@
{FILTER_BOOLEAN_OPS weapon}
[/tag]

[tag]
name="$filter_weapon_by_special"
max=0
super="$filter_weapon"
{SIMPLE_KEY modified_type string_list}
{FILTER_BOOLEAN_OPS weapon}
[/tag]

8 changes: 4 additions & 4 deletions data/schema/units/abilities.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@
name="resistance"
max=infinite
super="units/unit_type/abilities/~value~"
{FILTER_TAG "filter_weapon" weapon_by_special ()}
{FILTER_TAG "filter_second_weapon" weapon_by_special ()}
{FILTER_TAG "filter_weapon" weapon ()}
{FILTER_TAG "filter_second_weapon" weapon ()}
[/tag]
[tag]
name="leadership"
Expand All @@ -95,8 +95,8 @@
{SIMPLE_KEY sub f_int}
{SIMPLE_KEY multiply f_int}
{SIMPLE_KEY divide f_int}
{FILTER_TAG "filter_weapon" weapon_by_special ()}
{FILTER_TAG "filter_second_weapon" weapon_by_special ()}
{FILTER_TAG "filter_weapon" weapon ()}
{FILTER_TAG "filter_second_weapon" weapon ()}
[/tag]
[tag]
name="illuminates"
Expand Down
8 changes: 4 additions & 4 deletions data/schema/units/specials.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

{FILTER_TAG "filter_adjacent" adjacent ()}
{FILTER_TAG "filter_adjacent_location" adjacent_location ()}
{FILTER_TAG "filter_self" unit {FILTER_TAG "filter_weapon" weapon_by_special ()}}
{FILTER_TAG "filter_opponent" unit {FILTER_TAG "filter_weapon" weapon_by_special ()}}
{FILTER_TAG "filter_attacker" unit {FILTER_TAG "filter_weapon" weapon_by_special ()}}
{FILTER_TAG "filter_defender" unit {FILTER_TAG "filter_weapon" weapon_by_special ()}}
{FILTER_TAG "filter_self" unit {FILTER_TAG "filter_weapon" weapon ()}}
{FILTER_TAG "filter_opponent" unit {FILTER_TAG "filter_weapon" weapon ()}}
{FILTER_TAG "filter_attacker" unit {FILTER_TAG "filter_weapon" weapon ()}}
{FILTER_TAG "filter_defender" unit {FILTER_TAG "filter_weapon" weapon ()}}
{WML_MERGE_KEYS}
[/tag]
# A few specials inheriting from ~generic~ are included here so that unit abilities can then inherit from them.
Expand Down
49 changes: 26 additions & 23 deletions src/units/abilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ bool unit::ability_affects_weapon(const config& cfg, const_attack_ptr weapon, bo
if(!weapon) {
return false;
}
return weapon->matches_filter(filter, true);
return weapon->matches_filter(filter);
}

bool unit::has_ability_type(const std::string& ability) const
Expand Down Expand Up @@ -823,14 +823,14 @@ bool attack_type::has_special(const std::string& special, bool simple_check, boo
* Returns the currently active specials as an ability list, given the current
* context (see set_specials_context).
*/
unit_ability_list attack_type::get_specials(const std::string& special) const
unit_ability_list attack_type::get_specials(const std::string& special, bool no_check) const
{
//log_scope("get_specials");
const map_location loc = self_ ? self_->get_location() : self_loc_;
unit_ability_list res(loc);

for(const config& i : specials_.child_range(special)) {
if(special_active(i, AFFECT_SELF, special)) {
if(special_active(i, AFFECT_SELF, special, "filter_self", no_check)) {
res.emplace_back(&i, loc, loc);
}
}
Expand All @@ -840,7 +840,7 @@ unit_ability_list attack_type::get_specials(const std::string& special) const
}

for(const config& i : other_attack_->specials_.child_range(special)) {
if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
if(other_attack_->special_active(i, AFFECT_OTHER, special, "filter_self", no_check)) {
res.emplace_back(&i, other_loc_, other_loc_);
}
}
Expand Down Expand Up @@ -1195,9 +1195,9 @@ static std::vector<std::string> damage_type_list(const unit_ability_list& abil_l
/**
* Returns the type of damage inflicted.
*/
std::pair<std::string, std::string> attack_type::damage_type() const
std::pair<std::string, std::string> attack_type::damage_type(bool no_check) const
{
unit_ability_list abil_list = get_specials_and_abilities("damage");
unit_ability_list abil_list = get_specials_and_abilities("damage", no_check);
if(abil_list.empty()){
return {type(), ""};
}
Expand Down Expand Up @@ -1273,19 +1273,20 @@ namespace { // Helpers for attack_type::special_active()
* (normally a [filter_*] child) of the provided filter.
* @param[in] u A unit to filter.
* @param[in] u2 Another unit to filter.
* @param[in] loc The presumed location of @a un_it.
* @param[in] loc The presumed location of @a unit.
* @param[in] weapon The attack_type to filter.
* @param[in] filter The filter containing the child filter to use.
* @param[in] for_listing
* @param[in] child_tag The tag of the child filter to use.
* @param[in] no_check Parameter for decide is a special can be called in matches_filter.
*/
static bool special_unit_matches(unit_const_ptr & u,
unit_const_ptr & u2,
const map_location & loc,
const_attack_ptr weapon,
const config & filter,
const bool for_listing,
const std::string & child_tag)
const std::string & child_tag, bool no_check)
{
if (for_listing && !loc.valid())
// The special's context was set to ignore this unit, so assume we pass.
Expand All @@ -1312,7 +1313,7 @@ namespace { // Helpers for attack_type::special_active()

// Check for a weapon match.
if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
if ( !weapon || !weapon->matches_filter(*filter_weapon, true) )
if ( !weapon || !weapon->matches_filter(*filter_weapon, no_check) )
return false;
}

Expand All @@ -1333,31 +1334,31 @@ namespace { // Helpers for attack_type::special_active()
//beneficiary unit does not have a corresponding weapon
//(defense against ranged weapons abilities for a unit that only has melee attacks)

unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
unit_ability_list attack_type::get_weapon_ability(const std::string& ability, bool no_check) const
{
const map_location loc = self_ ? self_->get_location() : self_loc_;
unit_ability_list abil_list(loc);
if(self_) {
abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student");
return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student", no_check);
});
}

if(other_) {
abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student");
return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student", no_check);
});
}

return abil_list;
}

unit_ability_list attack_type::get_specials_and_abilities(const std::string& special) const
unit_ability_list attack_type::get_specials_and_abilities(const std::string& special, bool no_check) const
{
// get all weapon specials of the provided type
unit_ability_list abil_list = get_specials(special);
unit_ability_list abil_list = get_specials(special, no_check);
// append all such weapon specials as abilities as well
abil_list.append(get_weapon_ability(special));
abil_list.append(get_weapon_ability(special, no_check));
// get a list of specials/"specials as abilities" that may potentially overwrite others
unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
if(!abil_list.empty() && !overwriters.empty()){
Expand Down Expand Up @@ -1674,9 +1675,9 @@ bool attack_type::has_special_or_ability(const std::string& special, bool specia
//end of emulate weapon special functions.

bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
const std::string& filter_self) const
const std::string& filter_self, bool no_check) const
{
return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self);
return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self, no_check);
}

/**
Expand All @@ -1687,15 +1688,17 @@ bool attack_type::special_active(const config& special, AFFECTS whom, const std:
* @param special a weapon special WML structure
* @param whom specifies which combatant we care about
* @param tag_name tag name of the special config
* @param filter_self the filter to use
* @param filter_self the filter to use
* @param no_check Param for know if special can be called in matches_filter
*/
bool attack_type::special_active_impl(
const_attack_ptr self_attack,
const_attack_ptr other_attack,
const config& special,
AFFECTS whom,
const std::string& tag_name,
const std::string& filter_self)
const std::string& filter_self,
bool no_check)
{
assert(self_attack || other_attack);
bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
Expand Down Expand Up @@ -1810,13 +1813,13 @@ bool attack_type::special_active_impl(
const config& special_backstab = special["backstab"].to_bool() ? cfg : special;

// Filter the units involved.
if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self))
if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, no_check))
return false;
if (!special_unit_matches(other, self, other_loc, other_attack, special_backstab, is_for_listing, "filter_opponent"))
if (!special_unit_matches(other, self, other_loc, other_attack, special_backstab, is_for_listing, "filter_opponent", no_check))
return false;
if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker"))
if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", no_check))
return false;
if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender"))
if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", no_check))
return false;

const auto adjacent = get_adjacent_tiles(self_loc);
Expand Down
11 changes: 8 additions & 3 deletions src/units/attack_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ std::string attack_type::accuracy_parry_description() const
return s.str();
}

static bool check_string(const std::vector<std::string>& filter_attribute, const std::string& attribute)
{
return (std::find(filter_attribute.begin(), filter_attribute.end(), attribute) != filter_attribute.end());
}

/**
* Returns whether or not *this matches the given @a filter, ignoring the
* complexities introduced by [and], [or], and [not].
Expand Down Expand Up @@ -146,12 +151,12 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil

if (!filter_type.empty()){
if(no_check){
if (std::find(filter_type.begin(), filter_type.end(), attack.type()) == filter_type.end() ){
if (!check_string(filter_type, attack.type())){
return false;
}
} else {
std::pair<std::string, std::string> damage_type = attack.damage_type();
if ((std::find(filter_type.begin(), filter_type.end(), damage_type.first) == filter_type.end()) && (std::find(filter_type.begin(), filter_type.end(), damage_type.second) == filter_type.end())){
std::pair<std::string, std::string> damage_type = attack.damage_type(true);
if (!check_string(filter_type, attack.type()) && !check_string(filter_type, damage_type.first) && !check_string(filter_type, damage_type.second)){
return false;
}
}
Expand Down
15 changes: 9 additions & 6 deletions src/units/attack_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class attack_type : public std::enable_shared_from_this<attack_type>
* @param special_tags If true, match @a special against the tag name of special tags.
*/
bool has_special(const std::string& special, bool simple_check=false, bool special_id=true, bool special_tags=true) const;
unit_ability_list get_specials(const std::string& special) const;
unit_ability_list get_specials(const std::string& special, bool no_check = false) const;
std::vector<std::pair<t_string, t_string>> special_tooltips(boost::dynamic_bitset<>* active_list = nullptr) const;
std::string weapon_specials() const;
std::string weapon_specials_value(const std::set<std::string> checking_tags) const;
Expand All @@ -90,7 +90,7 @@ class attack_type : public std::enable_shared_from_this<attack_type>
unsigned & max_attacks) const;

/** return a modified damage type and/or add a secondary_type for hybrid use if special is active. */
std::pair<std::string, std::string> damage_type() const;
std::pair<std::string, std::string> damage_type(bool no_check = false) const;

/** Returns the damage per attack of this weapon, considering specials. */
int modified_damage() const;
Expand All @@ -101,12 +101,13 @@ class attack_type : public std::enable_shared_from_this<attack_type>
*/
int composite_value(const unit_ability_list& abil_list, int base_value) const;
/** Returns list for weapon like abilities for each ability type. */
unit_ability_list get_weapon_ability(const std::string& ability) const;
unit_ability_list get_weapon_ability(const std::string& ability, bool no_check = false) const;
/**
* @param special the tag name to check for
* @param no_check the param for know if call special inside mathes_filter()
* @return list which contains get_weapon_ability and get_specials list for each ability type, with overwritten items removed
*/
unit_ability_list get_specials_and_abilities(const std::string& special) const;
unit_ability_list get_specials_and_abilities(const std::string& special, bool no_check = false) const;
/** used for abilities used like weapon
* @return True if the ability @a special is active.
* @param special The special being checked.
Expand Down Expand Up @@ -174,7 +175,7 @@ class attack_type : public std::enable_shared_from_this<attack_type>
*/
bool check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const;
bool special_active(const config& special, AFFECTS whom, const std::string& tag_name,
const std::string& filter_self ="filter_self") const;
const std::string& filter_self ="filter_self", bool no_check = false) const;

/** weapon_specials_impl_self and weapon_specials_impl_adj : check if special name can be added.
* @param[in,out] temp_string the string modified and returned
Expand Down Expand Up @@ -266,7 +267,9 @@ class attack_type : public std::enable_shared_from_this<attack_type>
const config& special,
AFFECTS whom,
const std::string& tag_name,
const std::string& filter_self ="filter_self"
const std::string& filter_self ="filter_self",
bool leader_bool=false,
bool no_check = false
);

// Used via specials_context() to control which specials are
Expand Down

0 comments on commit 7a6d5e8

Please sign in to comment.