From 70d8ed703e2105283e54e883c22ac2c0b5532a8b Mon Sep 17 00:00:00 2001 From: newfrenchy83 Date: Thu, 17 Oct 2024 18:29:13 +0200 Subject: [PATCH] Fix [damage] weapon special produces unexpected damage values Fix https://github.com/wesnoth/wesnoth/issues/9467 --- .../AbilitiesWML/special_calculations.cfg | 16 +++++++------- src/actions/attack.cpp | 12 +++++------ src/reports.cpp | 4 ++-- src/units/abilities.cpp | 21 ++++++++++--------- src/units/abilities.hpp | 3 +++ src/units/attack_type.hpp | 2 +- 6 files changed, 31 insertions(+), 27 deletions(-) diff --git a/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_calculations.cfg b/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_calculations.cfg index 31c1f5d711c59..911e318cdad9c 100644 --- a/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_calculations.cfg +++ b/data/test/scenarios/wml_tests/UnitsWML/AbilitiesWML/special_calculations.cfg @@ -178,9 +178,9 @@ # API(s) being tested: [attacks]divide= ## # Expected end state: -# Bob's attack has 4 strikes after rounding down. +# Bob's attack has 5 strikes after rounding up. ##### -{ONE_CALCULATION_UNIT_TEST divide_float_2dp 10 (divide=2.02) 4} +{ONE_CALCULATION_UNIT_TEST divide_float_2dp 10 (divide=2.02) 5} ##### # API(s) being tested: [attacks]divide= ## @@ -192,16 +192,16 @@ # API(s) being tested: [attacks] ## # Expected end state: -# Bob's attack is the expected answer: 10 strikes * 2 / 3 = 6 strikes after rounding down. +# Bob's attack is the expected answer: 10 strikes * 2 / 3 = 7 strikes after rounding up. ##### -{ONE_CALCULATION_UNIT_TEST divide_multiply_combined 10 (divide,multiply=3,2) 6} +{ONE_CALCULATION_UNIT_TEST divide_multiply_combined 10 (divide,multiply=3,2) 7} ##### # API(s) being tested: [attacks]divide=,[attacks]multiply= ## # Expected end state: -# Bob's attack is the expected answer: 10 strikes * 2 / 3 = 6 strikes after rounding down. +# Bob's attack is the expected answer: 10 strikes * 2 / 3 = 7 strikes after rounding up. ##### -{TWO_CALCULATION_UNIT_TEST divide_multiply_separated 10 (divide=3) (multiply=2) 6} +{TWO_CALCULATION_UNIT_TEST divide_multiply_separated 10 (divide=3) (multiply=2) 7} ##### # API(s) being tested: [attacks]multiply= ## @@ -213,9 +213,9 @@ # API(s) being tested: [attacks]multiply= ## # Expected end state: -# Bob's attack has 9 strikes, because 3 * 3.334 first rounds 3.334 to 3.33, and then 3 * 3.33 floors to 9. +# Bob's attack has 10 strikes, because 3 * 3.334 first rounds 3.334 to 3.33, and then 3 * 3.33 round up to 10. ##### -{ONE_CALCULATION_UNIT_TEST multiply_float_3dp 3 (multiply=3.334) 9} +{ONE_CALCULATION_UNIT_TEST multiply_float_3dp 3 (multiply=3.334) 10} #undef ONE_CALCULATION_UNIT_TEST #undef TWO_CALCULATION_UNIT_TEST diff --git a/src/actions/attack.cpp b/src/actions/attack.cpp index 29792d5e30224..abdb48457202c 100644 --- a/src/actions/attack.cpp +++ b/src/actions/attack.cpp @@ -181,7 +181,7 @@ battle_context_unit_stats::battle_context_unit_stats(nonempty_unit_const_ptr up, chance_to_hit = std::clamp(cth, 0, 100); // Compute base damage done with the weapon. - int base_damage = weapon->modified_damage(); + double base_damage = weapon->modified_damage(); // Get the damage multiplier applied to the base damage of the weapon. int damage_multiplier = 100; @@ -201,8 +201,8 @@ battle_context_unit_stats::battle_context_unit_stats(nonempty_unit_const_ptr up, damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc, opp_weapon); // Compute both the normal and slowed damage. - damage = round_damage(base_damage, damage_multiplier, 10000); - slow_damage = round_damage(base_damage, damage_multiplier, 20000); + damage = std::round(round_damage(base_damage, damage_multiplier, 10000)); + slow_damage = std::round(round_damage(base_damage, damage_multiplier, 20000)); if(is_slowed) { damage = slow_damage; @@ -315,15 +315,15 @@ battle_context_unit_stats::battle_context_unit_stats(const unit_type* u_type, chance_to_hit = std::clamp(cth, 0, 100); - int base_damage = weapon->modified_damage(); + double base_damage = weapon->modified_damage(); int damage_multiplier = 100; unit_alignments::type alignment = weapon->alignment().value_or(u_type->alignment()); damage_multiplier += generic_combat_modifier(lawful_bonus, alignment, u_type->musthave_status("fearless"), 0); damage_multiplier *= opp_type->resistance_against(weapon->type(), !attacking); - damage = round_damage(base_damage, damage_multiplier, 10000); - slow_damage = round_damage(base_damage, damage_multiplier, 20000); + damage = std::round(round_damage(base_damage, damage_multiplier, 10000)); + slow_damage = std::round(round_damage(base_damage, damage_multiplier, 20000)); if(drains) { // Compute the drain percent (with 50% as the base for backward compatibility) diff --git a/src/reports.cpp b/src/reports.cpp index 4ab80de296d0c..a51b8c9fc8dbf 100644 --- a/src/reports.cpp +++ b/src/reports.cpp @@ -781,7 +781,7 @@ static int attack_info(const reports::context& rc, const attack_type &at, config { auto ctx = at.specials_context(u.shared_from_this(), hex, u.side() == rc.screen().playing_team().side()); int base_damage = at.damage(); - int specials_damage = at.modified_damage(); + double specials_damage = at.modified_damage(); int damage_multiplier = 100; const_attack_ptr weapon = at.shared_from_this(); unit_alignments::type attack_alignment = weapon->alignment().value_or(u.alignment()); @@ -794,7 +794,7 @@ static int attack_info(const reports::context& rc, const attack_type &at, config bool slowed = u.get_state(unit::STATE_SLOWED); int damage_divisor = slowed ? 20000 : 10000; // Assume no specific resistance (i.e. multiply by 100). - damage = round_damage(specials_damage, damage_multiplier * 100, damage_divisor); + damage = std::round(round_damage(specials_damage, damage_multiplier * 100, damage_divisor)); // Hit points are used to calculate swarm, so they need to be bounded. unsigned max_hp = u.max_hitpoints(); diff --git a/src/units/abilities.cpp b/src/units/abilities.cpp index a3ba92fbb8e7c..a3180915bbb5a 100644 --- a/src/units/abilities.cpp +++ b/src/units/abilities.cpp @@ -1371,9 +1371,9 @@ std::set attack_type::alternative_damage_types() const /** * Returns the damage per attack of this weapon, considering specials. */ -int attack_type::modified_damage() const +double attack_type::modified_damage() const { - int damage_value = composite_value(get_specials_and_abilities("damage"), damage()); + double damage_value = unit_abilities::effect(get_specials_and_abilities("damage"), damage(), shared_from_this()).get_composite_double_value(); return damage_value; } @@ -2438,8 +2438,8 @@ effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, EFF individual_effect set_effect_max; individual_effect set_effect_min; - utils::optional max_value = utils::nullopt; - utils::optional min_value = utils::nullopt; + utils::optional max_value = utils::nullopt; + utils::optional min_value = utils::nullopt; for (const unit_ability & ability : list) { const config& cfg = *ability.ability_cfg; @@ -2474,10 +2474,10 @@ effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, EFF if(wham == EFFECT_DEFAULT || wham == EFFECT_CUMULABLE){ if(cfg.has_attribute("max_value")){ - max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int(); + max_value = max_value ? std::min(*max_value, cfg["max_value"].to_double()) : cfg["max_value"].to_double(); } if(cfg.has_attribute("min_value")){ - min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int(); + min_value = min_value ? std::max(*min_value, cfg["min_value"].to_double()) : cfg["min_value"].to_double(); } } @@ -2574,15 +2574,16 @@ effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, EFF effect_list_.push_back(val.second); } - composite_value_ = static_cast((value_set + addition + substraction) * multiplier / divisor); + composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor; //clamp what if min_value < max_value or one attribute only used. if(max_value && min_value && *min_value < *max_value) { - composite_value_ = std::clamp(*min_value, *max_value, composite_value_); + composite_double_value_ = std::clamp(*min_value, *max_value, composite_double_value_); } else if(max_value && !min_value) { - composite_value_ = std::min(*max_value, composite_value_); + composite_double_value_ = std::min(*max_value, composite_double_value_); } else if(min_value && !max_value) { - composite_value_ = std::max(*min_value, composite_value_); + composite_double_value_ = std::max(*min_value, composite_double_value_); } + composite_value_ = std::round(composite_double_value_); } } // end namespace unit_abilities diff --git a/src/units/abilities.hpp b/src/units/abilities.hpp index 9c5f5ea2b6e1e..229bb0970963a 100644 --- a/src/units/abilities.hpp +++ b/src/units/abilities.hpp @@ -48,6 +48,8 @@ class effect int get_composite_value() const { return composite_value_; } + double get_composite_double_value() const + { return composite_double_value_; } const_iterator begin() const { return effect_list_.begin(); } const_iterator end() const @@ -55,6 +57,7 @@ class effect private: std::vector effect_list_; int composite_value_; + double composite_double_value_; }; diff --git a/src/units/attack_type.hpp b/src/units/attack_type.hpp index 35afe1f4e04bc..0553458b3c139 100644 --- a/src/units/attack_type.hpp +++ b/src/units/attack_type.hpp @@ -112,7 +112,7 @@ class attack_type : public std::enable_shared_from_this std::set alternative_damage_types() const; /** Returns the damage per attack of this weapon, considering specials. */ - int modified_damage() const; + double modified_damage() const; /** Return the special weapon value, considering specials. * @param abil_list The list of special checked.