Merge b0ebcce7621d3505a0dbd0ee742b555271826627 into 2d472aaa8f0905fc754e8fe0cda2171d302faf65

This commit is contained in:
newfrenchy83 2025-03-25 21:47:56 +00:00 committed by GitHub
commit a68c0a8bca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 980 additions and 128 deletions

View File

@ -0,0 +1,2 @@
### WML Engine
* add a [affect_distant]radius= tag for affect units distant to owner of ability.

View File

@ -128,109 +128,21 @@
#enddef
#define ABILITY_SHADOW_VEIL
[dummy]
[hides]
id=did_shadow_veil
name= _ "shadow veil"
name_inactive= _ "shadow veil"
description= _ "Allied undead units within a 5 hex radius are hidden."
description_inactive= _ "Allied undead units within a 5 hex radius are hidden."
{DID_SHADOW_VEIL_HANDLER}
[/dummy]
#enddef
#define ABILITY_SHADOW_VEIL_HELPER SIDE
[hides]
id=did_shadow_veil_helper
[filter]
[filter_location]
radius=5
[filter]
ability=did_shadow_veil
[filter_side]
[allied_with]
side={SIDE}
[/allied_with]
[/filter_side]
[/filter]
[/filter_location]
[/filter]
[/hides]
#enddef
#define DID_SHADOW_VEIL_HANDLER
[event]
id=did_shadow_veil_unit_placed
name=unit placed
first_time_only=no
[filter]
side=1
race=undead
[not]
ability=did_shadow_veil_helper
[/not]
[/filter]
[filter_condition]
[have_unit]
ability=did_shadow_veil
side=1
[/have_unit]
[/filter_condition]
[modify_unit]
[filter]
x,y=$x1,$y1
[not]
ability=did_shadow_veil_helper
[or]
ability=did_shadow_veil
[/or]
[/not]
[/filter]
[object]
duration=scenario
[effect]
apply_to=new_ability
[abilities]
{ABILITY_SHADOW_VEIL_HELPER $unit.side}
[/abilities]
[/effect]
[/object]
[/modify_unit]
[/event]
[event]
id=did_shadow_veil_malkeshar
name=post advance
first_time_only=yes
[filter]
type=Mal Keshar
ability=did_shadow_veil
[/filter]
[modify_unit]
affect_self=no
affect_allies=yes
[affect_distant]
radius=5
[filter]
race=undead
[not]
ability=did_shadow_veil_helper
[or]
ability=did_shadow_veil
[/or]
[/not]
[filter_side]
[allied_with]
side=$unit.side
[/allied_with]
[/filter_side]
[/filter]
[object]
take_only_once=no
duration=scenario
[effect]
apply_to=new_ability
[abilities]
{ABILITY_SHADOW_VEIL_HELPER $this_unit.side}
[/abilities]
[/effect]
[/object]
[/modify_unit]
[/event]
[/affect_distant]
[/hides]
#enddef
#define WEAPON_SPECIAL_FROST_NOVA

View File

@ -40,6 +40,11 @@
[/key]
{FILTER_TAG "filter" unit ()}
[/tag]
[tag]
name="affect_distant"
{REQUIRED_KEY radius s_int}
{FILTER_TAG "filter" unit ()}
[/tag]
{FILTER_TAG "filter_self" unit ()}
{FILTER_TAG "filter_adjacent" adjacent ()}
{FILTER_TAG "filter_adjacent_location" adjacent_location ()}

View File

@ -0,0 +1,74 @@
#textdomain wesnoth-test
##
# Starting state:
#
# Side 1 leader Alice (Orcish Grunt)
# Side 2 leader Bob (Orcish Grunt)
# Side 3 leader Charlie (Orcish Grunt)
# Side 4 leader Dave (Orcish Grunt)
#
# None of the sides are allied.
#
# On the default map (used unless overridden with the MAP_FILE argument:
# * All four leaders are on a single keep, with Alice and Bob already in position to attack any of the other units.
# * There is no free castle hex to recruit onto.
##
#define SEPARATE_KEEP_A_B_C_D_UNIT_TEST NAME CONTENT
#arg SIDE_LEADER
Orcish Grunt#endarg
#arg MAP_FILE
test/maps/4p_separate_castles.map#endarg
[test]
name=_ "Unit Test " + {NAME}
map_file={MAP_FILE}
turns=unlimited
id={NAME}
random_start_time=no
is_unit_test=yes
{DAWN}
[side]
side=1
controller=human
[leader]
name = _ "Alice"
type = {SIDE_LEADER}
id=alice
[/leader]
[/side]
[side]
side=2
controller=human
[leader]
name = _ "Bob"
type = {SIDE_LEADER}
id=bob
[/leader]
[/side]
[side]
side=3
controller=human
[leader]
name = _ "Charlie"
type = {SIDE_LEADER}
id=charlie
[/leader]
[/side]
[side]
side=4
controller=human
[leader]
name = _ "Dave"
type = {SIDE_LEADER}
id=dave
[/leader]
[/side]
{CONTENT}
[/test]
#enddef

View File

