Extend special_id_active and special_type_active to account for abilities used as specials

This commit is contained in:
newfrenchy83 2021-02-19 01:19:35 +01:00 committed by GitHub
parent f671d2a678
commit 05b2ea2262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1357 additions and 33 deletions

View file

@ -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.

View file

@ -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]

View file

@ -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]

View file

@ -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 units 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

View file

@ -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.

View file

@ -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

View file

@ -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]
)}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
);
}

View file

@ -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
{
bool abil_bool = get_special_bool(ability);
unit_ability_list abil = list_ability(ability);
if(!abil.empty()) {
abil_bool = true;
/**
* 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<special_match>& tag_result,
std::vector<special_match>& 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);
}
return abil_bool;
}
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)
{
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_match> special_tag_matches;
std::vector<special_match> 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;
}
}
}
}
}
if(other_){
std::vector<special_match> special_tag_matches;
std::vector<special_match> 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;
}

View file

@ -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;
}

View file

@ -92,8 +92,20 @@ public:
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 @@ private:
// 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,

View file

@ -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<std::string>* extra_hit_sounds)
int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector<std::string>* 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<decltype(ctx)> 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();

View file

@ -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<std::string>* extra_hit_sounds=nullptr);
int swing, const std::string& hit_text, int drain_amount, const std::string& att_text, const std::vector<std::string>* extra_hit_sounds=nullptr,
bool attacking=true);
void unit_recruited(const map_location& loc,

View file

@ -1649,6 +1649,43 @@ public:
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 @@ public:
return get_abilities_weapons(tag_name, loc_, weapon, opp_weapon);
}
const config &abilities() const { return abilities_; }
const std::set<std::string>& 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 @@ public:
private:
const std::set<std::string> 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

View file

@ -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