Skip to content

Commit

Permalink
Add [attack_alignment] weapon special for specific attacks
Browse files Browse the repository at this point in the history
Although it is possible to mimic the operation of an alignment of the unit according to the time of day in standard conditions with [damage], the use of a fixed value limits the use of this substitutes for pre-determined conditions, but in the case of deep caves or large illuminations, the absolute value is no longer 25 and the special damage continues to mimic the standard values. That's why I propose a [attack_alignment] which actually replicates the alignment for a single attack.
The objective being that a unit can have attacks of alignment different from the standard alignment.
  • Loading branch information
newfrenchy83 committed Oct 8, 2023
1 parent b5805ec commit 53e46ea
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### WML Engine
* Add a [attack_alignment] special to change the alignment of an attack under specific conditions (terrain, leadership, etc)
1 change: 1 addition & 0 deletions data/schema/units/abilities.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
{BASED_ON_SPECIAL "poison"}
{BASED_ON_SPECIAL "slow"}
{BASED_ON_SPECIAL "petrifies"}
{BASED_ON_SPECIAL "attack_alignment"}
[tag]
name="*"
max=infinite
Expand Down
6 changes: 6 additions & 0 deletions data/schema/units/specials.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@
{SIMPLE_KEY priority real}
[/tag]
[/tag]
[tag]
name="attack_alignment"
max=infinite
super="units/unit_type/attack/specials/~value~"
{SIMPLE_KEY alignment alignment}
[/tag]
[tag]
name="attacks"
max=infinite
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#textdomain wesnoth-test

{GENERIC_UNIT_TEST "special_alignment_test" (
[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]
{ABILITY_ILLUMINATES}
[/abilities]
[/effect]
[effect]
apply_to=attack
[set_specials]
mode=append
[attacks]
value=1
[/attacks]
[damage]
value=12
[/damage]
[attack_alignment]
alignment=lawful
[/attack_alignment]
[chance_to_hit]
value=100
[/chance_to_hit]
[/set_specials]
[/effect]
[filter]
id=bob
[/filter]
[/object]
[object]
silent=yes
[effect]
apply_to=attack
[set_specials]
mode=append
[attacks]
value=1
[/attacks]
[damage]
value=12
[/damage]
[attack_alignment]
alignment=chaotic
[/attack_alignment]
[chance_to_hit]
value=100
[/chance_to_hit]
[/set_specials]
[/effect]
[filter]
id=alice
[/filter]
[/object]

[store_unit]
[filter]
id=alice
[/filter]
variable=a
kill=yes
[/store_unit]
[store_unit]
[filter]
id=bob
[/filter]
variable=b
[/store_unit]
[unstore_unit]
variable=a
find_vacant=yes
x,y=$b.x,$b.y
[/unstore_unit]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]

[do_command]
[attack]
weapon=0
defender_weapon=0
[source]
x,y=$a.x,$a.y
[/source]
[destination]
x,y=$b.x,$b.y
[/destination]
[/attack]
[/do_command]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]
[store_unit]
[filter]
id=bob
[/filter]
variable=b
[/store_unit]
#damage without modification are 12, if test fail hitpoints = 100-12 = 88
#if succed then damage inflicted by bob are 15(lawful and illumination) and by alice 9(chaotic)
{ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 85})}
{ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 91})}
{SUCCEED}
[/event]
)}

{GENERIC_UNIT_TEST "special_custom_alignment_test" (
[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]
{ABILITY_ILLUMINATES}
[/abilities]
[/effect]
[effect]
apply_to=attack
[set_specials]
mode=append
[attacks]
value=1
[/attacks]
[damage]
value=12
[/damage]
[attack_alignment]
alignment=lawful
multiply=2
[/attack_alignment]
[chance_to_hit]
value=100
[/chance_to_hit]
[/set_specials]
[/effect]
[filter]
id=bob
[/filter]
[/object]
[object]
silent=yes
[effect]
apply_to=attack
[set_specials]
mode=append
[attacks]
value=1
[/attacks]
[damage]
value=12
[/damage]
[attack_alignment]
alignment=chaotic
[/attack_alignment]
[chance_to_hit]
value=100
[/chance_to_hit]
[/set_specials]
[/effect]
[filter]
id=alice
[/filter]
[/object]

[store_unit]
[filter]
id=alice
[/filter]
variable=a
kill=yes
[/store_unit]
[store_unit]
[filter]
id=bob
[/filter]
variable=b
[/store_unit]
[unstore_unit]
variable=a
find_vacant=yes
x,y=$b.x,$b.y
[/unstore_unit]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]

[do_command]
[attack]
weapon=0
defender_weapon=0
[source]
x,y=$a.x,$a.y
[/source]
[destination]
x,y=$b.x,$b.y
[/destination]
[/attack]
[/do_command]
[store_unit]
[filter]
id=alice
[/filter]
variable=a
[/store_unit]
[store_unit]
[filter]
id=bob
[/filter]
variable=b
[/store_unit]
#damage without modification are 12, if test fail hitpoints = 100-12 = 88
#if succed then damage inflicted by bob are 15(lawful and illumination) and by alice 9(chaotic)
{ASSERT ({VARIABLE_CONDITIONAL a.hitpoints equals 82})}
{ASSERT ({VARIABLE_CONDITIONAL b.hitpoints equals 91})}
{SUCCEED}
[/event]
)}
6 changes: 4 additions & 2 deletions src/actions/attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,10 @@ battle_context_unit_stats::battle_context_unit_stats(nonempty_unit_const_ptr up,
int damage_multiplier = 100;

// Time of day bonus.
damage_multiplier += combat_modifier(
resources::gameboard->units(), resources::gameboard->map(), u_loc, u.alignment(), u.is_fearless());
std::pair<unit_alignments::type, int> alignment = weapon->specials_alignment();
int tod_bonus = combat_modifier(
resources::gameboard->units(), resources::gameboard->map(), u_loc, alignment.first, u.is_fearless());
damage_multiplier += alignment.second * tod_bonus;

// Leadership bonus.
int leader_bonus = under_leadership(u, u_loc, weapon, opp_weapon);
Expand Down
6 changes: 4 additions & 2 deletions src/gui/dialogs/attack_predictions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,10 @@ void attack_predictions::set_data(window& window, const combatant_data& attacker
// Time of day modifier.
const unit& u = *attacker.unit_;

const int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
u.get_location(), u.alignment(), u.is_fearless());
std::pair<unit_alignments::type, int> alignment = weapon->specials_alignment();
int tod_bonus = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
u.get_location(), alignment.first, u.is_fearless());
const int tod_modifier = alignment.second * tod_bonus;

