mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-16 12:14:34 +00:00
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:
parent
3f73a70dab
commit
b5805eca8f
|
@ -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.
|
|
@ -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]
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
)}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user