use serde::{Deserialize, Serialize};
use crate::buff_types::BuffableStat;
use crate::team::ResolvedBuff;
use crate::types::Element;
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Enemy {
pub level: u32,
pub resistance: f64,
pub def_reduction: f64,
#[serde(default)]
pub def_ignore: f64,
}
pub fn apply_enemy_debuffs(
enemy: &Enemy,
buffs: &[ResolvedBuff],
element: Option<Element>,
) -> Enemy {
let mut res_reduction = 0.0;
let mut def_reduction_add = 0.0;
let mut def_ignore_add = 0.0;
for buff in buffs {
match buff.stat {
BuffableStat::ElementalResReduction(e) => {
if element == Some(e) {
res_reduction += buff.value;
}
}
BuffableStat::PhysicalResReduction => {
if element.is_none() {
res_reduction += buff.value;
}
}
BuffableStat::DefReduction => {
def_reduction_add += buff.value;
}
BuffableStat::DefIgnore => {
def_ignore_add += buff.value;
}
_ => {}
}
}
Enemy {
level: enemy.level,
resistance: enemy.resistance - res_reduction,
def_reduction: f64::min(1.0, enemy.def_reduction + def_reduction_add),
def_ignore: f64::min(1.0, enemy.def_ignore + def_ignore_add),
}
}
pub fn superconduct_debuff() -> ResolvedBuff {
ResolvedBuff {
source: "Superconduct".to_string(),
stat: BuffableStat::PhysicalResReduction,
value: 0.40,
target: crate::team::BuffTarget::Team,
origin: None,
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct EnemyDebuffs {
pub pyro_res_reduction: f64,
pub hydro_res_reduction: f64,
pub electro_res_reduction: f64,
pub cryo_res_reduction: f64,
pub dendro_res_reduction: f64,
pub anemo_res_reduction: f64,
pub geo_res_reduction: f64,
pub physical_res_reduction: f64,
pub def_reduction: f64,
pub def_ignore: f64,
}
pub(crate) fn collect_enemy_debuffs(buffs: &[ResolvedBuff]) -> EnemyDebuffs {
let mut d = EnemyDebuffs::default();
for buff in buffs {
match buff.stat {
BuffableStat::ElementalResReduction(Element::Pyro) => {
d.pyro_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Hydro) => {
d.hydro_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Electro) => {
d.electro_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Cryo) => {
d.cryo_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Dendro) => {
d.dendro_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Anemo) => {
d.anemo_res_reduction += buff.value
}
BuffableStat::ElementalResReduction(Element::Geo) => d.geo_res_reduction += buff.value,
BuffableStat::PhysicalResReduction => d.physical_res_reduction += buff.value,
BuffableStat::DefReduction => d.def_reduction += buff.value,
BuffableStat::DefIgnore => d.def_ignore += buff.value,
_ => {}
}
}
d
}
pub fn apply_debuffs_to_enemy(
enemy: &Enemy,
debuffs: &EnemyDebuffs,
element: Option<Element>,
) -> Enemy {
let res_reduction = match element {
Some(Element::Pyro) => debuffs.pyro_res_reduction,
Some(Element::Hydro) => debuffs.hydro_res_reduction,
Some(Element::Electro) => debuffs.electro_res_reduction,
Some(Element::Cryo) => debuffs.cryo_res_reduction,
Some(Element::Dendro) => debuffs.dendro_res_reduction,
Some(Element::Anemo) => debuffs.anemo_res_reduction,
Some(Element::Geo) => debuffs.geo_res_reduction,
None => debuffs.physical_res_reduction,
};
Enemy {
level: enemy.level,
resistance: enemy.resistance - res_reduction,
def_reduction: f64::min(1.0, enemy.def_reduction + debuffs.def_reduction),
def_ignore: f64::min(1.0, enemy.def_ignore + debuffs.def_ignore),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::team::BuffTarget;
const EPSILON: f64 = 1e-6;
fn base_enemy() -> Enemy {
Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
}
}
fn res_reduction_buff(element: Element, value: f64) -> ResolvedBuff {
ResolvedBuff {
source: "Test RES Shred".into(),
stat: BuffableStat::ElementalResReduction(element),
value,
target: BuffTarget::Team,
origin: None,
}
}
#[test]
fn test_empty_buffs_no_change() {
let enemy = base_enemy();
let result = apply_enemy_debuffs(&enemy, &[], Some(Element::Pyro));
assert!((result.resistance - 0.10).abs() < EPSILON);
assert!((result.def_reduction - 0.0).abs() < EPSILON);
assert_eq!(result.level, 90);
}
#[test]
fn test_elemental_res_reduction_matching() {
let enemy = base_enemy();
let buffs = vec![res_reduction_buff(Element::Pyro, 0.40)];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - (-0.30)).abs() < EPSILON);
}
#[test]
fn test_elemental_res_reduction_non_matching_skipped() {
let enemy = base_enemy();
let buffs = vec![res_reduction_buff(Element::Pyro, 0.40)];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Cryo));
assert!((result.resistance - 0.10).abs() < EPSILON);
}
#[test]
fn test_physical_res_reduction() {
let enemy = base_enemy();
let buffs = vec![ResolvedBuff {
source: "Superconduct".into(),
stat: BuffableStat::PhysicalResReduction,
value: 0.40,
target: BuffTarget::Team,
origin: None,
}];
let result = apply_enemy_debuffs(&enemy, &buffs, None);
assert!((result.resistance - (-0.30)).abs() < EPSILON);
}
#[test]
fn test_physical_res_reduction_ignored_for_elemental() {
let enemy = base_enemy();
let buffs = vec![ResolvedBuff {
source: "Superconduct".into(),
stat: BuffableStat::PhysicalResReduction,
value: 0.40,
target: BuffTarget::Team,
origin: None,
}];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - 0.10).abs() < EPSILON);
}
#[test]
fn test_multiple_debuffs_stack_additively() {
let enemy = base_enemy();
let buffs = vec![
res_reduction_buff(Element::Pyro, 0.40),
res_reduction_buff(Element::Pyro, 0.20),
];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - (-0.50)).abs() < EPSILON);
}
#[test]
fn test_negative_resistance_no_floor() {
let enemy = base_enemy();
let buffs = vec![res_reduction_buff(Element::Pyro, 0.80)];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - (-0.70)).abs() < EPSILON);
}
#[test]
fn test_def_reduction() {
let enemy = base_enemy();
let buffs = vec![ResolvedBuff {
source: "Lisa A4".into(),
stat: BuffableStat::DefReduction,
value: 0.15,
target: BuffTarget::Team,
origin: None,
}];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Electro));
assert!((result.def_reduction - 0.15).abs() < EPSILON);
}
#[test]
fn test_def_reduction_adds_to_existing() {
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.20,
def_ignore: 0.0,
};
let buffs = vec![ResolvedBuff {
source: "Lisa A4".into(),
stat: BuffableStat::DefReduction,
value: 0.15,
target: BuffTarget::Team,
origin: None,
}];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Electro));
assert!((result.def_reduction - 0.35).abs() < EPSILON);
}
#[test]
fn test_def_reduction_clamped_at_1() {
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.80,
def_ignore: 0.0,
};
let buffs = vec![ResolvedBuff {
source: "Test".into(),
stat: BuffableStat::DefReduction,
value: 0.50,
target: BuffTarget::Team,
origin: None,
}];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Electro));
assert!((result.def_reduction - 1.0).abs() < EPSILON);
}
#[test]
fn test_ally_buffs_ignored() {
let enemy = base_enemy();
let buffs = vec![
ResolvedBuff {
source: "Bennett".into(),
stat: BuffableStat::AtkFlat,
value: 1000.0,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Noblesse".into(),
stat: BuffableStat::AtkPercent,
value: 0.20,
target: BuffTarget::Team,
origin: None,
},
];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - 0.10).abs() < EPSILON);
assert!((result.def_reduction - 0.0).abs() < EPSILON);
}
#[test]
fn test_physical_attack_zhongli_only_physical_shred() {
let enemy = base_enemy();
let buffs = vec![
res_reduction_buff(Element::Pyro, 0.20),
res_reduction_buff(Element::Hydro, 0.20),
res_reduction_buff(Element::Electro, 0.20),
res_reduction_buff(Element::Cryo, 0.20),
res_reduction_buff(Element::Dendro, 0.20),
res_reduction_buff(Element::Anemo, 0.20),
res_reduction_buff(Element::Geo, 0.20),
ResolvedBuff {
source: "Zhongli Shield".into(),
stat: BuffableStat::PhysicalResReduction,
value: 0.20,
target: BuffTarget::Team,
origin: None,
},
];
let result = apply_enemy_debuffs(&enemy, &buffs, None);
assert!((result.resistance - (-0.10)).abs() < EPSILON);
}
#[test]
fn test_superconduct_debuff_values() {
let debuff = superconduct_debuff();
assert_eq!(debuff.stat, BuffableStat::PhysicalResReduction);
assert!((debuff.value - 0.40).abs() < EPSILON);
}
#[test]
fn test_golden_res_10_shred_20() {
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![res_reduction_buff(Element::Pyro, 0.20)];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - (-0.10)).abs() < EPSILON);
let mult = crate::damage::resistance_multiplier(&result);
assert!((mult - 1.05).abs() < EPSILON);
}
#[test]
fn test_golden_res_10_shred_60() {
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![
res_reduction_buff(Element::Pyro, 0.40),
res_reduction_buff(Element::Pyro, 0.20),
];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - (-0.50)).abs() < EPSILON);
let mult = crate::damage::resistance_multiplier(&result);
assert!((mult - 1.25).abs() < EPSILON);
}
#[test]
fn test_golden_res_70_shred_40() {
let enemy = Enemy {
level: 90,
resistance: 0.70,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![res_reduction_buff(Element::Pyro, 0.40)];
let result = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
assert!((result.resistance - 0.30).abs() < EPSILON);
let mult = crate::damage::resistance_multiplier(&result);
assert!((mult - 0.70).abs() < EPSILON);
}
#[test]
fn test_integration_vv_zhongli_stacked_damage() {
use crate::damage::{DamageInput, calculate_damage};
use crate::stats::Stats;
use crate::types::{DamageType, ScalingStat};
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![
res_reduction_buff(Element::Pyro, 0.40), res_reduction_buff(Element::Pyro, 0.20), ];
let debuffed = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Pyro));
let input = DamageInput {
character_level: 90,
stats: Stats {
atk: 2000.0,
crit_rate: 0.0,
crit_dmg: 0.0,
dmg_bonus: 0.0,
..Default::default()
},
talent_multiplier: 1.0,
scaling_stat: ScalingStat::Atk,
damage_type: DamageType::Normal,
element: Some(Element::Pyro),
reaction: None,
reaction_bonus: 0.0,
flat_dmg: 0.0,
};
let result_no_debuff = calculate_damage(&input, &enemy).unwrap();
let result_debuffed = calculate_damage(&input, &debuffed).unwrap();
assert!(result_debuffed.non_crit > result_no_debuff.non_crit);
let ratio = result_debuffed.non_crit / result_no_debuff.non_crit;
let expected_ratio = 1.25 / 0.90;
assert!((ratio - expected_ratio).abs() < 0.001);
}
#[test]
fn test_integration_superconduct_physical() {
use crate::damage::{DamageInput, calculate_damage};
use crate::stats::Stats;
use crate::types::{DamageType, ScalingStat};
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![superconduct_debuff()]; let debuffed = apply_enemy_debuffs(&enemy, &buffs, None);
let input = DamageInput {
character_level: 90,
stats: Stats {
atk: 2000.0,
crit_rate: 0.0,
crit_dmg: 0.0,
dmg_bonus: 0.0,
..Default::default()
},
talent_multiplier: 1.0,
scaling_stat: ScalingStat::Atk,
damage_type: DamageType::Normal,
element: None, reaction: None,
reaction_bonus: 0.0,
flat_dmg: 0.0,
};
let result_no_debuff = calculate_damage(&input, &enemy).unwrap();
let result_debuffed = calculate_damage(&input, &debuffed).unwrap();
let ratio = result_debuffed.non_crit / result_no_debuff.non_crit;
let expected_ratio = 1.15 / 0.90;
assert!((ratio - expected_ratio).abs() < 0.001);
}
#[test]
fn test_integration_lisa_def_reduction() {
use crate::damage::{DamageInput, calculate_damage};
use crate::stats::Stats;
use crate::types::{DamageType, ScalingStat};
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let buffs = vec![ResolvedBuff {
source: "Lisa A4".into(),
stat: BuffableStat::DefReduction,
value: 0.15,
target: BuffTarget::Team,
origin: None,
}];
let debuffed = apply_enemy_debuffs(&enemy, &buffs, Some(Element::Electro));
let input = DamageInput {
character_level: 90,
stats: Stats {
atk: 2000.0,
crit_rate: 0.0,
crit_dmg: 0.0,
dmg_bonus: 0.0,
..Default::default()
},
talent_multiplier: 1.0,
scaling_stat: ScalingStat::Atk,
damage_type: DamageType::Normal,
element: Some(Element::Electro),
reaction: None,
reaction_bonus: 0.0,
flat_dmg: 0.0,
};
let result_no_debuff = calculate_damage(&input, &enemy).unwrap();
let result_debuffed = calculate_damage(&input, &debuffed).unwrap();
assert!(result_debuffed.non_crit > result_no_debuff.non_crit);
let def_mult_debuffed = 190.0 / (190.0 + 190.0 * 0.85);
let def_mult_original = 0.5;
let expected_ratio = def_mult_debuffed / def_mult_original;
let ratio = result_debuffed.non_crit / result_no_debuff.non_crit;
assert!((ratio - expected_ratio).abs() < 0.001);
}
#[test]
fn test_collect_enemy_debuffs_empty() {
let debuffs = collect_enemy_debuffs(&[]);
assert!((debuffs.pyro_res_reduction - 0.0).abs() < EPSILON);
assert!((debuffs.def_reduction - 0.0).abs() < EPSILON);
}
#[test]
fn test_collect_enemy_debuffs_all_elements() {
let buffs = vec![
ResolvedBuff {
source: "Citlali Q".into(),
stat: BuffableStat::ElementalResReduction(Element::Pyro),
value: 0.20,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Citlali Q".into(),
stat: BuffableStat::ElementalResReduction(Element::Cryo),
value: 0.20,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "VV".into(),
stat: BuffableStat::ElementalResReduction(Element::Hydro),
value: 0.40,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Superconduct".into(),
stat: BuffableStat::PhysicalResReduction,
value: 0.40,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Lisa A4".into(),
stat: BuffableStat::DefReduction,
value: 0.15,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Bennett".into(),
stat: BuffableStat::AtkFlat,
value: 1000.0,
target: BuffTarget::Team,
origin: None,
},
];
let debuffs = collect_enemy_debuffs(&buffs);
assert!((debuffs.pyro_res_reduction - 0.20).abs() < EPSILON);
assert!((debuffs.cryo_res_reduction - 0.20).abs() < EPSILON);
assert!((debuffs.hydro_res_reduction - 0.40).abs() < EPSILON);
assert!((debuffs.electro_res_reduction - 0.0).abs() < EPSILON);
assert!((debuffs.dendro_res_reduction - 0.0).abs() < EPSILON);
assert!((debuffs.anemo_res_reduction - 0.0).abs() < EPSILON);
assert!((debuffs.geo_res_reduction - 0.0).abs() < EPSILON);
assert!((debuffs.physical_res_reduction - 0.40).abs() < EPSILON);
assert!((debuffs.def_reduction - 0.15).abs() < EPSILON);
}
#[test]
fn test_collect_enemy_debuffs_stacks() {
let buffs = vec![
ResolvedBuff {
source: "VV".into(),
stat: BuffableStat::ElementalResReduction(Element::Pyro),
value: 0.40,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Zhongli".into(),
stat: BuffableStat::ElementalResReduction(Element::Pyro),
value: 0.20,
target: BuffTarget::Team,
origin: None,
},
];
let debuffs = collect_enemy_debuffs(&buffs);
assert!((debuffs.pyro_res_reduction - 0.60).abs() < EPSILON);
}
#[test]
fn test_apply_debuffs_to_enemy_pyro() {
let enemy = base_enemy();
let debuffs = EnemyDebuffs {
pyro_res_reduction: 0.40,
..Default::default()
};
let result = apply_debuffs_to_enemy(&enemy, &debuffs, Some(Element::Pyro));
assert!((result.resistance - (-0.30)).abs() < EPSILON);
assert!((result.def_reduction - 0.0).abs() < EPSILON);
}
#[test]
fn test_apply_debuffs_to_enemy_physical() {
let enemy = base_enemy();
let debuffs = EnemyDebuffs {
physical_res_reduction: 0.40,
..Default::default()
};
let result = apply_debuffs_to_enemy(&enemy, &debuffs, None);
assert!((result.resistance - (-0.30)).abs() < EPSILON);
}
#[test]
fn test_apply_debuffs_to_enemy_wrong_element_no_effect() {
let enemy = base_enemy();
let debuffs = EnemyDebuffs {
pyro_res_reduction: 0.40,
..Default::default()
};
let result = apply_debuffs_to_enemy(&enemy, &debuffs, Some(Element::Cryo));
assert!((result.resistance - 0.10).abs() < EPSILON);
}
#[test]
fn test_apply_debuffs_to_enemy_def_reduction_clamped() {
let enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.80,
def_ignore: 0.0,
};
let debuffs = EnemyDebuffs {
def_reduction: 0.50,
..Default::default()
};
let result = apply_debuffs_to_enemy(&enemy, &debuffs, Some(Element::Pyro));
assert!((result.def_reduction - 1.0).abs() < EPSILON);
}
#[test]
fn test_apply_debuffs_to_enemy_combined() {
let enemy = base_enemy();
let debuffs = EnemyDebuffs {
pyro_res_reduction: 0.40,
cryo_res_reduction: 0.20,
def_reduction: 0.15,
..Default::default()
};
let result = apply_debuffs_to_enemy(&enemy, &debuffs, Some(Element::Pyro));
assert!((result.resistance - (-0.30)).abs() < EPSILON);
assert!((result.def_reduction - 0.15).abs() < EPSILON);
}
#[test]
fn test_full_pipeline_resolve_debuffs_damage() {
use crate::damage::{DamageInput, calculate_damage};
use crate::stat_profile::StatProfile;
use crate::stats::Stats;
use crate::team::{BuffTarget, ResolvedBuff, TeamMember, resolve_team_stats};
use crate::types::{DamageType, ScalingStat, WeaponType};
let dps = TeamMember {
element: Element::Pyro,
weapon_type: WeaponType::Claymore,
stats: StatProfile {
base_atk: 900.0,
crit_rate: 0.70,
crit_dmg: 1.50,
energy_recharge: 1.0,
..Default::default()
},
buffs_provided: vec![],
is_moonsign: false,
can_nightsoul: false,
};
let support = TeamMember {
element: Element::Cryo,
weapon_type: WeaponType::Catalyst,
stats: StatProfile {
base_atk: 600.0,
energy_recharge: 1.0,
..Default::default()
},
buffs_provided: vec![
ResolvedBuff {
source: "Bennett Burst".into(),
stat: BuffableStat::AtkFlat,
value: 1000.0,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "VV Pyro".into(),
stat: BuffableStat::ElementalResReduction(Element::Pyro),
value: 0.40,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Shenhe E".into(),
stat: BuffableStat::NormalAtkFlatDmg,
value: 2500.0,
target: BuffTarget::Team,
origin: None,
},
ResolvedBuff {
source: "Freedom-Sworn".into(),
stat: BuffableStat::NormalAtkDmgBonus,
value: 0.16,
target: BuffTarget::Team,
origin: None,
},
],
is_moonsign: false,
can_nightsoul: false,
};
let team = vec![dps, support];
let result = resolve_team_stats(&team, 0, &[]).unwrap();
assert!((result.final_stats.atk - 1900.0).abs() < EPSILON);
assert!((result.damage_context.normal_atk_dmg_bonus - 0.16).abs() < EPSILON);
assert!((result.damage_context.normal_atk_flat_dmg - 2500.0).abs() < EPSILON);
assert!((result.enemy_debuffs.pyro_res_reduction - 0.40).abs() < EPSILON);
let mut stats = result.final_stats.clone();
stats.dmg_bonus += result.damage_context.normal_atk_dmg_bonus;
let input = DamageInput {
character_level: 90,
stats,
talent_multiplier: 1.76,
scaling_stat: ScalingStat::Atk,
damage_type: DamageType::Normal,
element: Some(Element::Pyro),
reaction: None,
reaction_bonus: 0.0,
flat_dmg: result.damage_context.normal_atk_flat_dmg,
};
let base_enemy = Enemy {
level: 90,
resistance: 0.10,
def_reduction: 0.0,
def_ignore: 0.0,
};
let debuffed_enemy =
apply_debuffs_to_enemy(&base_enemy, &result.enemy_debuffs, Some(Element::Pyro));
assert!((debuffed_enemy.resistance - (-0.30)).abs() < EPSILON);
let result_full = calculate_damage(&input, &debuffed_enemy).unwrap();
let baseline_input = DamageInput {
character_level: 90,
stats: Stats {
atk: 900.0,
crit_rate: 0.70,
crit_dmg: 1.50,
..Default::default()
},
talent_multiplier: 1.76,
scaling_stat: ScalingStat::Atk,
damage_type: DamageType::Normal,
element: Some(Element::Pyro),
reaction: None,
reaction_bonus: 0.0,
flat_dmg: 0.0,
};
let _result_baseline = calculate_damage(&baseline_input, &base_enemy).unwrap();
let expected_non_crit = 3897.948;
assert!((result_full.non_crit - expected_non_crit).abs() < 0.01);
assert!(result_full.non_crit > 0.0);
assert!(result_full.crit > result_full.non_crit);
}
}