add a [remove_specials] tag in [effect] and [filter_special] to [has_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
This commit is contained in:
parent
ae5b2f0da6
commit
7d5663b379
8 changed files with 277 additions and 0 deletions
3
changelog_entries/add_filter_special.md
Normal file
3
changelog_entries/add_filter_special.md
Normal file
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -379,6 +379,137 @@
|
|||
[/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 (
|
||||
{FILTER_ABILITY_TEST}
|
||||
[event]
|
||||
name=start
|
||||
[object]
|
||||
silent=yes
|
||||
[effect]
|
||||
apply_to=new_ability
|
||||
[abilities]
|
||||
{ABILITY_ILLUMINATES}
|
||||
[/abilities]
|
||||
[/effect]
|
||||
[filter]
|
||||
id=alice
|
||||
[/filter]
|
||||
[/object]
|
||||
[/event]
|
||||
|
||||
[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 (
|
||||
{FILTER_ABILITY_TEST}
|
||||
[event]
|
||||
name=start
|
||||
[object]
|
||||
silent=yes
|
||||
[effect]
|
||||
apply_to=new_ability
|
||||
[abilities]
|
||||
{ABILITY_ILLUMINATES}
|
||||
[/abilities]
|
||||
[/effect]
|
||||
[filter]
|
||||
id=alice
|
||||
[/filter]
|
||||
[/object]
|
||||
[/event]
|
||||
|
||||
[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
|
||||
|
||||
##
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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"];
|
||||
|
@ -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) {
|
||||
|
|
|
@ -145,6 +145,17 @@ public:
|
|||
* 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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue