Add priority and filter to overwrite specials (#7746)

using overwrite_specials alone means that we have only two possibilities, either one_side is chosen and in this case if the ability used as a weapon carrying the attribute is applied to the unit (apply_to=self), the other abilities are the same type applied also to 'self' not carrying the attribute will be overwritten, but those of the opponent with apply_to=opponent will be kept in the list; or else both_sides is chosen and all abilities of the same type that do not carry the attribute will be overwritten. To be able to use the attribute in abilities like [damage] for example, it is necessary to be able to be even more selective as for a 'charge' type leadership with multiply=2.5 but which must not be combined with the classic charge and without overwriting the aute [damage] as backstab, [overwrite_filter] to only match damage with apply_to=both and active_on=offense is then interesting.

adding priority allows you to select that it ability can use its overwrite_specials attribute while the others can be overwritten in the same way as an ability without the attribute. Finally, this system makes it unnecessary to limit the use of the attribute to abilities used as weapons but also to special weapons.
This commit is contained in:
newfrenchy83 2023-10-08 17:09:31 +02:00 committed by GitHub
parent 3f73a70dab
commit b5805eca8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 267 additions and 43 deletions

View File

@ -0,0 +1,2 @@
### WML Engine
* Add [overwrite] tags who contain 'priority' attribute for define priority between two abilities with overwrite_specials, and [overwrite][filter_specials] to filter [abilities/specials] which may or may not be overwritten by the overwrite_specials attribute.

View File

@ -552,6 +552,9 @@
cumulative=no
affect_self=yes
overwrite_specials=both_sides
[overwrite]
priority=1000
[/overwrite]
[filter_opponent]
{SECOND_FILTER}
[/filter_opponent]
@ -605,6 +608,9 @@
cumulative=no
affect_self=yes
overwrite_specials=both_sides
[overwrite]
priority=1000
[/overwrite]
[filter_opponent]
{SECOND_FILTER}
[/filter_opponent]

View File

@ -4,9 +4,9 @@
max=0
{SIMPLE_KEY id string_list}
{SIMPLE_KEY tag_name string_list}
{SIMPLE_KEY overwrite_specials string_list}
{SIMPLE_KEY overwrite_specials ability_overwrite}
{SIMPLE_KEY apply_to string_list}
{SIMPLE_KEY active_on string_list}
{SIMPLE_KEY active_on ability_context}
{SIMPLE_KEY value s_int_range_list}
{SIMPLE_KEY add s_int_range_list}
{SIMPLE_KEY sub s_int_range_list}

View File

@ -5,7 +5,6 @@
max=infinite
super="units/unit_type/abilities/~generic~,units/unit_type/attack/specials/" + {NAME}
{FILTER_TAG "filter_student" unit {FILTER_TAG "filter_weapon" weapon ()}}
{DEFAULT_KEY overwrite_specials ability_overwrite none}
[/tag]
#enddef

View File

@ -54,6 +54,12 @@
{SIMPLE_KEY cumulative bool}
{DEPRECATED_KEY backstab bool}
{FILTER_TAG "filter_base_value" base_value ()}
{DEFAULT_KEY overwrite_specials ability_overwrite none}
[tag]
name="overwrite"
{FILTER_TAG "filter_specials" abilities ()}
{SIMPLE_KEY priority real}
[/tag]
[/tag]
[tag]
name="attacks"
@ -92,6 +98,12 @@
max=infinite
super="units/unit_type/attack/specials/~generic~"
{SIMPLE_KEY type string}
{DEFAULT_KEY overwrite_specials ability_overwrite none}
[tag]
name="overwrite"
{FILTER_TAG "filter_specials" abilities ()}
{SIMPLE_KEY priority real}
[/tag]
[/tag]
[tag]
name="swarm"

View File

@ -42,6 +42,15 @@
[/chance_to_hit]
[/set_specials]
[/effect]
[effect]#test if macro work when ability with overwrite_special is used
apply_to=new_ability
[abilities]
[chance_to_hit]
value=100
overwrite_specials=one_side
[/chance_to_hit]
[/abilities]
[/effect]
[filter]
id=bob
[/filter]
@ -64,6 +73,15 @@
[/chance_to_hit]
[/set_specials]
[/effect]
[effect]#test if macro work when ability with overwrite_special is used
apply_to=new_ability
[abilities]
[chance_to_hit]
value=100
overwrite_specials=one_side
[/chance_to_hit]
[/abilities]
[/effect]
[filter]
id=alice
[/filter]

View File

@ -0,0 +1,106 @@
#####
# API(s) being tested: [damage]with overwrite_specials attibute and [overwrite][filter_specials]
##
# Actions:
# Three [damage] abilities are added, one with value=5, other with add=11 and the last with multiply=2.
# The one with multiply=2 have overwrite_specials=both_sides and [overwrite][filter_specials] for overwrite [damage] with add=11 but not value=5.
# Have alice attack bob.
##
# Expected end state:
# Alice has 100 - (5*2)=90 hitpoints because add=11 overwrited but not value=5 who replace 9 pt of damage of bob.
#####
{GENERIC_UNIT_TEST "test_overwrite_specials_filter" (
[event]
name=start
[modify_unit]
[filter]
[/filter]
max_hitpoints=100
hitpoints=100
attacks_left=1
[/modify_unit]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=test_dmg
value=5
[/damage]
[attacks]
id=test_att
value=1
[/attacks]
[damage]
id=test_overwrite
add=11
[/damage]
[damage]
id=test_overwrite_specials
multiply=2
overwrite_specials=both_sides
[overwrite]
[filter_specials]
[not]
type_value=value
[/not]
[/filter_specials]
[/overwrite]
[/damage]
[chance_to_hit]
value=100
[/chance_to_hit]
[/abilities]
[/effect]
[filter]
id=bob
[/filter]
[/object]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
kill=yes
[/store_unit]
[store_unit]
[filter]
id=bob
[/filter]
variable=b
[/store_unit]
[unstore_unit]
variable=a
find_vacant=yes
x,y=$b.x,$b.y
[/unstore_unit]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]
[do_command]
[attack]
weapon=0
defender_weapon=0
[source]
x,y=$a.x,$a.y
[/source]
[destination]
x,y=$b.x,$b.y
[/destination]
[/attack]
[/do_command]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]
{ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 90})}
{SUCCEED}
[/event]
)}