@ -0,0 +1,54 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give charlie an ability specialX, which is affect alice if she in 10 hexes of distance of alex.
# Test whether the ability is active.
##
# Expected end state:
# specialX should affect alice.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_active" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one unit within 10 hex radius is alice"
value=100
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_distant]
radius=10
[filter]
id=alice
[/filter]
[/affect_distant]
[/damage]
[/abilities]
[/effect]
[filter]
id=charlie
[/filter]
[/object]
{ASSERT (
[have_unit]
id=alice
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,121 @@
#textdomain wesnoth-test
#define TEST_AFFECT_DISTANT_ACTIVE_WITH_SECOND_ABILITY FILTER
[event]
name=start
[unit]
id=alex
name=_"Alex"
x,y=1,1
type=Elvish Hero
side=1
[/unit]
[unit]
id=bobby
name=_"Bobby"
x,y=1,5
type=Elvish Hero
side=1
[/unit]
[unit]
id=carlos
name=_"Carlos"
x,y=1,12
type=Elvish Hero
side=1
[/unit]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[leadership]
id=specialY
name=_ "specialY"
description=_ "specialY affect units within radius of 7"
value=25
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_distant]
radius=7
[/affect_distant]
[/leadership]
[/abilities]
[/effect]
[filter]
id=carlos
[/filter]
[/object]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if within radius of specialY ability"
value=100
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[filter]
{FILTER}
[/filter]
[affect_distant]
radius=5
[/affect_distant]
[/damage]
[/abilities]
[/effect]
[filter]
id=bobby
[/filter]
[/object]
{ASSERT (
[have_unit]
id=alex
ability_id_active=specialX
[/have_unit]
)}
{SUCCEED}
[/event]
#enddef
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give carlos a leadership ability, specialY, that affects all units in a 7 hex radius
# Give bobby a damage ability, specialX, that affects all units in a 5 hex radius when affected by carlos' ability
# Place alex within range of bobby's ability but out of range of carlos' ability
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active and affect alex.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_active_with_second_ability_type" (
{TEST_AFFECT_DISTANT_ACTIVE_WITH_SECOND_ABILITY (ability_type_active=leadership)}
)}
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give carlos a leadership ability, specialY, that affects all units in a 7 hex radius
# Give bobby a damage ability, specialX, that affects all units in a 5 hex radius when affected by carlos' ability
# Place alex within range of bobby's ability but out of range of carlos' ability
# Test whether the ability is active.
##
# Expected end state:
# specialX should be active and affect alex.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_active_with_second_ability" (
{TEST_AFFECT_DISTANT_ACTIVE_WITH_SECOND_ABILITY (ability_id_active=specialY)}
)}
#undef TEST_AFFECT_DISTANT_ACTIVE_WITH_SECOND_ABILITY

View File

@ -0,0 +1,57 @@
#textdomain wesnoth-test
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give charlie an ability specialX, which is affect alice if she in 1 hexes of distance of alex.
# Test whether the ability is active.
##
# Expected end state:
# specialX shouln'd be affect alice.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_inactive" (
[event]
name=start
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if and only if one unit within 1 hex radius is alice"
value=100
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_distant]
radius=1
[filter]
id=alice
[/filter]
[/affect_distant]
[/damage]
[/abilities]
[/effect]
[filter]
id=charlie
[/filter]
[/object]
{ASSERT (
[not]
[have_unit]
id=alice
ability_id_active=specialX
[/have_unit]
[/not]
)}
{SUCCEED}
[/event]
)}

View File

@ -0,0 +1,125 @@
#textdomain wesnoth-test
#define TEST_AFFECT_DISTANT_INACTIVE_WITH_SECOND_ABILITY FILTER
[event]
name=start
[unit]
id=alex
name=_"Alex"
x,y=1,1
type=Elvish Hero
side=1
[/unit]
[unit]
id=bobby
name=_"Bobby"
x,y=1,5
type=Elvish Hero
side=1
[/unit]
[unit]
id=carlos
name=_"Carlos"
x,y=1,12
type=Elvish Hero
side=1
[/unit]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[leadership]
id=specialY
name=_ "specialY"
description=_ "specialY affect units within radius of 5"
value=25
affect_self=no
affect_allies=yes
affect_enemies=yes
[affect_distant]
radius=5
[/affect_distant]
[/leadership]
[/abilities]
[/effect]
[filter]
id=carlos
[/filter]
[/object]
[object]
silent=yes
[effect]
apply_to=new_ability
[abilities]
[damage]
id=specialX
name=_ "specialX"
description=_ "specialX is active if within radius of specialY ability"
value=100
apply_to=self
affect_self=no
affect_allies=yes
affect_enemies=yes
[filter]
{FILTER}
[/filter]
[affect_distant]
radius=5
[/affect_distant]
[/damage]
[/abilities]
[/effect]
[filter]
id=bobby
[/filter]
[/object]
{ASSERT (
[not]
[have_unit]
id=alex
ability_id_active=specialX
[/have_unit]
[/not]
)}
{SUCCEED}
[/event]
#enddef
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give carlos a leadership ability, specialY, that affects all units in a 5 hex radius
# Give bobby a damage ability, specialX, that affects all units in a 5 hex radius when affected by carlos' ability
# Place alex within range of bobby's ability but out of range of carlos' ability
# Place bobby out of range of carlos' ability
# Test whether the ability is active.
##
# Expected end state:
# specialX shouln'd be affect alex because inactive.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_inactive_with_second_ability_type" (
{TEST_AFFECT_DISTANT_INACTIVE_WITH_SECOND_ABILITY (ability_type_active=leadership)}
)}
#####
# API(s) being tested: ability[affect_distant]radius=
##
# Actions:
# Give carlos a leadership ability, specialY, that affects all units in a 5 hex radius
# Give bobby a damage ability, specialX, that affects all units in a 5 hex radius when affected by carlos' ability
# Place alex within range of bobby's ability but out of range of carlos' ability
# Place bobby out of range of carlos' ability
# Test whether the ability is active.
##
# Expected end state:
# specialX shouln'd be affect alex because inactive.
#####
{SEPARATE_KEEP_A_B_C_D_UNIT_TEST "affect_distant_inactive_with_second_ability" (
{TEST_AFFECT_DISTANT_INACTIVE_WITH_SECOND_ABILITY (ability_id_active=specialY)}
)}
#undef TEST_AFFECT_DISTANT_INACTIVE_WITH_SECOND_ABILITY

