From 5ecaf34c4a863b8bc31174d5240d762fe762e5cd Mon Sep 17 00:00:00 2001 From: newfrenchy83 Date: Wed, 7 Sep 2022 15:52:53 +0200 Subject: [PATCH] add a 'type' and 'added_type' attribute in [damage] special or ability who modify the type of attack used At the suggestion of @stevecotton, I propose a special 'type' and 'added_type' attribute capable of modifying the type of attack used when the conditions are met. --- .../[damage]_special_changelog.md | 2 + data/schema/filters/weapon.cfg | 9 +- data/schema/units/abilities.cfg | 2 +- data/schema/units/specials.cfg | 10 +- .../AbilitiesWML/special_damage_type.cfg | 287 ++++++++++++++++++ src/gui/dialogs/attack_predictions.cpp | 7 +- src/gui/dialogs/unit_attack.cpp | 23 ++ src/movetype.cpp | 10 +- src/reports.cpp | 16 +- src/units/abilities.cpp | 55 +++- src/units/attack_type.cpp | 35 ++- src/units/attack_type.hpp | 6 +- src/units/unit.cpp | 2 +- wml_test_schedule | 2 + 14 files changed, 444 insertions(+), 22 deletions(-) create mode 100644 changelog_entries/[damage]_special_changelog.md create mode 100644 data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_damage_type.cfg diff --git a/changelog_entries/[damage]_special_changelog.md b/changelog_entries/[damage]_special_changelog.md new file mode 100644 index 0000000000000..6423b43bc9416 --- /dev/null +++ b/changelog_entries/[damage]_special_changelog.md @@ -0,0 +1,2 @@ + ### WML Engine + * Add a 'type' and 'added_type' attribute to [damage] to change the type of attack under specific conditions (terrain, time of day, leadership etc...) \ No newline at end of file diff --git a/data/schema/filters/weapon.cfg b/data/schema/filters/weapon.cfg index 5084885a061a3..da60c1d744fad 100644 --- a/data/schema/filters/weapon.cfg +++ b/data/schema/filters/weapon.cfg @@ -1,6 +1,6 @@ [tag] - name="$filter_weapon" + name="$filter_weapon_common" max=0 {SIMPLE_KEY range string_list} {SIMPLE_KEY name string_list} @@ -21,3 +21,10 @@ {FILTER_BOOLEAN_OPS weapon} [/tag] +[tag] + name="$filter_weapon" + max=0 + super="$filter_weapon_common" + {SIMPLE_KEY added_type string_list} + {SIMPLE_KEY modified_damage s_unsigned_range_list} +[/tag] diff --git a/data/schema/units/abilities.cfg b/data/schema/units/abilities.cfg index 3b95eee8de45b..ef29c0fa6c264 100644 --- a/data/schema/units/abilities.cfg +++ b/data/schema/units/abilities.cfg @@ -4,7 +4,7 @@ name={NAME} 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_student" unit {FILTER_TAG "filter_weapon" weapon_common ()}} {DEFAULT_KEY overwrite_specials ability_overwrite none} [/tag] #enddef diff --git a/data/schema/units/specials.cfg b/data/schema/units/specials.cfg index b8493cb47596c..2d5d8168a4828 100644 --- a/data/schema/units/specials.cfg +++ b/data/schema/units/specials.cfg @@ -14,10 +14,10 @@ {FILTER_TAG "filter_adjacent" adjacent ()} {FILTER_TAG "filter_adjacent_location" adjacent_location ()} - {FILTER_TAG "filter_self" unit {FILTER_TAG "filter_weapon" weapon ()}} - {FILTER_TAG "filter_opponent" unit {FILTER_TAG "filter_weapon" weapon ()}} - {FILTER_TAG "filter_attacker" unit {FILTER_TAG "filter_weapon" weapon ()}} - {FILTER_TAG "filter_defender" unit {FILTER_TAG "filter_weapon" weapon ()}} + {FILTER_TAG "filter_self" unit {FILTER_TAG "filter_weapon" weapon_common ()}} + {FILTER_TAG "filter_opponent" unit {FILTER_TAG "filter_weapon" weapon_common ()}} + {FILTER_TAG "filter_attacker" unit {FILTER_TAG "filter_weapon" weapon_common ()}} + {FILTER_TAG "filter_defender" unit {FILTER_TAG "filter_weapon" weapon_common ()}} {WML_MERGE_KEYS} [/tag] # A few specials inheriting from ~generic~ are included here so that unit abilities can then inherit from them. @@ -69,6 +69,8 @@ name="damage" max=infinite super="units/unit_type/attack/specials/~value~" + {SIMPLE_KEY type string} + {SIMPLE_KEY added_type string} [/tag] [tag] name="drains" diff --git a/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_damage_type.cfg b/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_damage_type.cfg new file mode 100644 index 0000000000000..9b93f912a140a --- /dev/null +++ b/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_damage_type.cfg @@ -0,0 +1,287 @@ + +{GENERIC_UNIT_TEST "damage_type_test" ( + [event] + name=start + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=resistance + replace=yes + [resistance] + arcane=50 + fire=200 + [/resistance] + [/effect] + [effect] + apply_to=attack + [set_specials] + mode=append + [attacks] + value=1 + [/attacks] + [damage] + value=12 + [/damage] + [damage] + type=fire + [/damage] + [damage] + type=cold + [/damage] + [damage] + type=cold + [/damage] + [chance_to_hit] + value=100 + [/chance_to_hit] + [/set_specials] + [/effect] + [filter] + id=bob + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=resistance + replace=yes + [resistance] + cold=200 + fire=50 + [/resistance] + [/effect] + [effect] + apply_to=attack + [set_specials] + mode=append + [attacks] + value=1 + [/attacks] + [damage] + value=12 + [/damage] + [damage] + type=cold + [/damage] + [damage] + type=arcane + [/damage] + [damage] + type=arcane + [/damage] + [damage] + type=fire + [/damage] + [damage] + type=fire + [/damage] + [damage] + type=fire + [/damage] + [chance_to_hit] + value=100 + [/chance_to_hit] + [/set_specials] + [/effect] + [filter] + id=alice + [/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] + [store_unit] + [filter] + id=bob + [/filter] + variable=b + [/store_unit] + #damage without modification are 12, if test fail hitpoints !=76 + #if succed then damage by alice 24(bob vulnerable to fire and fire prioritized) + #if succed then damage by bob 24(alice vulnerable to cold, cold priority is 1) + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 76})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 76})} + {SUCCEED} + [/event] +)} + +{GENERIC_UNIT_TEST "damage_secondary_type_test" ( + [event] + name=start + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=resistance + replace=yes + [resistance] + blade=200 + fire=100 + [/resistance] + [/effect] + [effect] + apply_to=attack + [set_specials] + mode=append + [attacks] + value=1 + [/attacks] + [damage] + value=12 + [/damage] + [damage] + added_type=cold + [/damage] + [chance_to_hit] + value=100 + [/chance_to_hit] + [/set_specials] + [/effect] + [filter] + id=bob + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=resistance + replace=yes + [resistance] + cold=200 + blade=100 + [/resistance] + [/effect] + [effect] + apply_to=attack + [set_specials] + mode=append + [attacks] + value=1 + [/attacks] + [damage] + value=12 + [/damage] + [damage] + added_type=fire + [/damage] + [chance_to_hit] + value=100 + [/chance_to_hit] + [/set_specials] + [/effect] + [filter] + id=alice + [/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] + [store_unit] + [filter] + id=bob + [/filter] + variable=b + [/store_unit] + #damage without modification are 12, if test fail hitpoints !=76 + #if succed then damage by alice 24(bob more vulnerable to blade) + #if succed then damage by bob 24(alice vulnerable to cold, cold [damage] is used) + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 76})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 76})} + {SUCCEED} + [/event] +)} diff --git a/src/gui/dialogs/attack_predictions.cpp b/src/gui/dialogs/attack_predictions.cpp index 4cce7cb568563..1eec8db2f718c 100644 --- a/src/gui/dialogs/attack_predictions.cpp +++ b/src/gui/dialogs/attack_predictions.cpp @@ -203,7 +203,12 @@ void attack_predictions::set_data(window& window, const combatant_data& attacker } } - ss << string_table["type_" + weapon->type()]; + std::pair types = weapon->damage_type(); + std::string type_bis = types.second; + if (!type_bis.empty()) { + type_bis = ", " + string_table["type_" + type_bis]; + } + ss << string_table["type_" + types.first] + type_bis; set_label_helper("resis_label", ss.str()); diff --git a/src/gui/dialogs/unit_attack.cpp b/src/gui/dialogs/unit_attack.cpp index 7663c68eaa8fa..d14dd527594e8 100644 --- a/src/gui/dialogs/unit_attack.cpp +++ b/src/gui/dialogs/unit_attack.cpp @@ -115,6 +115,25 @@ void unit_attack::pre_show(window& window) attacker_itor_->get_location(), false, attacker.weapon ); + std::pair types = attacker_weapon.damage_type(); + std::string attw_type_bis = types.second; + std::string attw_type = !(types.first).empty() ? types.first : attacker_weapon.type(); + if (!attw_type.empty()) { + attw_type = string_table["type_" + attw_type]; + } + if (!attw_type_bis.empty()) { + attw_type_bis = ", " + string_table["type_" + attw_type_bis]; + } + std::pair def_types = defender_weapon.damage_type(); + std::string defw_type_bis = def_types.second; + std::string defw_type = !(def_types.first).empty() ? def_types.first : defender_weapon.type(); + if (!defw_type.empty()) { + defw_type = string_table["type_" + defw_type]; + } + if (!defw_type_bis.empty()) { + defw_type_bis = ", " + string_table["type_" + defw_type_bis]; + } + const std::set checking_tags_other = {"disable", "berserk", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"}; std::string attw_specials = attacker_weapon.weapon_specials(); std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"}); @@ -163,22 +182,26 @@ void unit_attack::pre_show(window& window) // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does not consider the swarm weapon special attacker_stats << "" << attw_name << "" << "\n" + << attw_type << attw_type_bis << "\n" << attacker.damage << font::weapon_numbers_sep << attacker.num_blows << attw_specials << "\n" << font::span_color(a_cth_color) << attacker.chance_to_hit << "%"; attacker_tooltip << _("Weapon: ") << "" << attw_name << "" << "\n" + << _("Type: ") << attw_type << attw_type_bis << "\n" << _("Damage: ") << attacker.damage << "" << attw_specials_dmg << "" << "\n" << _("Attacks: ") << attacker.num_blows << "" << attw_specials_atk << "" << "\n" << _("Chance to hit: ") << font::span_color(a_cth_color) << attacker.chance_to_hit << "%"<< "" << attw_specials_cth << "" << attw_specials_others; defender_stats << "" << defw_name << "" << "\n" + << defw_type << defw_type_bis << "\n" << defender.damage << font::weapon_numbers_sep << defender.num_blows << defw_specials << "\n" << font::span_color(d_cth_color) << defender.chance_to_hit << "%"; defender_tooltip << _("Weapon: ") << "" << defw_name << "" << "\n" + << _("Type: ") << defw_type << defw_type_bis << "\n" << _("Damage: ") << defender.damage << "" << defw_specials_dmg << "" << "\n" << _("Attacks: ") << defender.num_blows << "" << defw_specials_atk << "" << "\n" << _("Chance to hit: ") << font::span_color(d_cth_color) << defender.chance_to_hit << "%"<< "" << defw_specials_cth << "" diff --git a/src/movetype.cpp b/src/movetype.cpp index 54f7d85bb720c..2c80f83957d0d 100644 --- a/src/movetype.cpp +++ b/src/movetype.cpp @@ -743,7 +743,15 @@ utils::string_map_res movetype::resistances::damage_table() const */ int movetype::resistances::resistance_against(const attack_type & attack) const { - return cfg_[attack.type()].to_int(100); + std::pair types = attack.damage_type(); + int res = cfg_[types.first].to_int(100); + if(!(types.second).empty()){ + int res_bis = cfg_[types.second].to_int(100); + if(res_bis > res){ + res = res_bis; + } + } + return res; } diff --git a/src/reports.cpp b/src/reports.cpp index 31426d66ee87d..b494e9a2049a2 100644 --- a/src/reports.cpp +++ b/src/reports.cpp @@ -876,15 +876,22 @@ static int attack_info(const reports::context& rc, const attack_type &at, config const string_with_tooltip damage_and_num_attacks {flush(str), flush(tooltip)}; std::string range = string_table["range_" + at.range()]; - std::string lang_type = string_table["type_" + at.type()]; + std::pair types = at.damage_type(); + std::string secondary_lang_type = types.second; + if (!secondary_lang_type.empty()) { + secondary_lang_type = ", " + string_table["type_" + secondary_lang_type]; + } + std::string lang_type = string_table["type_" + types.first] + secondary_lang_type; // SCALE_INTO() is needed in case the 72x72 images/misc/missing-image.png is substituted. const std::string range_png = std::string("icons/profiles/") + at.range() + "_attack.png~SCALE_INTO(16,16)"; - const std::string type_png = std::string("icons/profiles/") + at.type() + ".png~SCALE_INTO(16,16)"; + const std::string type_png = std::string("icons/profiles/") + types.first + ".png~SCALE_INTO(16,16)"; + const std::string secondary_type_png = !(types.second).empty() ? std::string("icons/profiles/") + types.second + ".png~SCALE_INTO(16,16)" : ""; const bool range_png_exists = image::locator(range_png).file_exists(); const bool type_png_exists = image::locator(type_png).file_exists(); + const bool secondary_type_png_exists = image::locator(secondary_type_png).file_exists(); - if(!range_png_exists || !type_png_exists) { + if(!range_png_exists || !type_png_exists || (!secondary_type_png_exists && !secondary_lang_type.empty())) { str << span_color(font::weapon_details_color) << " " << " " << range << font::weapon_details_sep << lang_type << "\n"; @@ -941,6 +948,9 @@ static int attack_info(const reports::context& rc, const attack_type &at, config const std::string spacer = "misc/blank.png~CROP(0, 0, 16, 21)"; // 21 == 16+5 add_image(res, spacer + "~BLIT(" + range_png + ",0,5)", damage_versus.tooltip); add_image(res, spacer + "~BLIT(" + type_png + ",0,5)", damage_versus.tooltip); + if(secondary_type_png_exists){ + add_image(res, spacer + "~BLIT(" + secondary_type_png + ",0,5)", damage_versus.tooltip); + } add_text(res, damage_and_num_attacks.str, damage_and_num_attacks.tooltip); add_text(res, damage_versus.str, damage_versus.tooltip); // This string is usually empty diff --git a/src/units/abilities.cpp b/src/units/abilities.cpp index 6e2a9f4e35acc..c7c7db8f2ecf0 100644 --- a/src/units/abilities.cpp +++ b/src/units/abilities.cpp @@ -1168,6 +1168,59 @@ void attack_type::modified_attacks(unsigned & min_attacks, } } +// These static functions and damage_type() are used to define a type of attack different +// from the native type via two attributes in [damage], +// 'type' replaces the native type when the [damage] which carries it is activated, +// while 'added_type ' only makes the substitution +// when it is to the advantage of the user (adversary more vulnerable to the type carried by 'added_type' than by the default type). +static void add_to_vector(std::vector& type_list, std::vector& added_type_list, const config& cfg, bool& is_active) +{ + if(cfg["type"] && is_active) { + type_list.push_back(cfg["type"].str()); + } + if(cfg["added_type"] && is_active) { + added_type_list.push_back(cfg["added_type"].str()); + } +} + +static std::vector vector_type(unit_ability_list& abil_list, const std::string& type) +{ + std::vector type_list; + for(auto& i : abil_list) { + if(!(*i.ability_cfg)[type].str().empty()){ + type_list.push_back((*i.ability_cfg)[type].str()); + } + } + if(type_list.size() >= 2){ + std::sort(type_list.begin(), type_list.end()); + if(type_list.size() >= 3){ + std::unordered_map type_count; + for( const std::string& character : type_list ){ + type_count[character]++; + } + std::sort( std::begin( type_list ) , std::end( type_list ) , [&]( const std::string& rhs , const std::string& lhs ){ + return type_count[lhs] < type_count[rhs]; + }); + } + } + return type_list; +} + +std::pair attack_type::damage_type() const +{ + unit_ability_list abil_list = get_specials_and_abilities("damage"); + if(abil_list.empty()){ + return {type(), ""}; + } + + std::vector type_list = vector_type(abil_list, "type", types); + std::vector added_type_list = vector_type(abil_list, "added_type", types); + std::string type_damage, sec_type_damage; + type_damage = !type_list.empty() ? type_list.front() : type(); + sec_type_damage = !added_type_list.empty() ? added_type_list.front() : ""; + return {type_damage, sec_type_damage}; +} + /** * Returns the damage per attack of this weapon, considering specials. @@ -1270,7 +1323,7 @@ namespace { // Helpers for attack_type::special_active() // Check for a weapon match. if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) { - if ( !weapon || !weapon->matches_filter(*filter_weapon) ) + if ( !weapon || !weapon->matches_filter(*filter_weapon, true) ) return false; } diff --git a/src/units/attack_type.cpp b/src/units/attack_type.cpp index d19f037192733..3293710b74aef 100644 --- a/src/units/attack_type.cpp +++ b/src/units/attack_type.cpp @@ -101,7 +101,7 @@ std::string attack_type::accuracy_parry_description() const * Returns whether or not *this matches the given @a filter, ignoring the * complexities introduced by [and], [or], and [not]. */ -static bool matches_simple_filter(const attack_type & attack, const config & filter) +static bool matches_simple_filter(const attack_type & attack, const config & filter, bool can_loop) { const std::vector& filter_range = utils::split(filter["range"]); const std::string& filter_damage = filter["damage"]; @@ -144,8 +144,27 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if ( !filter_name.empty() && std::find(filter_name.begin(), filter_name.end(), attack.id()) == filter_name.end() ) return false; - if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), attack.type()) == filter_type.end() ) - return false; + if(!filter_type.empty()){ + const std::string& damage_type = can_loop ? attack.type() : attack.damage_type().first; + if ( std::find(filter_type.begin(), filter_type.end(), damage_type) == filter_type.end() ){ + return false; + } + } + + if(!can_loop){ + const std::string& filter_modified_damage = filter["modified_damage"]; + const std::vector filter_added_type = utils::split(filter["added_type"]); + if(!filter_added_type.empty()){ + if ( std::find(filter_added_type.begin(), filter_added_type.end(), attack.damage_type().second) == filter_added_type.end() ){ + return false; + } + } + if(!filter_modified_damage.empty()){ + if ( !in_ranges(attack.modified_damage(), utils::parse_ranges_unsigned(filter_modified_damage)) ){ + return false; + } + } + } if(!filter_special.empty()) { deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead"); @@ -245,25 +264,25 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil /** * Returns whether or not *this matches the given @a filter. */ -bool attack_type::matches_filter(const config& filter) const +bool attack_type::matches_filter(const config& filter, bool can_loop) const { // Handle the basic filter. - bool matches = matches_simple_filter(*this, filter); + bool matches = matches_simple_filter(*this, filter, can_loop); // Handle [and], [or], and [not] with in-order precedence for (const config::any_child condition : filter.all_children_range() ) { // Handle [and] if ( condition.key == "and" ) - matches = matches && matches_filter(condition.cfg); + matches = matches && matches_filter(condition.cfg, can_loop); // Handle [or] else if ( condition.key == "or" ) - matches = matches || matches_filter(condition.cfg); + matches = matches || matches_filter(condition.cfg, can_loop); // Handle [not] else if ( condition.key == "not" ) - matches = matches && !matches_filter(condition.cfg); + matches = matches && !matches_filter(condition.cfg, can_loop); } return matches; diff --git a/src/units/attack_type.hpp b/src/units/attack_type.hpp index b431578c3626a..6af2a82e57e7f 100644 --- a/src/units/attack_type.hpp +++ b/src/units/attack_type.hpp @@ -88,6 +88,10 @@ class attack_type : public std::enable_shared_from_this /** Calculates the number of attacks this weapon has, considering specials. */ void modified_attacks(unsigned & min_attacks, unsigned & max_attacks) const; + + /** return a modified damage type and/or add a secondary_type for hybrid use if special is active. */ + std::pair damage_type() const; + /** Returns the damage per attack of this weapon, considering specials. */ int modified_damage() const; @@ -117,7 +121,7 @@ class attack_type : public std::enable_shared_from_this // In unit_types.cpp: - bool matches_filter(const config& filter) const; + bool matches_filter(const config& filter, bool can_loop = false) const; bool apply_modification(const config& cfg); bool describe_modification(const config& cfg,std::string* description); diff --git a/src/units/unit.cpp b/src/units/unit.cpp index 8bfbab55916c0..c87da297e1308 100644 --- a/src/units/unit.cpp +++ b/src/units/unit.cpp @@ -1785,7 +1785,7 @@ bool unit::resistance_filter_matches(const config& cfg, bool attacker, const std int unit::resistance_against(const std::string& damage_name,bool attacker,const map_location& loc, const_attack_ptr weapon, const_attack_ptr opp_weapon) const { - int res = movement_type_.resistance_against(damage_name); + int res = opp_weapon ? movement_type_.resistance_against(*opp_weapon) : movement_type_.resistance_against(damage_name); unit_ability_list resistance_abilities = get_abilities_weapons("resistance",loc, weapon, opp_weapon); utils::erase_if(resistance_abilities, [&](const unit_ability& i) { diff --git a/wml_test_schedule b/wml_test_schedule index d9befed6c9ecd..95f543893ec54 100644 --- a/wml_test_schedule +++ b/wml_test_schedule @@ -331,6 +331,8 @@ 0 trait_exclusion_test 0 trait_requirement_test 0 test_remove_ability_by_filter +0 damage_type_test +0 damage_secondary_type_test 0 swarms_filter_student_by_type 0 swarms_effects_not_checkable 0 filter_special_id_active