Skip to content

Commit

Permalink
add a [remove_specials] tag in [effect] and [filter_special] to [has_…
Browse files Browse the repository at this point in the history
…attack/filter_weapon]

with this, it is possible to  simultaneously check specials with id and type, and/or other attributes for remove_specials effector filter_weapon
  • Loading branch information
newfrenchy83 committed Oct 29, 2024
1 parent 5208905 commit ed3147e
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changelog_entries/add_filter_special.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### WML Engine
* add a [remove_specials] tag in [effect] to be able to remove specials with other criteria than the id (type of the special, active_on, apply_to or other attributes)
* add [filter_special] to [has_attack/filter_weapon] in order to simultaneously check specials with id and type, and/or other attributes
1 change: 1 addition & 0 deletions data/schema/filters/weapon.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
{SIMPLE_KEY accuracy s_unsigned_range_list}
{SIMPLE_KEY movement_used s_unsigned_range_list}
{SIMPLE_KEY attacks_used s_unsigned_range_list}
{FILTER_TAG "filter_special" abilities {SIMPLE_KEY active s_bool}}
{FILTER_BOOLEAN_OPS weapon}
[/tag]
4 changes: 4 additions & 0 deletions data/schema/units/modifications.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
super="units/unit_type~core/attack/specials"
{DEFAULT_KEY mode effect_set_special_mode replace}
[/tag]
[tag]
name="remove_specials"
super="$filter_abilities"
[/tag]
[/case]
[case]
value=movement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,109 @@
[/event]
)}

#####
# API(s) being tested: [event][filter_attack][filter_special]active=yes
##
# Actions:
# Use the common setup from FILTER_ABILITY_TEST.
# Add an event with a filter matching Alice's drains ability, but only when it's active.
# Alice attacks Bob and then Bob attacks Alice, as defined in FILTER_ABILITY_TEST.
##
# Expected end state:
# The filtered event is triggered exactly once.
#####
{GENERIC_UNIT_TEST event_test_filter_special_active (
{FILTER_ABILITY_TEST}
[event]
name=attack
first_time_only=no
[filter_attack]
[filter_special]
active=yes
tag_name=drains
value=25
[/filter_special]
[/filter_attack]
{ASSERT ({VARIABLE_CONDITIONAL side_number equals 1})}
{ASSERT ({VARIABLE_CONDITIONAL triggers equals 0})}
{VARIABLE_OP triggers add 1}
[/event]
[event]
name=turn 2
{RETURN ({VARIABLE_CONDITIONAL triggers equals 1})}
[/event]
)}

#####
# API(s) being tested: [event][filter_attack][filter_special]active=yes
##
# Actions:
# Use the common setup from FILTER_ABILITY_TEST.
# Add an event with a filter matching Alice's drains ability, but only when it's active.
# Give Alice the Illuminates ability, which makes it the wrong time of day for her drains ability.
# The events in FILTER_ABILITY_TEST make Alice attacks Bob and then Bob attack Alice.
##
# Expected end state:
# The filtered event is never triggered.
#####
{GENERIC_UNIT_TEST event_test_filter_special_active_inactive (
{MORNING}
{FILTER_ABILITY_TEST}

[event]
name=attack
first_time_only=no
[filter_attack]
[filter_special]
active=yes
tag_name=drains
value=25
[/filter_special]
[/filter_attack]
{FAIL}
[/event]

[event]
name=turn 2
{SUCCEED}
[/event]
)}

#####
# API(s) being tested: [event][filter_attack][filter_special]
##
# Actions:
# Use the common setup from FILTER_ABILITY_TEST.
# Add an event with a filter matching Alice's drains ability.
# Give Alice the Illuminates ability, which makes it the wrong time of day for her drains ability.
# The events in FILTER_ABILITY_TEST make Alice attacks Bob and then Bob attack Alice.
##
# Expected end state:
# The filtered event is triggered exactly once.
#####
{GENERIC_UNIT_TEST event_test_filter_special_simple_check (
{MORNING}
{FILTER_ABILITY_TEST}

[event]
name=attack
first_time_only=no
[filter_attack]
[filter_special]
tag_name=drains
value=25
[/filter_special]
[/filter_attack]
{ASSERT ({VARIABLE_CONDITIONAL side_number equals 1})}
{ASSERT ({VARIABLE_CONDITIONAL triggers equals 0})}
{VARIABLE_OP triggers add 1}
[/event]
[event]
name=turn 2
{RETURN ({VARIABLE_CONDITIONAL triggers equals 1})}
[/event]
)}

#undef FILTER_ABILITY_TEST

##
Expand Down
99 changes: 99 additions & 0 deletions src/units/abilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,105 @@ bool attack_type::special_matches_filter(const config & cfg, const std::string&
return common_matches_filter(cfg, tag_name, filter);
}

bool attack_type::has_special_with_filter(const config & filter) const
{
using namespace utils::config_filters;
bool check_if_active = filter["active"].to_bool();
for(const auto [key, cfg] : specials().all_children_view()) {
if(special_matches_filter(cfg, key, filter)){
if(!check_if_active){
return true;
}
if ( special_active(cfg, AFFECT_SELF, key) ) {
return true;
}
}
}

if(!check_if_active || !other_attack_){
return false;
}

for(const auto [key, cfg] : other_attack_->specials().all_children_view()) {
if(other_attack_->special_matches_filter(cfg, key, filter)){
if ( other_attack_->special_active(cfg, AFFECT_OTHER, key) ) {
return true;
}
}
}

return false;
}