if(tod_modifier != 0) {
set_label_helper("tod_modifier", utils::signed_percent(tod_modifier));
Expand Down
4 changes: 2 additions & 2 deletions src/gui/dialogs/unit_attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,13 @@ void unit_attack::pre_show(window& window)

const std::set<std::string> checking_tags_other = {"disable", "berserk", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
std::string attw_specials = attacker_weapon.weapon_specials();
std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage", "attack_alignment"});
std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0 && defender_weapon.num_attacks() == 0 && defender.chance_to_hit == 0);
std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
std::string defw_specials_dmg = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
std::string defw_specials_dmg = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage", "attack_alignment"}) : "";
std::string defw_specials_atk = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
std::string defw_specials_cth = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
std::string defw_specials_others = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
Expand Down
5 changes: 3 additions & 2 deletions src/reports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,9 @@ static int attack_info(const reports::context& rc, const attack_type &at, config
int specials_damage = at.modified_damage();
int damage_multiplier = 100;
const_attack_ptr weapon = at.shared_from_this();
int tod_bonus = combat_modifier(get_visible_time_of_day_at(rc, hex), u.alignment(), u.is_fearless());
damage_multiplier += tod_bonus;
std::pair<unit_alignments::type, int> alignment = weapon->specials_alignment();
int tod_bonus = combat_modifier(get_visible_time_of_day_at(rc, hex), alignment.first, u.is_fearless());
damage_multiplier += alignment.second * tod_bonus;
int leader_bonus = under_leadership(u, hex, weapon);
if (leader_bonus != 0)
damage_multiplier += leader_bonus;
Expand Down
54 changes: 54 additions & 0 deletions src/units/abilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,60 @@ int attack_type::modified_damage() const
return damage_value;
}

static void alignment_list(unit_ability_list& align_list)
{ //check if have abilities or specials alignment and remove alignment bad specified
const std::set<std::string> checking_alignment = {"neutral", "lawful", "chaotic", "liminal"};
utils::erase_if(align_list, [&](const unit_ability& i) {
return (checking_alignment.count((*i.ability_cfg)["alignment"].str()) == 0);
});
}

void attack_type::choose_alignment(unit_ability_list& align_list, unit_alignments::type& alignment) const
{
std::map<std::string, int> values;
std::map<std::string, unit_ability_list> lists;
for(const auto& i : align_list) {
const auto& alignment = (*i.ability_cfg)["alignment"].str();
values[alignment] += 1;
lists[alignment].emplace_back(i);
}
int count_liminal = values["liminal"];
int count_chaotic = values["chaotic"];
int count_lawful = values["lawful"];
int count_neutral = values["neutral"];
std::string str_alignment;
if(count_liminal > 0 && count_liminal >= count_chaotic && count_liminal >= count_lawful && count_liminal >= count_neutral){
str_alignment = "liminal";
}
else if((count_neutral > 0 && count_neutral > count_chaotic && count_neutral > count_lawful) || (count_lawful > 0 && count_lawful == count_chaotic)){
str_alignment = "neutral";
}
else if( count_lawful > 0 && count_lawful > count_chaotic){
str_alignment = "lawful";
}
else if( count_chaotic > 0 && count_chaotic > count_lawful){
str_alignment = "chaotic";
}

auto new_align = unit_alignments::get_enum(str_alignment);
if(new_align) {
alignment = *new_align;
}
align_list = lists[str_alignment];
}

std::pair<unit_alignments::type, int> attack_type::specials_alignment() const
{
unit_ability_list align_list = get_specials_and_abilities("attack_alignment");
alignment_list(align_list);
if(align_list.empty()){
return {(*self_).alignment(), 1};
}
unit_alignments::type alignment;
choose_alignment(align_list, alignment);
return {alignment, (std::max(0, composite_value(align_list, 100))/100)};
}


namespace { // Helpers for attack_type::special_active()

Expand Down
Loading

0 comments on commit 53e46ea

Please sign in to comment.