View File

@ -1312,17 +1312,17 @@ unit_ability_list attack_type::get_weapon_ability(const std::string& ability) co
unit_ability_list attack_type::get_specials_and_abilities(const std::string& special) const
{
unit_ability_list abil_list = get_weapon_ability(special);
if(!abil_list.empty()){
abil_list = overwrite_special_checking(abil_list, abil_list, false);
}
unit_ability_list spe_list = get_specials(special);
if(!spe_list.empty()){
spe_list = overwrite_special_checking(spe_list, abil_list, true);
if(special == "plague" && !spe_list.empty()){
return spe_list;
}
abil_list.append(spe_list);
// get all weapon specials of the provided type
unit_ability_list abil_list = get_specials(special);
// append all such weapon specials as abilities as well
abil_list.append(get_weapon_ability(special));
// 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()){
// remove all abilities that would be overwritten
utils::erase_if(abil_list, [&](const unit_ability& j) {
return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
});
}
return abil_list;
}
@ -1338,36 +1338,94 @@ static bool overwrite_special_affects(const config& special)
return (apply_to == "one_side" || apply_to == "both_sides");
}
unit_ability_list attack_type::overwrite_special_checking(unit_ability_list input, unit_ability_list overwriters, bool is_special) const
unit_ability_list attack_type::overwrite_special_overwriter(unit_ability_list overwriters, const std::string& tag_name) const
{
for(unit_ability_list::iterator i = overwriters.begin(); i != overwriters.end();) {
if(!overwrite_special_affects(*i->ability_cfg)) {
i = overwriters.erase(i);
} else {
++i;
}
}
//remove element without overwrite_specials key, if list empty after check return empty list.
utils::erase_if(overwriters, [&](const unit_ability& i) {
return (!overwrite_special_affects(*i.ability_cfg));
});
// if empty, nothing is doing any overwriting
if(overwriters.empty()){
return input;
return overwriters;
}
for(const auto& i : overwriters) {
bool affect_side = ((*i.ability_cfg)["overwrite_specials"] == "one_side");
utils::erase_if(input, [&](const unit_ability& j) {
bool is_overwritable = (is_special || !overwrite_special_affects(*j.ability_cfg));
bool one_side_overwritable = true;
if(affect_side && is_overwritable){
if(special_affects_self(*i.ability_cfg, is_attacker_)){
one_side_overwritable = special_affects_self(*j.ability_cfg, is_attacker_);
}
else if(special_affects_opponent(*i.ability_cfg, !is_attacker_)){
one_side_overwritable = special_affects_opponent(*j.ability_cfg, !is_attacker_);
}
// if there are specials/"specials as abilities" that could potentially overwrite each other
if(overwriters.size() >= 2){
// sort them by overwrite priority from highest to lowest (default priority is 0)
utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
auto oi = (*i.ability_cfg).optional_child("overwrite");
double l = 0;
if(oi && !oi["priority"].empty()){
l = oi["priority"].to_double(0);
}
return (is_overwritable && one_side_overwritable);
auto oj = (*j.ability_cfg).optional_child("overwrite");
double r = 0;
if(oj && !oj["priority"].empty()){
r = oj["priority"].to_double(0);
}
return l > r;
});
// remove any that need to be overwritten
utils::erase_if(overwriters, [&](const unit_ability& i) {
return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
});
}
return input;
return overwriters;
}
bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
{
if(overwriters.empty()){
return false;
}
for(const auto& j : overwriters) {
// whether the overwriter affects a single side
bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
// the overwriter's priority, default of 0
auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
// the cfg being checked for whether it will be overwritten
auto has_overwrite_specials = cfg.optional_child("overwrite");
// if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
// else true
bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
// true if the cfg being checked affects one or both sides and doesn't have a higher priority, or if it doesn't affect one or both sides
// aka whether the cfg being checked can potentially be overwritten by the current overwriter
bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
bool one_side_overwritable = true;
// if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
// then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
if(affect_side && is_overwritable){
if(special_affects_self(*j.ability_cfg, is_attacker_)){
one_side_overwritable = special_affects_self(cfg, is_attacker_);
}
else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
}
}
// check whether the current overwriter is disabled due to a filter
bool special_matches = true;
if(overwrite_specials){
auto overwrite_filter = (*overwrite_specials).optional_child("filter_specials");
if(overwrite_filter && is_overwritable && one_side_overwritable){
if(self_){
special_matches = (*self_).ability_matches_filter(cfg, tag_name, *overwrite_filter);
}
}
}
// if the cfg being checked should be overwritten
// and either this unit or its opponent are affected
// and the current overwriter is not disabled due to a filter
if(is_overwritable && one_side_overwritable && special_matches){
return true;
}
}
return false;
}
/**

View File

@ -98,7 +98,10 @@ public:
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;
/** Returns list who contains get_weapon_ability and get_specials list for each ability type */
/**
* @param special the tag name to check for
* @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;
/** used for abilities used like weapon
* @return True if the ability @a special is active.
@ -136,14 +139,22 @@ private:
// Configured as a bit field, in case that is useful.
enum AFFECTS { AFFECT_SELF=1, AFFECT_OTHER=2, AFFECT_EITHER=3 };
/**
* Filter a list of abilities or weapon specials, removing any entries that are overridden by
* the overwrite_specials attributes of a second list.
* Filter a list of abilities or weapon specials, removing any entries that don't own
* the overwrite_specials attributes.
*
* @param input list to check, a filtered copy of this list is returned by the function.
* @param overwriters list that may have overwrite_specials attributes.
* @param is_special if true, input contains weapon specials; if false, it contains abilities.
* @param tag_name type of abilitie/special checked.
*/
unit_ability_list overwrite_special_checking(unit_ability_list input, unit_ability_list overwriters, bool is_special) const;
unit_ability_list overwrite_special_overwriter(unit_ability_list overwriters, const std::string& tag_name) const;
/**
* Check whether @a cfg would be overwritten by any element of @a overwriters.
*
* @return True if element checked is overwritable.
* @param overwriters list used for check if element is overwritable.
* @param cfg element checked.
* @param tag_name type of abilitie/special checked.
*/
bool overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const;
/** check_self_abilities : return an boolean value for checking of activities of abilities used like weapon
* @return True if the special @a special is active.
* @param cfg the config to one special ability checked.

View File

@ -94,6 +94,7 @@ public:
const unit_ability& front() const { return cfgs_.front(); }
unit_ability& back() { return cfgs_.back(); }
const unit_ability& back() const { return cfgs_.back(); }
std::size_t size() { return cfgs_.size(); }
iterator erase(const iterator& erase_it) { return cfgs_.erase(erase_it); }
iterator erase(const iterator& first, const iterator& last) { return cfgs_.erase(first, last); }

View File

@ -106,4 +106,14 @@ void erase_if(Container& container, const Predicate& predicate)
container.erase(std::remove_if(container.begin(), container.end(), predicate), container.end());
}
/**
* Convenience wrapper for using std::sort on a container.
*
*/
template<typename Container, typename Predicate>
void sort_if(Container& container, const Predicate& predicate)
{
std::sort(container.begin(), container.end(), predicate);
}
} // namespace utils

View File

@ -331,6 +331,7 @@
0 trait_exclusion_test
0 trait_requirement_test
0 test_remove_ability_by_filter
0 test_overwrite_specials_filter
0 swarms_filter_student_by_type
0 swarms_effects_not_checkable
0 filter_special_id_active