wowlab_engine_domain/dbc/game_data/
damage_def.rs1use wowlab_engine_gamedata::{
2 ResolvedDamageDef, ResolvedDamageKind, ResolvedDamageWeapon, ResolvedGameData,
3};
4use wowlab_types::{
5 constants::HUNDRED,
6 sim::{EffectRef, SpellIdx},
7};
8
9use super::{super::EffectLookup, damage::is_physical};
10
11const WEAPON_DAMAGE_EFFECTS: &[super::super::SpellEffectKind] = &[
12 super::super::SpellEffectKind::WeaponDamageNoSchool,
13 super::super::SpellEffectKind::WeaponPercentDamage,
14 super::super::SpellEffectKind::WeaponDamage,
15 super::super::SpellEffectKind::NormalizedWeaponDamage,
16];
17
18pub(super) fn damage_def_at(
19 lookup: EffectLookup<'_>,
20 forwarded_value: Option<f64>,
21) -> Option<ResolvedDamageDef> {
22 let data = lookup.data;
23 let EffectRef {
24 spell: spell_id,
25 effect_index,
26 } = lookup.effect;
27
28 if super::super::spell_effect_is_any(lookup.effect_type(), WEAPON_DAMAGE_EFFECTS) {
29 let def = weapon_damage_def(data, spell_id, forwarded_value)?;
30
31 return (def.effect_index == effect_index).then_some(def);
32 }
33
34 if !super::super::spell_effect_is_any(
35 lookup.effect_type(),
36 &[
37 super::super::SpellEffectKind::SchoolDamage,
38 super::super::SpellEffectKind::HealthLeech,
39 ],
40 ) {
41 return None;
42 }
43
44 let is_physical = is_physical(data, spell_id)?;
45 let weapon = if data.requires_off_hand(spell_id).unwrap_or(false) {
46 ResolvedDamageWeapon::OffHand
47 } else {
48 ResolvedDamageWeapon::MainHand
49 };
50 let ap = lookup.ap_coef();
51 let sp = lookup.sp_coef();
52 let base_points = forwarded_value.unwrap_or_else(|| lookup.base_points());
53 let (kind, coefficient) = if ap > 0.0 {
54 (ResolvedDamageKind::Ap, ap)
55 } else if sp > 0.0 {
56 (ResolvedDamageKind::Sp, sp)
57 } else {
58 (ResolvedDamageKind::Flat, 0.0)
59 };
60
61 Some(ResolvedDamageDef {
62 kind,
63 coefficient,
64 base_points,
65 is_physical,
66 effect_index,
67 weapon,
68 })
69}
70
71fn weapon_damage_def(
72 data: &ResolvedGameData,
73 spell_id: SpellIdx,
74 forwarded_value: Option<f64>,
75) -> Option<ResolvedDamageDef> {
76 let mut first_effect = None;
77 let mut flat_bonus = 0.0;
78 let mut multiplier = None;
79 let mut normalized = false;
80 let mut has_non_percent = false;
81
82 for effect_index in 1..=data.max_effect_index(spell_id) {
83 let kind = super::super::spell_effect_kind(data.effect_type(spell_id, effect_index));
84
85 match kind {
86 Some(
87 super::super::SpellEffectKind::WeaponDamageNoSchool
88 | super::super::SpellEffectKind::WeaponDamage,
89 ) => {
90 first_effect.get_or_insert(effect_index);
91 has_non_percent = true;
92 flat_bonus +=
93 forwarded_value.unwrap_or_else(|| data.base_points(spell_id, effect_index));
94 }
95 Some(super::super::SpellEffectKind::WeaponPercentDamage) => {
96 first_effect.get_or_insert(effect_index);
97 multiplier = Some(
98 forwarded_value.unwrap_or_else(|| data.base_points(spell_id, effect_index))
99 / HUNDRED,
100 );
101 }
102 Some(super::super::SpellEffectKind::NormalizedWeaponDamage) => {
103 first_effect.get_or_insert(effect_index);
104 has_non_percent = true;
105 normalized = true;
106 flat_bonus +=
107 forwarded_value.unwrap_or_else(|| data.base_points(spell_id, effect_index));
108 }
109 _ => {}
110 }
111 }
112
113 let effect_index = first_effect?;
114 let kind = if normalized {
115 ResolvedDamageKind::NormalizedWeapon
116 } else if has_non_percent {
117 ResolvedDamageKind::Weapon
118 } else {
119 ResolvedDamageKind::WeaponPercent
120 };
121
122 Some(ResolvedDamageDef {
123 kind,
124 coefficient: multiplier.unwrap_or(1.0),
125 base_points: flat_bonus,
126 is_physical: is_physical(data, spell_id)?,
127 effect_index,
128 weapon: if data.requires_off_hand(spell_id).unwrap_or(false) {
129 ResolvedDamageWeapon::OffHand
130 } else {
131 ResolvedDamageWeapon::MainHand
132 },
133 })
134}
135
136fn coefficient_damage_def(
137 data: &ResolvedGameData,
138 spell_id: SpellIdx,
139 is_physical: bool,
140 weapon: ResolvedDamageWeapon,
141) -> Option<ResolvedDamageDef> {
142 const DAMAGE_KIND_TIERS: [&[super::super::SpellEffectKind]; 2] = [
143 &[
144 super::super::SpellEffectKind::SchoolDamage,
145 super::super::SpellEffectKind::HealthLeech,
146 ],
147 &[
148 super::super::SpellEffectKind::TriggerMissile,
149 super::super::SpellEffectKind::TriggerSpell,
150 super::super::SpellEffectKind::TriggerSpellWithValue,
151 super::super::SpellEffectKind::TriggerSpell2,
152 ],
153 ];
154
155 for kinds in DAMAGE_KIND_TIERS {
156 for effect_index in 1u8..=data.max_effect_index(spell_id) {
157 if !super::super::spell_effect_is_any(data.effect_type(spell_id, effect_index), kinds) {
158 continue;
159 }
160
161 let ap = data.ap_coef(spell_id, effect_index);
162 let sp = data.sp_coef(spell_id, effect_index);
163
164 if ap > 0.0 {
165 return Some(ResolvedDamageDef {
166 kind: ResolvedDamageKind::Ap,
167 coefficient: ap,
168 base_points: data.base_points(spell_id, effect_index),
169 is_physical,
170 effect_index,
171 weapon,
172 });
173 }
174
175 if sp > 0.0 {
176 return Some(ResolvedDamageDef {
177 kind: ResolvedDamageKind::Sp,
178 coefficient: sp,
179 base_points: data.base_points(spell_id, effect_index),
180 is_physical,
181 effect_index,
182 weapon,
183 });
184 }
185 }
186 }
187
188 None
189}
190
191pub(super) fn actual_damage_def(
192 data: &ResolvedGameData,
193 spell_id: SpellIdx,
194) -> Option<ResolvedDamageDef> {
195 let is_physical = is_physical(data, spell_id)?;
196 let weapon = if data.requires_off_hand(spell_id).unwrap_or(false) {
197 ResolvedDamageWeapon::OffHand
198 } else {
199 ResolvedDamageWeapon::MainHand
200 };
201
202 if let Some(def) = weapon_damage_def(data, spell_id, None) {
203 return Some(def);
204 }
205
206 if let Some(def) = coefficient_damage_def(data, spell_id, is_physical, weapon) {
207 return Some(def);
208 }
209
210 for effect_index in 1u8..=data.max_effect_index(spell_id) {
211 if super::super::spell_effect_is_any(
212 data.effect_type(spell_id, effect_index),
213 &[
214 super::super::SpellEffectKind::SchoolDamage,
215 super::super::SpellEffectKind::HealthLeech,
216 ],
217 ) {
218 return Some(ResolvedDamageDef {
219 kind: ResolvedDamageKind::Flat,
220 coefficient: 0.0,
221 base_points: data.base_points(spell_id, effect_index),
222 is_physical,
223 effect_index,
224 weapon,
225 });
226 }
227 }
228
229 None
230}
231
232#[must_use]
234pub fn damage_def(data: &ResolvedGameData, spell_id: SpellIdx) -> Option<ResolvedDamageDef> {
235 actual_damage_def(data, spell_id).or_else(|| {
236 let is_physical = is_physical(data, spell_id)?;
237 let weapon = if data.requires_off_hand(spell_id).unwrap_or(false) {
238 ResolvedDamageWeapon::OffHand
239 } else {
240 ResolvedDamageWeapon::MainHand
241 };
242
243 Some(ResolvedDamageDef {
244 kind: ResolvedDamageKind::Flat,
245 coefficient: 0.0,
246 base_points: 0.0,
247 is_physical,
248 effect_index: 1,
249 weapon,
250 })
251 })
252}