wowlab_engine_domain/dbc/passives/
combat.rs1use std::collections::BTreeMap;
2
3use wowlab_types::constants::HUNDRED;
4#[cfg(test)]
5use wowlab_types::data::SpellDataFlat;
6
7use super::{
8 AuraSubtypeKind, PassiveQuery, PetStatKind, RankedPassive, SpellEffectSemanticExt, aura_kind,
9 modified_effect_base_points, pct_to_mult, pet_stat_kind,
10};
11
12#[derive(Clone, Copy, Debug, PartialEq)]
14pub struct PassivePetStatMods {
15 pub npc_id: u32,
16 pub attack_power_mult: f64,
17 pub spell_power_mult: f64,
18}
19
20fn compound_flat_aura_mult(passives: &[RankedPassive], expected: AuraSubtypeKind) -> f64 {
21 let effects = passives.iter().flat_map(|(aura, ranks)| {
22 aura.effects
23 .iter()
24 .map(move |effect| (aura, effect, *ranks))
25 });
26
27 effects
28 .filter(|(_, effect, _)| aura_kind(effect) == Some(expected))
29 .fold(1.0, |mult, (aura, effect, ranks)| {
30 let percent = modified_effect_base_points(
31 PassiveQuery {
32 passives,
33 spell: aura,
34 },
35 effect.index,
36 effect.base_points,
37 );
38
39 mult * pct_to_mult(percent * ranks)
40 })
41}
42
43#[must_use]
45pub fn passive_auto_attack_mult(passives: &[RankedPassive]) -> f64 {
46 compound_flat_aura_mult(passives, AuraSubtypeKind::ModAutoAttackDamage)
47}
48
49#[must_use]
51pub fn passive_pet_damage_mult(passives: &[RankedPassive]) -> f64 {
52 compound_flat_aura_mult(passives, AuraSubtypeKind::ModPetDamageDone)
53}
54
55#[must_use]
57pub fn passive_guardian_damage_mult(passives: &[RankedPassive]) -> f64 {
58 compound_flat_aura_mult(passives, AuraSubtypeKind::ModGuardianDamageDonePercent)
59}
60
61#[must_use]
63pub fn passive_pet_stat_mods(passives: &[RankedPassive]) -> Vec<PassivePetStatMods> {
64 let mut by_npc = BTreeMap::<u32, (f64, f64)>::new();
65
66 for (aura, ranks) in passives {
67 for effect in &aura.effects {
68 if !effect.aura_is(AuraSubtypeKind::ModPetStat) || effect.misc_value_1 <= 0 {
69 continue;
70 }
71
72 let Ok(npc_id) = u32::try_from(effect.misc_value_1) else {
73 continue;
74 };
75 let entry = by_npc.entry(npc_id).or_insert((1.0, 1.0));
76 let mult = pct_to_mult(effect.base_points * ranks);
77
78 match pet_stat_kind(effect.misc_value_0) {
79 Some(PetStatKind::AttackPowerInheritance) => entry.0 *= mult,
80 Some(PetStatKind::SpellPowerInheritance) => entry.1 *= mult,
81 _ => {}
82 }
83 }
84 }
85
86 by_npc
87 .into_iter()
88 .map(
89 |(npc_id, (attack_power_mult, spell_power_mult))| PassivePetStatMods {
90 npc_id,
91 attack_power_mult,
92 spell_power_mult,
93 },
94 )
95 .collect()
96}
97
98#[must_use]
100pub fn passive_target_armor_mult(passives: &[RankedPassive]) -> f64 {
101 let effects = passives
102 .iter()
103 .flat_map(|(aura, ranks)| aura.effects.iter().map(move |effect| (effect, *ranks)));
104 let penetration_pct = effects
105 .filter(|(effect, _)| effect.aura_is(AuraSubtypeKind::ModTargetArmorPercent))
106 .map(|(effect, ranks)| effect.base_points * ranks)
107 .sum::<f64>();
108
109 (1.0 - penetration_pct / HUNDRED).max(0.0)
110}
111
112#[cfg(test)]
113mod tests {
114 use googletest::prelude::*;
115 use wowlab_types::data::{SpellEffect, SpellLabel};
116
117 use super::*;
118
119 #[gtest]
120 fn pet_damage_passive_uses_label_modifier_on_its_effect_value() {
121 let pet_damage = SpellDataFlat {
122 labels: vec![SpellLabel(6324)],
123 is_passive: true,
124 effects: vec![SpellEffect {
125 index: 0,
126 aura: AuraSubtypeKind::ModPetDamageDone as i32,
127 base_points: 0.0,
128 ..Default::default()
129 }],
130 ..Default::default()
131 };
132 let specialization = SpellDataFlat {
133 is_passive: true,
134 effects: vec![SpellEffect {
135 aura: AuraSubtypeKind::AddFlatLabelModifier as i32,
136 base_points: 15.0,
137 misc_value_0: super::super::ModifierPropertyKind::Effect1 as i32,
138 misc_value_1: 6324,
139 ..Default::default()
140 }],
141 ..Default::default()
142 };
143
144 expect_that!(
145 passive_pet_damage_mult(&[(pet_damage, 1.0), (specialization, 1.0)]),
146 eq(1.15)
147 );
148 }
149
150 #[gtest]
151 fn pet_and_guardian_damage_passives_are_resolved_independently() {
152 let passive = SpellDataFlat {
153 is_passive: true,
154 effects: vec![
155 SpellEffect {
156 aura: AuraSubtypeKind::ModPetDamageDone as i32,
157 base_points: 20.0,
158 ..Default::default()
159 },
160 SpellEffect {
161 aura: AuraSubtypeKind::ModGuardianDamageDonePercent as i32,
162 base_points: -10.0,
163 ..Default::default()
164 },
165 ],
166 ..Default::default()
167 };
168 let passives = [(passive, 1.0)];
169
170 expect_that!(passive_pet_damage_mult(&passives), eq(1.2));
171 expect_that!(passive_guardian_damage_mult(&passives), eq(0.9));
172 }
173
174 #[gtest]
175 fn pet_stat_mods_group_ap_and_sp_inheritance_by_npc() {
176 let passive = SpellDataFlat {
177 is_passive: true,
178 effects: vec![
179 SpellEffect {
180 aura: AuraSubtypeKind::ModPetStat as i32,
181 base_points: 10.0,
182 misc_value_0: PetStatKind::AttackPowerInheritance as i32,
183 misc_value_1: 26_125,
184 ..Default::default()
185 },
186 SpellEffect {
187 aura: AuraSubtypeKind::ModPetStat as i32,
188 base_points: 20.0,
189 misc_value_0: PetStatKind::SpellPowerInheritance as i32,
190 misc_value_1: 26_125,
191 ..Default::default()
192 },
193 ],
194 ..Default::default()
195 };
196
197 expect_that!(
198 passive_pet_stat_mods(&[(passive, 1.0)]),
199 eq(&vec![PassivePetStatMods {
200 npc_id: 26_125,
201 attack_power_mult: 1.1,
202 spell_power_mult: 1.2,
203 }])
204 );
205 }
206}