diff --git a/changelog.md b/changelog.md index 0093557979d6..5a0e43507341 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,8 @@ * Added information about the build's (not runtime) target CPU architecture to the game version info dialog and --report. * Added terminal-style command history browsing with up-down keys for in-game consoles used by debug mode, ai and search floating textboxes. ### WML Engine + * Extent 'special_id_active' and 'special_type_active' to abilities used like weapon and to [leadership] abilities. + * abilities used like weapon can call [leading_anim] now. ### Miscellaneous and Bug Fixes * Fixed display zoom not being taken into account when using the `x`, `y`, `directional_x` and `directional_y` attributes in unit animations. diff --git a/data/campaigns/Heir_To_The_Throne/units/Battle_Princess.cfg b/data/campaigns/Heir_To_The_Throne/units/Battle_Princess.cfg index 226e8aa59492..990a511efecf 100644 --- a/data/campaigns/Heir_To_The_Throne/units/Battle_Princess.cfg +++ b/data/campaigns/Heir_To_The_Throne/units/Battle_Princess.cfg @@ -7,6 +7,7 @@ num_traits=0 image="units/human-battleprincess-resting.png" {LEADING_ANIM "units/human-battleprincess-leading-1.png" "units/human-battleprincess-leading-2.png" 22,-22} + {INITIATIVE_ANIM "units/human-battleprincess-leading-1.png" "units/human-battleprincess-leading-2.png"} hitpoints=62 movement_type=smallfoot [resistance] diff --git a/data/campaigns/Heir_To_The_Throne/units/Princess.cfg b/data/campaigns/Heir_To_The_Throne/units/Princess.cfg index 23eb5c6546ba..f741162cfcb6 100644 --- a/data/campaigns/Heir_To_The_Throne/units/Princess.cfg +++ b/data/campaigns/Heir_To_The_Throne/units/Princess.cfg @@ -8,6 +8,7 @@ image="units/human-princess.png" {DEFENSE_ANIM "units/human-princess-defend-2.png" "units/human-princess-defend-1.png" {SOUND_LIST:HUMAN_FEMALE_HIT} } {LEADING_ANIM "units/human-princess-leading-2.png" "units/human-princess-leading-1.png" 22,-22} + {INITIATIVE_ANIM "units/human-princess-leading-2.png" "units/human-princess-leading-1.png"} hitpoints=48 movement_type=smallfoot [resistance] diff --git a/data/campaigns/Heir_To_The_Throne/utils/abilities.cfg b/data/campaigns/Heir_To_The_Throne/utils/abilities.cfg index 35b6debdb189..1e5745258d5f 100644 --- a/data/campaigns/Heir_To_The_Throne/utils/abilities.cfg +++ b/data/campaigns/Heir_To_The_Throne/utils/abilities.cfg @@ -15,6 +15,19 @@ [affect_adjacent] [/affect_adjacent] [/firststrike] + [firststrike] + id=initiative_anim + affect_self=no + affect_allies=yes + active_on=defense + [filter_student] + [filter_weapon] + special_id_active=initiative + [/filter_weapon] + [/filter_student] + [affect_adjacent] + [/affect_adjacent] + [/firststrike] #enddef #define NOTE_INITIATIVE @@ -22,3 +35,21 @@ note=_"This unit’s grasp of melee tactics allows adjacent allies to strike the first blow even when defending." [/special_note] #enddef + +#define INITIATIVE_ANIM FULL_IMAGE HALFWAYS_IMAGE + [leading_anim] + [filter_attack] + special_id_active=initiative_anim + [not] + special_type_active=leadership + [/not] + [not] + special_id_active=firststrike + [/not] + [/filter_attack] + start_time=-126 + [frame] + image={HALFWAYS_IMAGE}:1,{FULL_IMAGE}:250,{HALFWAYS_IMAGE}:1 + [/frame] + [/leading_anim] +#enddef diff --git a/data/core/macros/animation-utils.cfg b/data/core/macros/animation-utils.cfg index 717dde7a232f..2bb46dda0358 100644 --- a/data/core/macros/animation-utils.cfg +++ b/data/core/macros/animation-utils.cfg @@ -25,20 +25,43 @@ # Define an animation of a unit waving/raising their weapon, # with a gleam of light reflecting off it at the point specified by # OFFSET_POSITION - [leading_anim] - start_time=-126 - [frame] - image={HALFWAYS_IMAGE}:1,{FULL_IMAGE}:250,{HALFWAYS_IMAGE}:1 - [/frame] + {LEADING_ANIM_FILTER {FULL_IMAGE} {HALFWAYS_IMAGE} {OFFSET_POSITION} special_type_active=leadership} +#enddef - halo_start_time=-100 - [halo_frame] - halo="halo/misc/leadership-flare-[1~13].png:20" - halo_x,halo_y={OFFSET_POSITION} - [/halo_frame] +#define LEADING_ANIM_FILTER FULL_IMAGE HALFWAYS_IMAGE OFFSET_POSITION ATTACK + # Define an animation of a unit waving/raising their weapon, + # with a gleam of light reflecting off it at the point specified by + # OFFSET_POSITION + [leading_anim] + [filter_attack] + {ATTACK} + [/filter_attack] + {LEADING_BASE {FULL_IMAGE} {HALFWAYS_IMAGE} {OFFSET_POSITION}} [/leading_anim] #enddef +#define HELPING_ANIM FULL_IMAGE HALFWAYS_IMAGE OFFSET_POSITION + # Define an animation of a unit waving/raising their weapon, + # with a gleam of light reflecting off it at the point specified by + # OFFSET_POSITION + [resistance_anim] + {LEADING_BASE {FULL_IMAGE} {HALFWAYS_IMAGE} {OFFSET_POSITION}} + [/resistance_anim] +#enddef + +#define LEADING_BASE FULL_IMAGE HALFWAYS_IMAGE OFFSET_POSITION + start_time=-126 + [frame] + image={HALFWAYS_IMAGE}:1,{FULL_IMAGE}:250,{HALFWAYS_IMAGE}:1 + [/frame] + + halo_start_time=-100 + [halo_frame] + halo="halo/misc/leadership-flare-[1~13].png:20" + halo_x,halo_y={OFFSET_POSITION} + [/halo_frame] +#enddef + #define DEFENSE_ANIM REACTION_IMAGE BASE_IMAGE HIT_SOUND # Define a defensive animation moving from a specified BASE_IMAGE # to REACTION_IMAGE, with HIT_SOUND playing only if a hit occurs. diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities.cfg index b14198fa71be..300522140951 100644 --- a/data/test/scenarios/swarm_disables_upgrades_with_abilities.cfg +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities.cfg @@ -14,6 +14,9 @@ [/filter_student] [filter_opponent] race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] [/filter_opponent] #enddef #define FILTER_ALICE @@ -81,6 +84,7 @@ {FILTER_ALICE} [/damage] [chance_to_hit] + id=test_cth value=100 {FILTER_ALICE} [/chance_to_hit] @@ -155,3 +159,6 @@ {SUCCEED} [/event] )} + +#undef FILTER_BOB +#undef FILTER_ALICE diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent.cfg new file mode 100644 index 000000000000..ebbf5ee1637d --- /dev/null +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent.cfg @@ -0,0 +1,183 @@ +# This unit test defines a WML object based implementation of the "unupgradable" ability +# https://github.com/ProditorMagnus/Ageless-for-1-14/blob/52c1eaf31722bb58046a1b459d4f29daa8d88487/data/general_data/weapon_specials/unupgradable.cfg +# and checks that it works. What is being tested here is that +# [swarm] with swarm_attacks_max=swarm_attacks_min prevents strike changes +# - through [attacks] +# - through [effect] increase_attacks + +{GENERIC_UNIT_TEST "swarm_disables_upgrades_with_abilities_adjacent" ( +#define FILTER_BOB +[filter_student] + [filter_weapon] + type=blade + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef +#define FILTER_ALICE +[filter_student] + [filter_weapon] + type=blade,pierce + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=orc +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef + [event] + name=start + [unit] + id=alex + name=_"Alex" + x,y=12,4 + type=Elvish Hero + side=1 + [/unit] + [unit] + id=ben + name=_"Ben" + x,y=14,3 + type=Orcish Warrior + side=2 + [/unit] + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [swarm] + swarm_attacks_max=1 + swarm_attacks_min=1 + {FILTER_BOB} + [/swarm] + [attacks] + value=10 + {FILTER_BOB} + [/attacks] + [attacks] + add=13 + {FILTER_BOB} + [/attacks] + [damage] + value=1 + {FILTER_BOB} + [/damage] + [chance_to_hit] + value=100 + {FILTER_BOB} + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=ben + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [attacks] + value=10 + {FILTER_ALICE} + [/attacks] + [damage] + value=1 + {FILTER_ALICE} + [/damage] + [chance_to_hit] + id=test_cth + value=100 + {FILTER_ALICE} + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=alex + [/filter] + [/object] + [object] + silent=yes + [filter] + id=bob + [/filter] + [effect] + apply_to=attack + increase_attacks=12 + [/effect] + [/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=12,3 + [/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] + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 99})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 90})} + {SUCCEED} + [/event] +)} diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_fail.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_fail.cfg new file mode 100644 index 000000000000..0c409ab17e77 --- /dev/null +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_fail.cfg @@ -0,0 +1,181 @@ +# This unit test defines a WML object based implementation of the "unupgradable" ability +# https://github.com/ProditorMagnus/Ageless-for-1-14/blob/52c1eaf31722bb58046a1b459d4f29daa8d88487/data/general_data/weapon_specials/unupgradable.cfg +# and checks that it works. What is being tested here is that +# [swarm] with swarm_attacks_max=swarm_attacks_min prevents strike changes +# - through [attacks] +# - through [effect] increase_attacks + +{GENERIC_UNIT_TEST "swarm_disables_upgrades_with_abilities_adjacent_fail" ( +#define FILTER_BOB +[filter_student] + [filter_weapon] + type=blade + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef +#define FILTER_ALICE +[filter_student] + [filter_weapon] + type=blade,pierce + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=orc +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef + [event] + name=start + [unit] + id=alex + name=_"Alex" + x,y=12,4 + type=Elvish Hero + side=1 + [/unit] + [unit] + id=ben + name=_"Ben" + x,y=14,3 + type=Orcish Warrior + side=2 + [/unit] + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [swarm] + swarm_attacks_max=1 + swarm_attacks_min=1 + {FILTER_BOB} + [/swarm] + [attacks] + value=10 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/attacks] + [damage] + value=1 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/damage] + [chance_to_hit] + value=100 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=ben + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [attacks] + value=10 + {FILTER_ALICE} + [/attacks] + [damage] + value=1 + {FILTER_ALICE} + [/damage] + [chance_to_hit] + id=test_fail_cth + value=100 + {FILTER_ALICE} + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=alex + [/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=12,3 + [/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] + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 90})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 90})} + {SUCCEED} + [/event] +)} + +#undef FILTER_BOB +#undef FILTER_ALICE diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership.cfg new file mode 100644 index 000000000000..28b7f4a4a1f2 --- /dev/null +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership.cfg @@ -0,0 +1,220 @@ +# This unit test defines a WML object based implementation of the "unupgradable" ability +# https://github.com/ProditorMagnus/Ageless-for-1-14/blob/52c1eaf31722bb58046a1b459d4f29daa8d88487/data/general_data/weapon_specials/unupgradable.cfg +# and checks that it works. What is being tested here is that +# [swarm] with swarm_attacks_max=swarm_attacks_min prevents strike changes +# - through [attacks] +# - through [effect] increase_attacks + +{GENERIC_UNIT_TEST "swarm_disables_upgrades_with_abilities_adjacent_leadership" ( +#define FILTER_BOB +[filter_student] + [filter_weapon] + type=blade + [and] + special_id_active=leader_test_bob + [/and] + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef +#define FILTER_ALICE +[filter_student] + [filter_weapon] + type=blade,pierce + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=orc +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef + [event] + name=start + [unit] + id=alex + name=_"Alex" + x,y=12,4 + type=Elvish Hero + side=1 + [/unit] + [unit] + id=ben + name=_"Ben" + x,y=14,3 + type=Orcish Warrior + side=2 + [/unit] + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [swarm] + swarm_attacks_max=1 + swarm_attacks_min=1 + {FILTER_BOB} + [/swarm] + [attacks] + value=10 + {FILTER_BOB} + [/attacks] + [attacks] + add=13 + {FILTER_BOB} + [/attacks] + [damage] + value=1 + {FILTER_BOB} + [/damage] + [chance_to_hit] + value=100 + {FILTER_BOB} + [/chance_to_hit] + [leadership] + id=leader_test_bob + value=100 + cumulative=no + affect_self=no + [filter_weapon] + type=blade + [/filter_weapon] + [filter_second_weapon] + special_id_active=test_cth + [/filter_second_weapon] + [affect_adjacent] + [filter] + formula="level < other.level" + [/filter] + [/affect_adjacent] + [/leadership] + [/abilities] + [/effect] + [filter] + id=ben + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [attacks] + value=10 + {FILTER_ALICE} + [/attacks] + [damage] + value=1 + {FILTER_ALICE} + [/damage] + [chance_to_hit] + id=test_cth + value=100 + {FILTER_ALICE} + [/chance_to_hit] + [leadership] + id=leader_test_alice + value=100 + cumulative=no + affect_self=no + [filter_weapon] + type=blade,pierce + [/filter_weapon] + [affect_adjacent] + [filter] + formula="level < other.level" + [/filter] + [/affect_adjacent] + [/leadership] + [/abilities] + [/effect] + [filter] + id=alex + [/filter] + [/object] + [object] + silent=yes + [filter] + id=bob + [/filter] + [effect] + apply_to=attack + increase_attacks=12 + [/effect] + [/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=12,3 + [/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] + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 98})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 80})} + {SUCCEED} + [/event] +)} + +#undef FILTER_BOB +#undef FILTER_ALICE diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership_fail.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership_fail.cfg new file mode 100644 index 000000000000..a06bdabaa653 --- /dev/null +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities_adjacent_leadership_fail.cfg @@ -0,0 +1,201 @@ +# This unit test defines a WML object based implementation of the "unupgradable" ability +# https://github.com/ProditorMagnus/Ageless-for-1-14/blob/52c1eaf31722bb58046a1b459d4f29daa8d88487/data/general_data/weapon_specials/unupgradable.cfg +# and checks that it works. What is being tested here is that +# [swarm] with swarm_attacks_max=swarm_attacks_min prevents strike changes +# - through [attacks] +# - through [effect] increase_attacks + +{GENERIC_UNIT_TEST "swarm_disables_upgrades_with_abilities_adjacent_leadership_fail" ( +#define FILTER_BOB +[filter_student] + [filter_weapon] + type=blade + [and] + special_id_active=leader_test_bob + [/and] + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef +#define FILTER_ALICE +[filter_student] + [filter_weapon] + type=blade,pierce + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=orc +[/filter_opponent] +affect_self=no +affect_allies=yes +[affect_adjacent] +[/affect_adjacent] +#enddef + [event] + name=start + [unit] + id=alex + name=_"Alex" + x,y=12,4 + type=Elvish Hero + side=1 + [/unit] + [unit] + id=ben + name=_"Ben" + x,y=14,3 + type=Orcish Warrior + side=2 + [/unit] + [modify_unit] + [filter] + [/filter] + max_hitpoints=100 + hitpoints=100 + attacks_left=1 + [/modify_unit] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [swarm] + swarm_attacks_max=1 + swarm_attacks_min=1 + {FILTER_BOB} + [/swarm] + [attacks] + value=10 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/attacks] + [damage] + value=1 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/damage] + [chance_to_hit] + value=100 + affect_self=no + affect_allies=yes + [affect_adjacent] + [/affect_adjacent] + [/chance_to_hit] + [leadership] + id=leader_test_fail_bob + value=100 + cumulative=no + affect_self=no + [filter_weapon] + type=blade + [/filter_weapon] + [filter_second_weapon] + special_id_active=test_cth + [/filter_second_weapon] + [affect_adjacent] + [filter] + formula="level < other.level" + [/filter] + [/affect_adjacent] + [/leadership] + [/abilities] + [/effect] + [filter] + id=ben + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [attacks] + value=10 + {FILTER_ALICE} + [/attacks] + [damage] + value=1 + {FILTER_ALICE} + [/damage] + [chance_to_hit] + id=test_cth + value=100 + {FILTER_ALICE} + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=alex + [/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=12,3 + [/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] + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 80})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 90})} + {SUCCEED} + [/event] +)} + +#undef FILTER_BOB +#undef FILTER_ALICE diff --git a/data/test/scenarios/swarm_disables_upgrades_with_abilities_fail.cfg b/data/test/scenarios/swarm_disables_upgrades_with_abilities_fail.cfg new file mode 100644 index 000000000000..16dda4731100 --- /dev/null +++ b/data/test/scenarios/swarm_disables_upgrades_with_abilities_fail.cfg @@ -0,0 +1,147 @@ +# This unit test defines a WML object based implementation of the "unupgradable" ability +# https://github.com/ProditorMagnus/Ageless-for-1-14/blob/52c1eaf31722bb58046a1b459d4f29daa8d88487/data/general_data/weapon_specials/unupgradable.cfg +# and checks that it works. What is being tested here is that +# [swarm] with swarm_attacks_max=swarm_attacks_min prevents strike changes +# - through [attacks] +# - through [effect] increase_attacks + +{GENERIC_UNIT_TEST "swarm_disables_upgrades_with_abilities_fail" ( +#define FILTER_BOB +[filter_student] + [filter_weapon] + type=blade + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=elf + [filter_weapon] + special_id_active=test_cth #for testing if special_id_active work with abilities used like weapons + [/filter_weapon] +[/filter_opponent] +#enddef +#define FILTER_ALICE +[filter_student] + [filter_weapon] + type=blade,pierce + [/filter_weapon] +[/filter_student] +[filter_opponent] + race=orc +[/filter_opponent] +#enddef + [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] + [swarm] + swarm_attacks_max=1 + swarm_attacks_min=1 + {FILTER_BOB} + [/swarm] + [attacks] + value=10 + [/attacks] + [damage] + value=1 + [/damage] + [chance_to_hit] + value=100 + [/chance_to_hit] + [/abilities] + [/effect] + [filter] + id=bob + [/filter] + [/object] + [object] + silent=yes + [effect] + apply_to=new_ability + [abilities] + [attacks] + value=10 + {FILTER_ALICE} + [/attacks] + [damage] + value=1 + {FILTER_ALICE} + [/damage] + [chance_to_hit] + id=test_fail_cth + value=100 + {FILTER_ALICE} + [/chance_to_hit] + [/abilities] + [/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] + {ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 90})} + {ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 90})} + {SUCCEED} + [/event] +)} + +#undef FILTER_BOB +#undef FILTER_ALICE diff --git a/src/actions/attack.cpp b/src/actions/attack.cpp index ded138ea3c32..3b1d2f282d15 100644 --- a/src/actions/attack.cpp +++ b/src/actions/attack.cpp @@ -1102,7 +1102,7 @@ bool attack::perform_hit(bool attacker_turn, statistics::attack_context& stats) damage, *attacker_stats->weapon, defender_stats->weapon, abs_n, float_text.str(), drains_damage, "", - &extra_hit_sounds + &extra_hit_sounds, attacker_turn ); } diff --git a/src/units/abilities.cpp b/src/units/abilities.cpp index f922351d8f74..28f7e15d5b9a 100644 --- a/src/units/abilities.cpp +++ b/src/units/abilities.cpp @@ -1171,14 +1171,197 @@ unit_ability_list attack_type::get_special_ability(const std::string& ability) c return abil_list; } -bool attack_type::bool_ability(const std::string& ability) const + /** + * Gets the children of parent (which should be the abilities for an + * attack_type) and places the ones whose tag or id= matches @a id into + * @a tag_result and @a id_result. + * @param tag_result receive the children whose tag matches @a id + * @param id_result receive the children whose id matches @a id + * @param parent the tags whose contain children (abilities here) + * @param id tag or id of child tested + * @param special_id if true, children check by id + * @param special_tags if true, children check by tags + */ +static void get_ability_children(std::vector& tag_result, + std::vector& id_result, + const config& parent, const std::string& id, + bool special_id=true, bool special_tags=true) { + if(special_id && special_tags){ + get_special_children(tag_result, id_result, parent, id); + } else if(special_id && !special_tags){ + get_special_children_id(id_result, parent, id); + } else if(!special_id && special_tags){ + get_special_children_tags(tag_result, parent, id); + } +} + +bool unit::get_self_ability_bool(const config& special, const std::string& tag_name, const map_location& loc) const +{ + return (ability_active(tag_name, special, loc) && ability_affects_self(tag_name, special, loc)); +} + +bool unit::get_adj_ability_bool(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from) const +{ + const auto adjacent = get_adjacent_tiles(loc); + return (affects_side(special, side(), from.side()) && from.ability_active(tag_name, special, adjacent[dir]) && ability_affects_adjacent(tag_name, special, dir, loc, from)); +} + +bool unit::get_self_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const_attack_ptr weapon, const_attack_ptr opp_weapon) const +{ + return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true)); +} + +bool unit::get_adj_ability_bool_weapon(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from, const_attack_ptr weapon, const_attack_ptr opp_weapon) const +{ + return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true)); +} + +bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const +{ + return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true); +} + +bool attack_type::check_self_abilities_impl(const_attack_ptr self_attack, const_attack_ptr other_attack, const config& special, unit_const_ptr u, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool) { - bool abil_bool = get_special_bool(ability); - unit_ability_list abil = list_ability(ability); - if(!abil.empty()) { - abil_bool = true; + if(tag_name == "leadership" && leader_bool){ + if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) { + return true; + } + } + 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, true, "filter_student")) { + return true; + } + } + return false; +} + +bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const +{ + return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true); +} + +bool attack_type::check_adj_abilities_impl(const_attack_ptr self_attack, const_attack_ptr other_attack, const config& special, unit_const_ptr u, const unit& from, int dir, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool) +{ + if(tag_name == "leadership" && leader_bool){ + if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) { + return true; + } + } + 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, true, "filter_student")) { + return true; + } + } + return false; +} +/** + * Returns whether or not @a *this has a special ability with a tag or id equal to + * @a special. the Check is for a special ability + * active in the current context (see set_specials_context), including + * specials obtained from the opponent's attack. + */ +bool attack_type::get_special_ability_bool(const std::string& special, bool special_id, bool special_tags) const +{ + assert(display::get_singleton()); + const unit_map& units = display::get_singleton()->get_units(); + if(self_){ + std::vector special_tag_matches; + std::vector special_id_matches; + get_ability_children(special_tag_matches, special_id_matches, (*self_).abilities(), special, special_id , special_tags); + if(special_tags){ + for(const special_match& entry : special_tag_matches) { + if(check_self_abilities(*entry.cfg, entry.tag_name)){ + return true; + } + } + } + if(special_id){ + for(const special_match& entry : special_id_matches) { + if(check_self_abilities(*entry.cfg, entry.tag_name)){ + return true; + } + } + } + + 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; + + get_ability_children(special_tag_matches, special_id_matches, it->abilities(), special, special_id , special_tags); + if(special_tags){ + for(const special_match& entry : special_tag_matches) { + if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){ + return true; + } + } + } + if(special_id){ + for(const special_match& entry : special_id_matches) { + if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){ + return true; + } + } + } + } } - return abil_bool; + + if(other_){ + std::vector special_tag_matches; + std::vector special_id_matches; + get_ability_children(special_tag_matches, special_id_matches, (*other_).abilities(), special, special_id , special_tags); + if(special_tags){ + for(const special_match& entry : special_tag_matches) { + if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){ + return true; + } + } + } + + if(special_id){ + for(const special_match& entry : special_id_matches) { + if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){ + 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; + + get_ability_children(special_tag_matches, special_id_matches, it->abilities(), special, special_id , special_tags); + if(special_tags){ + for(const special_match& entry : special_tag_matches) { + if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){ + return true; + } + } + } + + if(special_id){ + for(const special_match& entry : special_id_matches) { + if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){ + return true; + } + } + } + } + } + return false; +} + +bool attack_type::bool_ability(const std::string& special, bool special_id, bool special_tags) const +{ + return (get_special_bool(special, false, special_id, special_tags) || get_special_ability_bool(special, special_id, special_tags)); } //end of emulate weapon special functions. @@ -1273,7 +1456,7 @@ bool attack_type::special_active_impl(const_attack_ptr self_attack, const_attack return false; } if (tag_name == "firststrike" && !is_attacker && other_attack && - other_attack->get_special_bool("firststrike", false)) { + other_attack->bool_ability("firststrike")) { return false; } diff --git a/src/units/attack_type.cpp b/src/units/attack_type.cpp index 660647a33241..33877aef8c86 100644 --- a/src/units/attack_type.cpp +++ b/src/units/attack_type.cpp @@ -180,7 +180,7 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if(!filter_special_id_active.empty()) { bool found = false; for(auto& special : filter_special_id_active) { - if(attack.get_special_bool(special, false, true, false)) { + if(attack.bool_ability(special, true, false)) { found = true; break; } @@ -204,7 +204,7 @@ static bool matches_simple_filter(const attack_type & attack, const config & fil if(!filter_special_type_active.empty()) { bool found = false; for(auto& special : filter_special_type_active) { - if(attack.get_special_bool(special, false, false)) { + if(attack.bool_ability(special, false)) { found = true; break; } diff --git a/src/units/attack_type.hpp b/src/units/attack_type.hpp index 57523df60f2e..a7310a5525ce 100644 --- a/src/units/attack_type.hpp +++ b/src/units/attack_type.hpp @@ -92,8 +92,20 @@ class attack_type : public std::enable_shared_from_this unit_ability_list list_ability(const std::string& ability) const; /** Returns list who contains list_ability and get_specials list for each ability type */ unit_ability_list get_special_ability(const std::string& ability) const; - /** return an boolean value for abilities like poison slow firstrike or petrifies */ - bool bool_ability(const std::string& ability) const; + /** used for abilities used like weapon + * @return True if the ability @a special is active. + * @param special The special being checked. + * @param special_id If true, match @a special against the @c id of special tags. + * @param special_tags If true, match @a special against the tag name of special tags. + */ + bool get_special_ability_bool(const std::string& special, bool special_id=true, bool special_tags=true) const; + /** used for abilities used like weapon and true specials + * @return True if the ability @a special is active. + * @param special The special being checked. + * @param special_id If true, match @a special against the @c id of special tags. + * @param special_tags If true, match @a special against the tag name of special tags. + */ + bool bool_ability(const std::string& special, bool special_id=true, bool special_tags=true) const; // In unit_types.cpp: @@ -113,9 +125,72 @@ class attack_type : public std::enable_shared_from_this // Configured as a bit field, in case that is useful. enum AFFECTS { AFFECT_SELF=1, AFFECT_OTHER=2, AFFECT_EITHER=3 }; + /** 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. + * @param special The special ability type who is being checked. + */ + bool check_self_abilities(const config& cfg, const std::string& special) const; + /** check_adj_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. + * @param special The special ability type who is being checked. + * @param dir direction to research a unit adjacent to self_. + * @param from unit adjacent to self_ is checked. + */ + 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, bool include_backstab=true, const std::string& filter_self ="filter_self") const; + /** check_self_abilities_impl : return an boolean value for checking of activities of abilities used like weapon + * @return True if the special @a tag_name is active. + * @param self_attack the attack used by unit checked in this function. + * @param other_attack the attack used by opponent to unit checked. + * @param special the config to one special ability checked. + * @param u the unit checked. + * @param loc location of the unit checked. + * @param whom determine if unit affected or not by special ability. + * @param tag_name The special ability type who is being checked. + * @param leader_bool If true, [leadership] abilities are checked. + */ + static bool check_self_abilities_impl( + const_attack_ptr self_attack, + const_attack_ptr other_attack, + const config& special, + unit_const_ptr u, + const map_location& loc, + AFFECTS whom, + const std::string& tag_name, + bool leader_bool=false + ); + + + /** check_adj_abilities_impl : return an boolean value for checking of activities of abilities used like weapon in unit adjacent to fighter + * @return True if the special @a tag_name is active. + * @param self_attack the attack used by unit who fight. + * @param other_attack the attack used by opponent. + * @param special the config to one special ability checked. + * @param u the unit who is or not affected by an abilities owned by @a from. + * @param from unit adjacent to @a u is checked. + * @param dir direction to research a unit adjacent to @a u. + * @param loc location of the unit checked. + * @param whom determine if unit affected or not by special ability. + * @param tag_name The special ability type who is being checked. + * @param leader_bool If true, [leadership] abilities are checked. + */ + static bool check_adj_abilities_impl( + const_attack_ptr self_attack, + const_attack_ptr other_attack, + const config& special, + unit_const_ptr u, + const unit& from, + int dir, + const map_location& loc, + AFFECTS whom, + const std::string& tag_name, + bool leader_bool=false + ); + static bool special_active_impl( const_attack_ptr self_attack, const_attack_ptr other_attack, diff --git a/src/units/udisplay.cpp b/src/units/udisplay.cpp index 0afbf31ce663..489b0ef5d912 100644 --- a/src/units/udisplay.cpp +++ b/src/units/udisplay.cpp @@ -599,7 +599,8 @@ void unit_die(const map_location& loc, unit& loser, void unit_attack(display * disp, game_board & board, const map_location& a, const map_location& b, int damage, const attack_type& attack, const_attack_ptr secondary_attack, - int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector* extra_hit_sounds) + int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector* extra_hit_sounds, + bool attacking) { if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !preferences::show_combat()) { return; @@ -620,6 +621,13 @@ void unit_attack(display * disp, game_board & board, assert(def.valid()); unit &defender = *def; int def_hitpoints = defender.hitpoints(); + const_attack_ptr weapon = attack.shared_from_this(); + auto ctx = weapon->specials_context(attacker.shared_from_this(), defender.shared_from_this(), a, b, attacking, secondary_attack); + std::optional opp_ctx; + + if(secondary_attack) { + opp_ctx.emplace(secondary_attack->specials_context(defender.shared_from_this(), attacker.shared_from_this(), b, a, !attacking, weapon)); + } att->set_facing(a.get_relative_dir(b)); def->set_facing(b.get_relative_dir(a)); @@ -640,16 +648,20 @@ void unit_attack(display * disp, game_board & board, unit_animator animator; animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2, - (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, attack.shared_from_this(), + (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon, secondary_attack, swing); // note that we take an anim from the real unit, we'll use it later const unit_animation* defender_anim = def->anim_comp().choose_animation(*disp, def->get_location(), "defend", - att->get_location(), damage, hit_type, attack.shared_from_this(), secondary_attack, swing); + att->get_location(), damage, hit_type, weapon, secondary_attack, swing); animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0}); - for(const unit_ability& ability : attacker.get_abilities_weapons("leadership", attack.shared_from_this(), secondary_attack)) { + unit_ability_list abilities = attacker.get_abilities_weapons("leadership", weapon, secondary_attack); + for(auto& special : attacker.checking_tags()) { + abilities.append(weapon->list_ability(special)); + } + for(const unit_ability& ability : abilities) { if(ability.teacher_loc == a) { continue; } @@ -663,10 +675,10 @@ void unit_attack(display * disp, game_board & board, leader->set_facing(ability.teacher_loc.get_relative_dir(a)); animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc, att->get_location(), damage, true, "", {0,0,0}, - hit_type, attack.shared_from_this(), secondary_attack, swing); + hit_type, weapon, secondary_attack, swing); } - for(const unit_ability& ability : defender.get_abilities_weapons("resistance", secondary_attack, attack.shared_from_this())) { + for(const unit_ability& ability : defender.get_abilities_weapons("resistance", secondary_attack, weapon)) { if(ability.teacher_loc == a) { continue; } @@ -680,7 +692,7 @@ void unit_attack(display * disp, game_board & board, helper->set_facing(ability.teacher_loc.get_relative_dir(b)); animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc, def->get_location(), damage, true, "", {0,0,0}, - hit_type, attack.shared_from_this(), secondary_attack, swing); + hit_type, weapon, secondary_attack, swing); } @@ -717,7 +729,11 @@ void reset_helpers(const unit *attacker,const unit *defender) display* disp = display::get_singleton(); const unit_map& units = disp->get_units(); if(attacker) { - for(const unit_ability& ability : attacker->get_abilities("leadership")) { + unit_ability_list attacker_abilities = attacker->get_abilities("leadership"); + for(auto& special : attacker->checking_tags()) { + attacker_abilities.append(attacker->get_abilities(special)); + } + for(const unit_ability& ability : attacker_abilities) { unit_map::const_iterator leader = units.find(ability.teacher_loc); assert(leader != units.end()); leader->anim_comp().set_standing(); @@ -725,7 +741,11 @@ void reset_helpers(const unit *attacker,const unit *defender) } if(defender) { - for(const unit_ability& ability : defender->get_abilities("resistance")) { + unit_ability_list defender_abilities = defender->get_abilities("resistance"); + for(auto& special : defender->checking_tags()) { + defender_abilities.append(defender->get_abilities(special)); + } + for(const unit_ability& ability : defender_abilities) { unit_map::const_iterator helper = units.find(ability.teacher_loc); assert(helper != units.end()); helper->anim_comp().set_standing(); diff --git a/src/units/udisplay.hpp b/src/units/udisplay.hpp index a4b2e27050fa..3ddb80f41190 100644 --- a/src/units/udisplay.hpp +++ b/src/units/udisplay.hpp @@ -119,7 +119,8 @@ void unit_sheath_weapon( const map_location& loc, unit_ptr u=unit_ptr(), const_a void unit_attack(display * disp, game_board & board, //TODO: Would be nice if this could be purely a display function and defer damage dealing to its caller const map_location& a, const map_location& b, int damage, const attack_type& attack, const_attack_ptr secondary_attack, - int swing, const std::string& hit_text, int drain_amount, const std::string& att_text, const std::vector* extra_hit_sounds=nullptr); + int swing, const std::string& hit_text, int drain_amount, const std::string& att_text, const std::vector* extra_hit_sounds=nullptr, + bool attacking=true); void unit_recruited(const map_location& loc, diff --git a/src/units/unit.hpp b/src/units/unit.hpp index a6dfae3ad120..922d55a8ff48 100644 --- a/src/units/unit.hpp +++ b/src/units/unit.hpp @@ -1649,6 +1649,43 @@ class unit : public std::enable_shared_from_this return get_ability_bool(tag_name, loc_); } + /** Checks whether this unit currently possesses a given ability used like weapon + * @return True if the ability @a tag_name is active. + * @param special the const config to one of abilities @a tag_name checked. + * @param tag_name name of ability type checked. + * @param loc location of the unit checked. + */ + bool get_self_ability_bool(const config& special, const std::string& tag_name, const map_location& loc) const; + /** Checks whether this unit currently possesses a given ability of leadership type + * @return True if the ability @a tag_name is active. + * @param special the const config to one of abilities @a tag_name checked. + * @param tag_name name of ability type checked. + * @param loc location of the unit checked. + * @param weapon the attack used by unit checked in this function. + * @param opp_weapon the attack used by opponent to unit checked. + */ + bool get_self_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const_attack_ptr weapon = nullptr, const_attack_ptr opp_weapon = nullptr) const; + /** Checks whether this unit is affected by a given ability used like weapon + * @return True if the ability @a tag_name is active. + * @param special the const config to one of abilities @a tag_name checked. + * @param tag_name name of ability type checked. + * @param loc location of the unit checked. + * @param from unit adjacent to @a this is checked in case of [affect_adjacent] abilities. + * @param dir direction to research a unit adjacent to @a this. + */ + bool get_adj_ability_bool(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from) const; + /** Checks whether this unit is affected by a given ability of leadership type + * @return True if the ability @a tag_name is active. + * @param special the const config to one of abilities @a tag_name checked. + * @param tag_name name of ability type checked. + * @param loc location of the unit checked. + * @param from unit adjacent to @a this is checked in case of [affect_adjacent] abilities. + * @param dir direction to research a unit adjacent to @a this. + * @param weapon the attack used by unit checked in this function. + * @param opp_weapon the attack used by opponent to unit checked. + */ + bool get_adj_ability_bool_weapon(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon = nullptr) const; + /** * Gets the unit's active abilities of a particular type if it were on a specified location. * @param tag_name The type of ability to check for @@ -1674,6 +1711,10 @@ class unit : public std::enable_shared_from_this return get_abilities_weapons(tag_name, loc_, weapon, opp_weapon); } + const config &abilities() const { return abilities_; } + + const std::set& checking_tags() const { return checking_tags_; }; + /** * Gets the names and descriptions of this unit's abilities. Location-independent variant * with all abilities shown as active. @@ -1723,6 +1764,8 @@ class unit : public std::enable_shared_from_this private: + + const std::set checking_tags_{"damage", "chance_to_hit", "berserk", "swarm", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"}; /** * Check if an ability is active. * @param ability The type (tag name) of the ability diff --git a/wml_test_schedule b/wml_test_schedule index 6a382b752805..2a9ffa63287a 100644 --- a/wml_test_schedule +++ b/wml_test_schedule @@ -169,6 +169,11 @@ 0 feeding 0 swarm_disables_upgrades 0 swarm_disables_upgrades_with_abilities +0 swarm_disables_upgrades_with_abilities_fail +0 swarm_disables_upgrades_with_abilities_adjacent +0 swarm_disables_upgrades_with_abilities_adjacent_fail +0 swarm_disables_upgrades_with_abilities_adjacent_leadership +0 swarm_disables_upgrades_with_abilities_adjacent_leadership_fail 0 test_force_chance_to_hit_macro # # Deterministic unit facing tests