Fix “filter_adjacent_location” not working in ability (#9079)

This commit is contained in:
newfrenchy83 2025-02-22 21:04:09 +01:00 committed by GitHub
parent fd9d9fa35e
commit cee370683d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 615 additions and 47 deletions

View File

@ -5,6 +5,8 @@
max=infinite
super="units/unit_type/abilities/~generic~,units/unit_type/attack/specials/" + {NAME}
{FILTER_TAG "filter_student" unit {FILTER_TAG "filter_weapon" weapon ()}}
{FILTER_TAG "filter_adjacent_student" adjacent ()}
{FILTER_TAG "filter_adjacent_student_location" adjacent_location ()}
[/tag]
#enddef

View File

@ -0,0 +1,49 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if one adjacent unit is bob.
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_active" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is bob"
value=100
apply_to=self
[filter_adjacent]
adjacent=n,ne,se,s,sw,nw
count=1-6
id=bob
[/filter_adjacent]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[have_unit]
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,48 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if one adjacent unit is bob, and bob is in correct location.
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_direction_active" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is bob, and bob is in correct location"
value=100
apply_to=self
[filter_adjacent]
adjacent=se,s,sw
id=bob
[/filter_adjacent]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[have_unit]
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,50 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if one adjacent unit is bob, and bob is in correct location.
# Test whether the ability is active.
##
# Expected end state:
# specialX shouldn't be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_direction_inactive" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is bob, and bob is in correct location"
value=100
apply_to=self
[filter_adjacent]
adjacent=n,ne,nw
id=bob
[/filter_adjacent]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[not]
[have_unit]
ability_id_active=specialX
[/have_unit]
[/not]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,51 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if one adjacent unit is Loki.
# Test whether the ability is active.
##
# Expected end state:
# specialX shouldn't be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_inactive" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is Loki"
value=100
apply_to=self
[filter_adjacent]
adjacent=n,ne,se,s,sw,nw
count=1-6
id=Loki
[/filter_adjacent]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[not]
[have_unit]
ability_id_active=specialX
[/have_unit]
[/not]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,49 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent_location]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if exactly three of adjacents terrains are Gg.
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_location_count_three_active" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if 3 terrains hexes are Gg"
value=100
apply_to=self
[filter_adjacent_location]
adjacent=n,ne,se,s,sw,nw
count=3
terrain=Gg
[/filter_adjacent_location]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[have_unit]
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,49 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent_location]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if zero of the adjacent terrains are Ww.
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_location_count_zero_active" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if 0 terrains hexes are Ww"
value=100
apply_to=self
[filter_adjacent_location]
adjacent=n,ne,se,s,sw,nw
count=0
terrain=Ww
[/filter_adjacent_location]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[have_unit]
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,49 @@
#####
# API(s) being tested: ability[filter_adjacent_location]adjacent,count=
##
# Actions:
# Give Alice an ability specialX, which is only active if zero of the adjacent terrains are Gg.
# Test whether specialX ability is active.
##
# Expected end state:
# specialX isn't active.
#####
{COMMON_KEEP_A_B_C_D_UNIT_TEST "filter_adjacent_location_count_zero_inactive" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if 0 terrains hexes are Gg"
value=100
apply_to=self
[filter_adjacent_location]
adjacent=n,ne,se,s,sw,nw
count=0
terrain=Gg
[/filter_adjacent_location]
[/damage]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]
[/object]
{ASSERT (
[not]
[have_unit]
ability_id_active=specialX
[/have_unit]
[/not]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,105 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent_student]
##
# Actions:
# place a unit alex adjacent to alice and only alice.
# Give alex an ability specialX who affect alice, which is only active in fight if one adjacent unit is bob.
# alice attack bob.
##
# Expected end state:
# attack event trigered if specialX active because alice is adjacent to bob.
#####
{GENERIC_UNIT_TEST "filter_adjacent_student_active" (
[event]
name=start
[unit]
id=alex
name=_"Alex"
x,y=12,4
type=Elvish Hero
side=1
[/unit]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is bob"
value=1
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_adjacent]
[filter]
id=alice
[/filter]
[/affect_adjacent]
[filter_adjacent_student]
id=bob
[/filter_adjacent_student]
[/damage]
[/abilities]
[/effect]
[filter]
id=alex
[/filter]
[/object]
{VARIABLE triggers_on_attack 0}
[/event]
[event]
name=side 1 turn 1
[do_command]
[move]
x=7,13
y=3,4
[/move]
[attack]
[source]
x,y=13,4
[/source]
[destination]
x,y=13,3
[/destination]
[/attack]
[/do_command]
[end_turn][/end_turn]
[/event]
[event]
name=side 2 turn
[do_command]
[attack]
[source]
x,y=13,3
[/source]
[destination]
x,y=13,4
[/destination]
[/attack]
[/do_command]
[end_turn][/end_turn]
[/event]
# Event when Alice attacks
[event]
name=attack
first_time_only=no
[filter_attack]
special_id_active=specialX
[/filter_attack]
{ASSERT ({VARIABLE_CONDITIONAL side_number equals 1})}
{VARIABLE_OP triggers_on_attack add 1}
[/event]
[event]
name=turn 2
{ASSERT ({VARIABLE_CONDITIONAL triggers_on_attack equals 1})}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,113 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[filter_adjacent_student]
##
# Actions:
# place a unit alex adjacent to alice and only alice.
# place loki adjacent to alex and only alex
# Give alex an ability specialX who affect alice, which is only active in fight if one adjacent unit is loki.
# alice attack bob.
##
# Expected end state:
# attack event no trigered if specialX active because alice is no adjacent to loki, [filter_adjacent_student] filter the units adjacent to student, and not owner of ability.
#####
{GENERIC_UNIT_TEST "filter_adjacent_student_inactive" (
[event]
name=start
[unit]
id=alex
name=_"Alex"
x,y=12,4
type=Elvish Hero
side=1
[/unit]
[unit]
id=loki
name=_"Loki"
x,y=11,4
type=Elvish Hero
side=1
[/unit]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one of the adjacents units is bob"
value=1
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_adjacent]
[filter]
id=alice
[/filter]
[/affect_adjacent]
[filter_adjacent_student]
id=loki
[/filter_adjacent_student]
[/damage]
[/abilities]
[/effect]
[filter]
id=alex
[/filter]
[/object]
{VARIABLE triggers_on_attack 0}
[/event]
[event]
name=side 1 turn 1
[do_command]
[move]
x=7,13
y=3,4
[/move]
[attack]
[source]
x,y=13,4
[/source]
[destination]
x,y=13,3
[/destination]
[/attack]
[/do_command]
[end_turn][/end_turn]
[/event]
[event]
name=side 2 turn
[do_command]
[attack]
[source]
x,y=13,3
[/source]
[destination]
x,y=13,4
[/destination]
[/attack]
[/do_command]
[end_turn][/end_turn]
[/event]
# Event when Alice attacks
[event]
name=attack
first_time_only=no
[filter_attack]
special_id_active=specialX
[/filter_attack]
{ASSERT ({VARIABLE_CONDITIONAL side_number equals 1})}
{VARIABLE_OP triggers_on_attack add 1}
[/event]
[event]
name=turn 2
{ASSERT ({VARIABLE_CONDITIONAL triggers_on_attack equals 0})}
{SUCCEED}
[/event]
)}

View File

@ -446,18 +446,16 @@ bool unit::ability_active_impl(const std::string& ability,const config& cfg,cons
std::size_t count = 0;
unit_filter ufilt{ vconfig(i) };
ufilt.set_use_flat_tod(illuminates);
std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
for (const map_location::direction index : dirs)
{
if (index == map_location::direction::indeterminate)
continue;
unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
if (unit == units.end())
return false;
continue;
if (!ufilt(*unit, *this))
return false;
continue;
if((*this).id() == (*unit).id())
return false;
continue;
if (i.has_attribute("is_enemy")) {
const display_context& dc = resources::filter_con->get_disp_context();
if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
@ -466,10 +464,9 @@ bool unit::ability_active_impl(const std::string& ability,const config& cfg,cons
}
count++;
}
if (i["count"].empty() && count != dirs.size()) {
return false;
}
if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
config::attribute_value i_count =i["count"];
if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
return false;
}
}
@ -480,21 +477,17 @@ bool unit::ability_active_impl(const std::string& ability,const config& cfg,cons
terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
adj_filter.flatten(illuminates);
std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
for (const map_location::direction index : dirs)
{
if (index == map_location::direction::indeterminate) {
continue;
}
if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
return false;
continue;
}
count++;
}
if (i["count"].empty() && count != dirs.size()) {
return false;
}
if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
config::attribute_value i_count =i["count"];
if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
return false;
}
}
@ -1554,13 +1547,13 @@ unit_ability_list attack_type::get_weapon_ability(const std::string& ability) co
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, true);
});
}
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, true);
});
}
@ -1757,7 +1750,7 @@ bool attack_type::check_self_abilities_impl(const const_attack_ptr& self_attack,
}
}
if((*u).checking_tags().count(tag_name) != 0){
if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, true)) {
return true;
}
}
@ -1777,7 +1770,7 @@ bool attack_type::check_adj_abilities_impl(const const_attack_ptr& self_attack,
}
}
if((*u).checking_tags().count(tag_name) != 0){
if((*u).get_adj_ability_bool(special, tag_name, dir, loc, from) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
if((*u).get_adj_ability_bool(special, tag_name, dir, loc, from) && special_active_impl(self_attack, other_attack, special, whom, tag_name, true)) {
return true;
}
}
@ -2197,9 +2190,9 @@ bool attack_type::has_special_or_ability_with_filter(const config & filter) cons
}
bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
const std::string& filter_self) const
bool in_abilities_tag) 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, in_abilities_tag);
}
/**
@ -2210,7 +2203,7 @@ 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 in_abilities_tag if special coded in [specials] or [abilities] tags
*/
bool attack_type::special_active_impl(
const const_attack_ptr& self_attack,
@ -2218,7 +2211,7 @@ bool attack_type::special_active_impl(
const config& special,
AFFECTS whom,
const std::string& tag_name,
const std::string& filter_self)
bool in_abilities_tag)
{
assert(self_attack || other_attack);
bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
@ -2320,6 +2313,7 @@ bool attack_type::special_active_impl(
//the function of this special in matches_filter()
//In apply_to=both case, tag_name must be checked in all filter because special applied to both self and opponent.
bool applied_both = special["apply_to"] == "both";
const std::string& filter_self = in_abilities_tag ? "filter_student" : "filter_self";
std::string self_check_if_recursion = (applied_both || whom_is_self) ? tag_name : "";
if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_check_if_recursion))
return false;
@ -2337,21 +2331,25 @@ bool attack_type::special_active_impl(
if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_check_if_recursion))
return false;
//if filter_self != "filter_self" then it's in [abilities] tags and
//[filter_student_adjacent] and [filter_student_adjacent] then designate 'the student' (which may be different from the owner of the ability),
//while in the tags[specials] the usual names are kept.
const std::string& filter_adjacent = in_abilities_tag ? "filter_adjacent_student" : "filter_adjacent";
const std::string& filter_adjacent_location = in_abilities_tag ? "filter_adjacent_student_location" : "filter_adjacent_location";
const auto adjacent = get_adjacent_tiles(self_loc);
// Filter the adjacent units.
for (const config &i : special.child_range("filter_adjacent"))
for (const config &i : special.child_range(filter_adjacent))
{
std::size_t count = 0;
std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
unit_filter filter{ vconfig(i) };
for (const map_location::direction index : dirs)
{
if (index == map_location::direction::indeterminate)
continue;
unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
if (unit == units.end() || !filter.matches(*unit, adjacent[static_cast<int>(index)], *self))
return false;
continue;
if (i.has_attribute("is_enemy")) {
const display_context& dc = resources::filter_con->get_disp_context();
if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
@ -2360,33 +2358,29 @@ bool attack_type::special_active_impl(
}
count++;
}
if (i["count"].empty() && count != dirs.size()) {
return false;
}
if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
config::attribute_value i_count =i["count"];
if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
return false;
}
}
// Filter the adjacent locations.
for (const config &i : special.child_range("filter_adjacent_location"))
for (const config &i : special.child_range(filter_adjacent_location))
{
std::size_t count = 0;
std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
for (const map_location::direction index : dirs)
{
if (index == map_location::direction::indeterminate)
continue;
if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
return false;
continue;
}
count++;
}
if (i["count"].empty() && count != dirs.size()) {
return false;
}
if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
config::attribute_value i_count =i["count"];
if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
return false;
}
}

View File

@ -272,7 +272,7 @@ private:
*/
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;
bool in_abilities_tag = 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
@ -364,7 +364,7 @@ private:
const config& special,
AFFECTS whom,
const std::string& tag_name,
const std::string& filter_self ="filter_self"
bool in_abilities_tag = false
);
// Used via specials_context() to control which specials are

View File

@ -411,6 +411,15 @@
0 taught_resistance_with_three_attack_types
0 swarms_filter_student_by_type
0 swarms_effects_not_checkable
0 filter_adjacent_location_count_three_active
0 filter_adjacent_location_count_zero_active
0 filter_adjacent_location_count_zero_inactive
0 filter_adjacent_active
0 filter_adjacent_inactive
0 filter_adjacent_student_active
0 filter_adjacent_student_inactive
0 filter_adjacent_direction_active
0 filter_adjacent_direction_inactive
0 filter_special_id_active
0 filter_ability_special_id_active
0 filter_special_id_not_exists