View File

@ -55,25 +55,6 @@ void move_action::write(config & cfg) const
child["goto_y"] = goto_hex.wml_y();
}
/**
* Reset halo of adjacent units when undo move.
*/
static void reset_adjacent(bool& halo_adjacent, unit_map& units, const map_location& loc)
{
if(halo_adjacent){
unit_map::iterator u = units.find(loc);
const auto adjacent = get_adjacent_tiles(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 == &*u )
continue;
it->anim_comp().set_standing();
}
}
}
/**
* Undoes this action.
* @return true on success; false on an error.
@ -103,17 +84,10 @@ bool move_action::undo(int)
// Move the unit.
unit_display::move_unit(rev_route, u.get_shared_ptr(), true, starting_dir);
bool halo_adjacent = false;
for(const auto [_, cfg] : u->abilities().all_children_view()){
if(!cfg["halo_image"].empty() && cfg.has_child("affect_adjacent")){
halo_adjacent = true;
break;
}
}
reset_adjacent(halo_adjacent, units, rev_route.front());
u->anim_comp().reset_affect_distant(gui);
units.move(u->get_location(), rev_route.back());
unit::clear_status_caches();
reset_adjacent(halo_adjacent, units, rev_route.back());
u->anim_comp().reset_affect_distant(gui);
// Restore the unit's old state.
u = units.find(rev_route.back());

View File

@ -182,6 +182,27 @@ void game_board::check_victory(bool& continue_level,
continue_level = false;
}
void game_board::set_affect_distant_max_radius(utils::optional<int> value, const std::string& tag_name)
{
if(value){
if(!affect_distant_max_radius_[tag_name] || *affect_distant_max_radius_[tag_name] < *value){
affect_distant_max_radius_[tag_name] = value;
}
if(!affect_distant_max_radius_for_filtering_ || *affect_distant_max_radius_for_filtering_ < *value){
affect_distant_max_radius_for_filtering_ = value;
}
}
}
void game_board::set_affect_distant_max_radius_image(utils::optional<int> value)
{
if(value){
if(!affect_distant_max_radius_for_image_ || *affect_distant_max_radius_for_image_ < *value){
affect_distant_max_radius_for_image_ = value;
}
}
}
unit_map::iterator game_board::find_visible_unit(const map_location& loc, const team& current_team, bool see_all)
{
if(!map_->on_board(loc)) {

View File

@ -52,6 +52,17 @@ class game_board : public display_context
n_unit::id_manager unit_id_manager_;
unit_map units_;
/**
* Variables used for define radius for abilities [affect_distant]radius= checking.
*
* @ affect_distant_max_radius_ is used for checking in abilities of defined type.
* @ affect_distant_max_radius_for_filtering_ used when checking abilities in [filter] or animations.
* @ affect_distant_max_radius_for_image_ define radius for images in abilities.
**/
std::map<std::string, utils::optional<int>> affect_distant_max_radius_;
utils::optional<int> affect_distant_max_radius_for_filtering_;
utils::optional<int> affect_distant_max_radius_for_image_;
/**
* Temporary unit move structs:
*
@ -131,6 +142,13 @@ public:
friend void swap(game_board & one, game_board & other);
//when used define radius max for check unit who own a ability with [affect_distant] tag.
utils::optional<int> affect_distant_max_radius(const std::string& value){return affect_distant_max_radius_[value];}
utils::optional<int> affect_distant_max_radius_for_filtering() const {return affect_distant_max_radius_for_filtering_;}
utils::optional<int> affect_distant_max_radius_for_image() const {return affect_distant_max_radius_for_image_;}
void set_affect_distant_max_radius(utils::optional<int> value, const std::string& tag_name = "");
void set_affect_distant_max_radius_image(utils::optional<int> value);
// Saving
void write_config(config & cfg) const;

View File

@ -2750,6 +2750,62 @@ int game_lua_kernel::intf_set_floating_label(lua_State* L, bool spawn)
return 1;
}
namespace
{
const unit_map& get_unit_map()
{
// Used if we're in the game, including during the construction of the display_context
if(resources::gameboard) {
return resources::gameboard->units();
}
// If we get here, we're in the scenario editor
assert(display::get_singleton());
return display::get_singleton()->context().units();
}
void reset_affect_distant(const unit& u_)
{
bool affect_distant = false;
bool affect_adjacent = false;
for(const auto [key, cfg] : u_.abilities().all_children_view()) {
bool image_or_hides = (key == "hides" || cfg.has_attribute("halo_image") || cfg.has_attribute("overlay_image"));
if(!affect_adjacent && image_or_hides && cfg.has_child("affect_adjacent")){
affect_adjacent = true;
}
if(!affect_distant && image_or_hides && cfg.has_child("affect_distant")){
affect_distant = true;
}
if(affect_adjacent && affect_distant){
break;
}
}
const unit_map& units = get_unit_map();
if(affect_adjacent){
const auto adjacent = get_adjacent_tiles(u_.get_location());
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 == &u_ )
continue;
it->anim_comp().set_standing();
}
}
utils::optional<int> max_radius = u_.affect_distant_max_radius();
if(max_radius && affect_distant){
std::vector<map_location> surrounding;
get_tiles_in_radius(u_.get_location(), (*max_radius + 1), surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == &u_) {
continue;
}
unit_itor->anim_comp().set_standing();
}
}
}
}
void game_lua_kernel::put_unit_helper(const map_location& loc)
{
if(game_display_) {
@ -2795,6 +2851,7 @@ int game_lua_kernel::intf_put_unit(lua_State *L)
put_unit_helper(loc);
u.put_map(loc);
u.get_shared()->anim_comp().set_standing();
reset_affect_distant(*u);
} else if(!lua_isnoneornil(L, 1)) {
const vconfig* vcfg = nullptr;
config cfg = luaW_checkconfig(L, 1, vcfg);
@ -2810,8 +2867,10 @@ int game_lua_kernel::intf_put_unit(lua_State *L)
put_unit_helper(loc);
u->set_location(loc);
units().insert(u);
reset_affect_distant(*u);
}
// Fire event if using the deprecated version or if the final argument is not false
// If the final boolean argument is omitted, the actual final argument (the unit or location) will always yield true.
if(luaW_toboolean(L, -1)) {
@ -2838,6 +2897,7 @@ int game_lua_kernel::intf_erase_unit(lua_State *L)
if (!map().on_board(loc)) {
return luaL_argerror(L, 1, "invalid location");
}
reset_affect_distant(*u);
} else if (int side = u.on_recall_list()) {
team &t = board().get_team(side);
// Should it use underlying ID instead?
@ -2900,6 +2960,7 @@ int game_lua_kernel::intf_put_recall_unit(lua_State *L)
units().erase(u->get_location());
resources::whiteboard->on_kill_unit();
u->anim_comp().clear_haloes();
reset_affect_distant(*u);
}
lu->lua_unit::~lua_unit();
new(lu) lua_unit(side, uid);
@ -2924,6 +2985,7 @@ int game_lua_kernel::intf_extract_unit(lua_State *L)
u = units().extract(u->get_location());
assert(u);
u->anim_comp().clear_haloes();
reset_affect_distant(*u);
} else if (int side = lu->on_recall_list()) {
team &t = board().get_team(side);
unit_ptr v = u->clone();
@ -2956,6 +3018,7 @@ int game_lua_kernel::intf_find_vacant_tile(lua_State *L)
const vconfig* vcfg = nullptr;
config cfg = luaW_checkconfig(L, 2, vcfg);
u = unit::create(cfg, false, vcfg);
reset_affect_distant(*u);
}
}
@ -3000,6 +3063,9 @@ static int intf_create_unit(lua_State *L)
config cfg = luaW_checkconfig(L, 1, vcfg);
unit_ptr u = unit::create(cfg, true, vcfg);
luaW_pushunit(L, u);
if (u->get_location().valid()) {
reset_affect_distant(*u);
}
return 1;
}
@ -3159,6 +3225,9 @@ static int intf_transform_unit(lua_State *L)
utp = &utp->get_variation(m2);
}
u.advance_to(*utp);
if (u.get_location().valid()) {
reset_affect_distant(u);
}
return 0;
}

View File

@ -213,6 +213,22 @@ bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc
}
}
}
utils::optional<int> max_radius = affect_distant_max_radius(tag_name);
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(loc, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == this) {
continue;
}
for(const config& i : unit_itor->abilities_.child_range(tag_name)) {
if(get_dist_ability_bool(i, tag_name, loc, *unit_itor, surrounding[j])){
return true;
}
}
}
}
return false;
@ -249,6 +265,22 @@ unit_ability_list unit::get_abilities(const std::string& tag_name, const map_loc
}
}
}
utils::optional<int> max_radius = affect_distant_max_radius(tag_name);
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(loc, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == this) {
continue;
}
for(const config& i : unit_itor->abilities_.child_range(tag_name)) {
if(get_dist_ability_bool(i, tag_name, loc, *unit_itor, surrounding[j])){
res.emplace_back(&i, loc, surrounding[j]);
}
}
}
}
return res;
@ -521,6 +553,44 @@ bool unit::ability_affects_adjacent(const std::string& ability, const config& cf
return false;
}
bool unit::ability_affects_distant(const std::string& ability, const config& cfg, const map_location& loc, const unit& from, const map_location& from_loc) const
{
unsigned int radius = 0;
bool illuminates = ability == "illuminates";
for (const config &i : cfg.child_range("affect_distant"))
{
radius = i["radius"].to_int(0);
if(radius == 0){
continue;
}
unsigned int distance = distance_between(from_loc, loc);
if(distance > radius){
continue;
}
auto filter = i.optional_child("filter");
if (!filter || //filter tag given
unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
return true;
}
}
return false;
}
bool unit::get_dist_ability_bool(const config& cfg, const std::string& ability, const map_location& loc, const unit& from, const map_location& from_loc) const
{
auto filter_lock = from.update_variables_recursion(cfg);
if(!filter_lock) {
show_recursion_warning(from, cfg);
return false;
}
return (ability_affects_distant(ability, cfg, loc, from, from_loc) && affects_side(cfg, side(), from.side()) && from.ability_active_impl(ability, cfg, from_loc));
}
bool unit::get_dist_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const unit& from, const map_location& from_loc, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) const
{
return (get_dist_ability_bool(special, tag_name, loc, from, from_loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
}
bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
{
auto filter = cfg.optional_child("filter_self");
@ -553,13 +623,24 @@ bool unit::has_ability_type(const std::string& ability) const
return !abilities_.child_range(ability).empty();
}
//these two functions below are used in order to add to the unit
//these functions below are used in order to add to the unit
//a second set of halo encoded in the abilities (like illuminates halo in [illuminates] ability for example)
static void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
namespace
{
auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
if(ret == image_list.end()){
image_list.push_back(cfg[attribute_name].str());
void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
{
auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
if(ret == image_list.end()){
image_list.push_back(cfg[attribute_name].str());
}
}
utils::optional<int> affect_distant_max_radius_image()
{
if(resources::gameboard) {
return resources::gameboard->affect_distant_max_radius_for_image();
}
return utils::nullopt;
}
}
@ -596,6 +677,23 @@ std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_t
}
}
}
utils::optional<int> max_radius = affect_distant_max_radius_image();
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(loc_, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == this) {
continue;
}
for(const auto [key, cfg] : unit_itor->abilities_.all_children_view()) {
if(!cfg[image_type + "_image"].str().empty() && get_dist_ability_bool(cfg, key, loc_, *unit_itor, surrounding[j]))
{
add_string_to_vector(image_list, cfg, image_type + "_image");
}
}
}
}
//rearranges vector alphabetically when its size equals or exceeds two.
if(image_list.size() >= 2){
std::sort(image_list.begin(), image_list.end());
@ -1785,6 +1883,26 @@ bool attack_type::check_adj_abilities_impl(const const_attack_ptr& self_attack,
}
return false;
}
bool attack_type::check_dist_abilities(const config& cfg, const std::string& special, const unit& from, const map_location& from_loc) const
{
return check_dist_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, self_loc_, from_loc, AFFECT_SELF, special, "filter_student");
}
bool attack_type::check_dist_abilities_impl(const const_attack_ptr& self_attack, const const_attack_ptr& other_attack, const config& special, const unit_const_ptr& u, const unit& from, const map_location& loc, const map_location& from_loc, AFFECTS whom, const std::string& tag_name, bool leader_bool)
{
if(tag_name == "leadership" && leader_bool){
if((*u).get_dist_ability_bool_weapon(special, tag_name, loc, from, from_loc, self_attack, other_attack)) {
return true;
}
}
if((*u).checking_tags().count(tag_name) != 0){
if((*u).get_dist_ability_bool(special, tag_name, loc, from, from_loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "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
@ -1795,6 +1913,7 @@ bool attack_type::has_weapon_ability(const std::string& special, bool special_id
{
const unit_map& units = get_unit_map();
if(self_){
utils::optional<int> max_radius = self_->affect_distant_max_radius(special_tags ? special : "");
std::vector<special_match> special_tag_matches_self;
std::vector<special_match> special_id_matches_self;
get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
@ -1839,9 +1958,39 @@ bool attack_type::has_weapon_ability(const std::string& special, bool special_id
}
}
}
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(self_loc_, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated())
continue;
if ( &*unit_itor == self_.get() )
continue;
std::vector<special_match> special_tag_matches_dist;
std::vector<special_match> special_id_matches_dist;
get_ability_children(special_tag_matches_dist, special_id_matches_dist, unit_itor->abilities(), special, special_id , special_tags);
if(special_tags){
for(const special_match& entry : special_tag_matches_dist) {
if(check_dist_abilities(*entry.cfg, entry.tag_name, *unit_itor, surrounding[j])){
return true;
}
}
}
if(special_id){
for(const special_match& entry : special_id_matches_dist) {
if(check_dist_abilities(*entry.cfg, entry.tag_name, *unit_itor, surrounding[j])){
return true;
}
}
}
}
}
}
if(other_){
utils::optional<int> max_radius = other_->affect_distant_max_radius(special_tags ? special : "");
std::vector<special_match> special_tag_matches_other;
std::vector<special_match> special_id_matches_other;
get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
@ -1888,6 +2037,35 @@ bool attack_type::has_weapon_ability(const std::string& special, bool special_id
}
}
}
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(other_loc_, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated())
continue;
if ( &*unit_itor == other_.get() )
continue;
std::vector<special_match> special_tag_matches_odist;
std::vector<special_match> special_id_matches_odist;
get_ability_children(special_tag_matches_odist, special_id_matches_odist, unit_itor->abilities(), special, special_id , special_tags);
if(special_tags){
for(const special_match& entry : special_tag_matches_odist) {
if(check_dist_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *unit_itor, other_loc_, surrounding[j], AFFECT_OTHER, entry.tag_name)){
return true;
}
}
}
if(special_id){
for(const special_match& entry : special_id_matches_odist) {
if(check_dist_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *unit_itor, other_loc_, surrounding[j], AFFECT_OTHER, entry.tag_name)){
return true;
}
}
}
}
}
}
return false;
}
@ -2141,6 +2319,7 @@ bool attack_type::has_ability_with_filter(const config & filter) const
}
const unit_map& units = get_unit_map();
if(self_){
utils::optional<int> max_radius = self_->affect_distant_max_radius();
for(const auto [key, cfg] : (*self_).abilities().all_children_view()) {
if(self_->ability_matches_filter(cfg, key, filter)){
if(check_self_abilities(cfg, key)){
@ -2163,9 +2342,27 @@ bool attack_type::has_ability_with_filter(const config & filter) const
}
}
}
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(self_loc_, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated())
continue;
if ( &*unit_itor == self_.get() )
continue;
for(const auto [key, cfg] : unit_itor->abilities().all_children_view()) {
if(unit_itor->ability_matches_filter(cfg, key, filter) && check_dist_abilities(cfg, key, *unit_itor, surrounding[j])){
return true;
}
}
}
}
}
if(other_){
utils::optional<int> max_radius = other_->affect_distant_max_radius();
for(const auto [key, cfg] : (*other_).abilities().all_children_view()) {
if(other_->ability_matches_filter(cfg, key, filter) && check_self_abilities_impl(other_attack_, shared_from_this(), cfg, other_, other_loc_, AFFECT_OTHER, key)){
return true;
@ -2186,6 +2383,23 @@ bool attack_type::has_ability_with_filter(const config & filter) const
}
}
}
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(other_loc_, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated())
continue;
if ( &*unit_itor == other_.get() )
continue;
for(const auto [key, cfg] : unit_itor->abilities().all_children_view()) {
if(unit_itor->ability_matches_filter(cfg, key, filter) && check_dist_abilities_impl(other_attack_, shared_from_this(), cfg, other_, *unit_itor, other_loc_, surrounding[j], AFFECT_OTHER, key)){
return true;
}
}
}
}
}
return false;
}

View File

@ -17,9 +17,12 @@
#include "config.hpp"
#include "display.hpp"
#include "game_board.hpp"
#include "map/map.hpp"
#include "preferences/preferences.hpp"
#include "random.hpp"
#include "resources.hpp"
#include "terrain/filter.hpp"
#include "units/unit.hpp"
#include "units/types.hpp"
@ -196,6 +199,48 @@ void unit_animation_component::reset_after_advance(const unit_type * newtype)
anim_.reset();
}
void unit_animation_component::reset_affect_distant(const display & disp)
{
bool affect_distant = false;
bool affect_adjacent = false;
for(const auto [key, cfg] : u_.abilities().all_children_view()) {
bool image_or_hides = (key == "hides" || cfg.has_attribute("halo_image") || cfg.has_attribute("overlay_image"));
if(!affect_adjacent && image_or_hides && cfg.has_child("affect_adjacent")){
affect_adjacent = true;
}
if(!affect_distant && image_or_hides && cfg.has_child("affect_distant")){
affect_distant = true;
}
if(affect_adjacent && affect_distant){
break;
}
}
const unit_map& units = disp.context().units();
if(affect_adjacent){
const auto adjacent = get_adjacent_tiles(u_.get_location());
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 == &u_ )
continue;
it->anim_comp().set_standing();
}
}
utils::optional<int> max_radius = u_.affect_distant_max_radius();
if(max_radius && affect_distant){
std::vector<map_location> surrounding;
get_tiles_in_radius(u_.get_location(), (*max_radius + 1), surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == &u_) {
continue;
}
unit_itor->anim_comp().set_standing();
}
}
}
void unit_animation_component::apply_new_animation_effect(const config & effect) {
if(effect["id"].empty()) {
unit_animation::add_anims(animations_, effect);

View File

@ -106,6 +106,9 @@ public:
/** Resets the animations list after the unit is advanced. */
void reset_after_advance(const unit_type * newtype = nullptr);
/** Refresh map around unit if has ability with [affect_adjacent/distant] tag */
void reset_affect_distant(const display & disp);
/** Adds an animation described by a config. Uses an internal cache to avoid redoing work. */
void apply_new_animation_effect(const config & effect);