bool attack_type::has_ability_with_filter(const config & filter) const
{
bool check_if_active = filter["active"].to_bool();
const unit_map& units = get_unit_map();
if(self_){
for(const auto [key, cfg] : (*self_).abilities().all_children_view()) {
if(self_->ability_matches_filter(cfg, key, filter)){
if(!check_if_active){
return true;
}
if(check_self_abilities(cfg, key)){
return true;
}
}
}

if(!check_if_active){
return false;
}

const auto adjacent = get_adjacent_tiles(self_loc_);
for(unsigned i = 0; i < adjacent.size(); ++i) {
const unit_map::const_iterator it = units.find(adjacent[i]);
if (it == units.end() || it->incapacitated())
continue;
if ( &*it == self_.get() )
continue;

for(const auto [key, cfg] : it->abilities().all_children_view()) {
if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities(cfg, key, i , *it)){
return true;
}
}
}
}

if(other_){
for(const auto [key, cfg] : (*other_).abilities().all_children_view()) {
if(other_->ability_matches_filter(cfg, key, filter) && check_self_abilities_impl(other_attack_, shared_from_this(), cfg, other_, other_loc_, AFFECT_OTHER, key)){
return true;
}
}

const auto adjacent = get_adjacent_tiles(other_loc_);
for(unsigned i = 0; i < adjacent.size(); ++i) {
const unit_map::const_iterator it = units.find(adjacent[i]);
if (it == units.end() || it->incapacitated())
continue;
if ( &*it == other_.get() )
continue;

for(const auto [key, cfg] : it->abilities().all_children_view()) {
if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities_impl(other_attack_, shared_from_this(), cfg, other_, *it, i, other_loc_, AFFECT_OTHER, key)){
return true;
}
}
}
}
return false;
}

bool attack_type::has_special_or_ability_with_filter(const config & filter) const
{
if(range().empty()){
return false;
}
return (has_special_with_filter(filter) || has_ability_with_filter(filter));
}

bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
const std::string& filter_self) const
{
Expand Down
25 changes: 25 additions & 0 deletions src/units/attack_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil
}
}

//children filter_special are checked later,
//but only when the function doesn't return earlier
if(auto sub_filter_special = filter.optional_child("filter_special")) {
if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
return false;
}
}

if (!filter_formula.empty()) {
try {
const wfl::attack_type_callable callable(attack);
Expand Down Expand Up @@ -306,6 +314,18 @@ bool attack_type::matches_filter(const config& filter, const std::string& check_
return matches;
}

void attack_type::remove_special_by_filter(const config& filter)
{
config::all_children_iterator i = specials_.ordered_begin();
while (i != specials_.ordered_end()) {
if(special_matches_filter(i->cfg, i->key, filter)) {
i = specials_.erase(i);
} else {
++i;
}
}
}

/**
* Modifies *this using the specifications in @a cfg, but only if *this matches
* @a cfg viewed as a filter.
Expand All @@ -330,6 +350,7 @@ bool attack_type::apply_modification(const config& cfg)
const std::string& set_min_range = cfg["set_min_range"];
const std::string& increase_max_range = cfg["increase_max_range"];
const std::string& set_max_range = cfg["set_max_range"];
auto remove_specials = cfg.optional_child("remove_specials");
const std::string& increase_damage = cfg["increase_damage"];
const std::string& set_damage = cfg["set_damage"];
const std::string& increase_attacks = cfg["increase_attacks"];
Expand Down Expand Up @@ -415,6 +436,10 @@ bool attack_type::apply_modification(const config& cfg)
max_range_ = utils::apply_modifier(max_range_, increase_max_range);
}

if(remove_specials) {
remove_special_by_filter(*remove_specials);
}

if(set_damage.empty() == false) {
damage_ = std::stoi(set_damage);
if (damage_ < 0) {
Expand Down
11 changes: 11 additions & 0 deletions src/units/attack_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ class attack_type : public std::enable_shared_from_this<attack_type>
* uses when a defender has no weapon for a given range.
*/
bool attack_empty() const {return (id().empty() && name().empty() && type().empty() && range().empty());}
/** remove special if matche condition
* @param filter if special check with filter, it will be removed.
*/
void remove_special_by_filter(const config& filter);
/** check if special matche
* @return True if special matche with filter(if 'active' filter is true, check if special active).
* @param filter if special check with filter, return true.
*/
bool has_special_with_filter(const config & filter) const;
bool has_ability_with_filter(const config & filter) const;
bool has_special_or_ability_with_filter(const config & filter) const;

// In unit_types.cpp:

Expand Down
3 changes: 3 additions & 0 deletions wml_test_schedule
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
0 event_test_filter_ability_wml_no_match
0 event_test_filter_ability_active
0 event_test_filter_ability_active_inactive
0 event_test_filter_special_active
0 event_test_filter_special_active_inactive
0 event_test_filter_special_simple_check
0 event_test_filter_ability_with_value_by_default
0 event_test_filter_ability_no_match_by_default
0 event_test_filter_ability_apply_to_resistance
Expand Down

0 comments on commit ed3147e

Please sign in to comment.