Skip to main content

wowlab_engine_domain/dbc/game_data/
damage_def.rs

1use 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/// Returns the first AP/SP damage effect or a zero-valued flat fallback.
233#[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}