View File

@ -271,6 +271,14 @@ private:
* @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;
/** check_dist_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 from unit distant to self_ is checked.
* @param from_loc location of @ from
*/
bool check_dist_abilities(const config& cfg, const std::string& special, const unit& from, const map_location& from_loc) const;
bool special_active(const config& special, AFFECTS whom, const std::string& tag_name,
bool in_abilities_tag = false) const;
@ -358,6 +366,32 @@ private:
bool leader_bool=false
);
/** check_dist_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 distant to @a u is checked.
* @param loc location of the unit checked.
* @param from_loc location of the unit distant to @a u.
* @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_dist_abilities_impl(
const const_attack_ptr& self_attack,
const const_attack_ptr& other_attack,
const config& special,
const unit_const_ptr& u,
const unit& from,
const map_location& loc,
const map_location& from_loc,
AFFECTS whom,
const std::string& tag_name,
bool leader_bool = false
);
static bool special_active_impl(
const const_attack_ptr& self_attack,
const const_attack_ptr& other_attack,

View File

@ -441,6 +441,24 @@ void unit_filter_compound::fill(const vconfig& cfg)
}
}
}
utils::optional<int> max_radius = args.u.affect_distant_max_radius();
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(args.loc, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == args.u.shared_from_this().get()) {
continue;
}
std::vector<ability_match> ability_id_matches_dist;
get_ability_children_id(ability_id_matches_dist, unit_itor->abilities(), ability);
for(const ability_match& entry : ability_id_matches_dist) {
if(args.u.get_dist_ability_bool(*entry.cfg, entry.tag_name, args.loc, *unit_itor, surrounding[j])){
return true;
}
}
}
}
}
return false;
}
@ -818,6 +836,22 @@ void unit_filter_compound::fill(const vconfig& cfg)
}
}
}
utils::optional<int> max_radius = args.u.affect_distant_max_radius();
if(max_radius){
std::vector<map_location> surrounding;
get_tiles_in_radius(args.loc, *max_radius, surrounding);
for(unsigned j = 0; j < surrounding.size(); ++j){
unit_map::const_iterator unit_itor = units.find(surrounding[j]);
if (unit_itor == units.end() || unit_itor->incapacitated() || &(*unit_itor) == (args.u.shared_from_this()).get()) {
continue;
}
for(const auto [key, cfg] : unit_itor->abilities().all_children_view()) {
if(args.u.get_dist_ability_bool(cfg, key, args.loc, *unit_itor, surrounding[j])){
return true;
}
}
}
}
}
return false;
});

View File

@ -90,6 +90,7 @@ void teleport_unit_between(const map_location& a, const map_location& b, unit& t
animator.add_animation(temp_unit.shared_from_this(),"pre_teleport",a);
animator.start_animations();
animator.wait_for_end();
temp_unit.anim_comp().reset_affect_distant(disp);
}
temp_unit.set_location(b);
@ -104,6 +105,7 @@ void teleport_unit_between(const map_location& a, const map_location& b, unit& t
animator.add_animation(temp_unit.shared_from_this(),"post_teleport",b);
animator.start_animations();
animator.wait_for_end();
temp_unit.anim_comp().reset_affect_distant(disp);
}
temp_unit.anim_comp().set_standing();
@ -260,6 +262,7 @@ void unit_mover::start(const unit_ptr& u)
if ( !can_draw_ )
return;
// If no animation then hide unit until end of movement
(*u).anim_comp().reset_affect_distant(*disp_);
if ( !animate_ ) {
was_hidden_ = u->get_hidden();
u->set_hidden(true);
@ -380,6 +383,7 @@ void unit_mover::proceed_to(const unit_ptr& u, std::size_t path_index, bool upda
u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
// Remember the unit to unhide when the animation finishes.
shown_unit_ = u;
(*u).anim_comp().reset_affect_distant(*disp_);
if ( wait )
wait_for_anims();
}
@ -464,6 +468,7 @@ void unit_mover::finish(const unit_ptr& u, map_location::direction dir)
// Switch the display back to the real unit.
u->set_hidden(was_hidden_);
temp_unit_ptr_->set_hidden(true);
(*u).anim_comp().reset_affect_distant(*disp_);
if(events::mouse_handler* mousehandler = events::mouse_handler::get_singleton()) {
mousehandler->invalidate_reachmap();
@ -589,6 +594,7 @@ void unit_die(const map_location& loc, unit& loser,
if(events::mouse_handler* mousehandler = events::mouse_handler::get_singleton()) {
mousehandler->invalidate_reachmap();
}
loser.anim_comp().reset_affect_distant(*disp);
}
@ -837,6 +843,7 @@ void unit_recruited(const map_location& loc,const map_location& leader_loc)
}
}
}
(*u).anim_comp().reset_affect_distant(*disp);
animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
animator.start_animations();
animator.wait_for_end();

View File

@ -406,6 +406,36 @@ unit::unit(unit_ctor_t)
{
}
namespace
{
void set_affect_distant_max_radius(utils::optional<int> value, const std::string& tag_name)
{
if(resources::gameboard) {
resources::gameboard->set_affect_distant_max_radius(value, tag_name);
}
}
void set_affect_distant_max_radius(const config& cfg, const std::string& tag_name)
{
for(const config& affect_distant : cfg.child_range("affect_distant")) {
if(affect_distant.has_attribute("radius")){
set_affect_distant_max_radius(affect_distant["radius"].to_int(), tag_name);
if(resources::gameboard && (cfg.has_attribute("halo_image") || cfg.has_attribute("overlay_image"))){
resources::gameboard->set_affect_distant_max_radius_image(affect_distant["radius"].to_int());
}
}
}
}
}
utils::optional<int> unit::affect_distant_max_radius(const std::string& tag_name) const
{
if(resources::gameboard) {
return !tag_name.empty() ? resources::gameboard->affect_distant_max_radius(tag_name) : resources::gameboard->affect_distant_max_radius_for_filtering();
}
return utils::nullopt;
}
void unit::init(const config& cfg, bool use_traits, const vconfig* vcfg)
{
loc_ = map_location(cfg["x"], cfg["y"], wml_loc());
@ -448,6 +478,15 @@ void unit::init(const config& cfg, bool use_traits, const vconfig* vcfg)
for(const vconfig& ability_event : ability_events) {
events_.add_child("event", ability_event.get_config());
}
const vconfig::child_list& affect_distants = child.get_children("affect_distant");
for(const vconfig& affect_distant : affect_distants) {
if(affect_distant.has_attribute("radius")){
set_affect_distant_max_radius(affect_distant["radius"].to_int(), key);
if(resources::gameboard && (child.has_attribute("halo_image") || child.has_attribute("overlay_image"))){
resources::gameboard->set_affect_distant_max_radius_image(affect_distant["radius"].to_int());
}
}
}
}
}
const vconfig::child_list& attacks = vcfg->get_children("attack");
@ -473,6 +512,7 @@ void unit::init(const config& cfg, bool use_traits, const vconfig* vcfg)
for(const config& ability_event : ability.child_range("event")) {
events_.add_child("event", ability_event);
}
set_affect_distant_max_radius(ability, key);
}
}
for(const config& attack : cfg.child_range("attack")) {
@ -1098,6 +1138,7 @@ void unit::advance_to(const unit_type& u_type, bool use_traits)
for(const config& ability_event : ability.child_range("event")) {
events.add_child("event", ability_event);
}
set_affect_distant_max_radius(ability, key);
}
}
for(const config& attack : cfg.child_range("attack")) {
@ -2251,6 +2292,7 @@ void unit::apply_builtin_effect(const std::string& apply_to, const config& effec
for(const config& event : cfg.child_range("event")) {
events.add_child("event", event);
}
set_affect_distant_max_radius(cfg, key);
}
}
abilities_.append(to_append);

View File

@ -1788,6 +1788,27 @@ public:
*/
bool get_adj_ability_bool_weapon(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from, const const_attack_ptr& weapon=nullptr, const const_attack_ptr& opp_weapon = nullptr) const;
/** Checks whether this unit is affected by a given ability, and that that ability is active.
* @return True if the ability @a tag_name is active.
* @param cfg the const config to one of abilities @a ability checked.
* @param ability name of ability type checked.
* @param loc location of the unit checked.
* @param from unit distant to @a this is checked in case of [affect_distant] abilities.
* @param from_loc the 'other unit' location.
*/
bool get_dist_ability_bool(const config& cfg, const std::string& ability, const map_location& loc, const unit& from, const map_location& from_loc) 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_distant] abilities.
* @param from_loc location of the @a from unit.
* @param weapon the attack used by unit checked in this function.
* @param opp_weapon the attack used by opponent to unit checked.
*/
bool get_dist_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const unit& from, const map_location& from_loc, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) 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
@ -1878,6 +1899,11 @@ public:
*/
bool ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const;
/**
* @returns game_board affect_distant_max_radius_[tag_name] or affect_distant_max_radius_for_filtering_
* @param tag_name the type of ability corresponding to the variable used, if empty return affect_distant_max_radius_for_filtering_.
*/
utils::optional<int> affect_distant_max_radius(const std::string& tag_name = "") const;
private:
@ -1947,6 +1973,15 @@ private:
*/
bool ability_affects_adjacent(const std::string& ability, const config& cfg, int dir, const map_location& loc, const unit& from) const;
/**
* Check if an ability affects distant units.
* @param ability The type (tag name) of the ability
* @param cfg an ability WML structure
* @param loc The location on which to resolve the ability
* @param from The "other unit" for filter matching
* @param from_loc the "other unit" location
*/
bool ability_affects_distant(const std::string& ability, const config& cfg, const map_location& loc, const unit& from, const map_location& from_loc) const;
/**
* Check if an ability affects the owning unit.
* @param ability The type (tag name) of the ability

View File

@ -422,6 +422,12 @@
0 filter_adjacent_direction_inactive
0 filter_special_id_active
0 filter_ability_special_id_active
0 affect_distant_active
0 affect_distant_inactive
0 affect_distant_active_with_second_ability
0 affect_distant_active_with_second_ability_type
0 affect_distant_inactive_with_second_ability
0 affect_distant_inactive_with_second_ability_type
0 filter_special_id_not_exists
0 special_id_active_lua_function
0 leadership_when_other_has_special