Skip to main content

wowlab_engine_combat/builder/aura_builder/effects/
combat.rs

1use super::{
2    AbsorbEffect, AbsorbKind, ActiveSpellGate, AuraDefinitionDraft, AuraSubtypeKind, BuffEffect,
3    BuilderError, DamageTransfer, DamageTransferTiming, EffectLookup, HUNDRED, SpellSchoolMask,
4    aura_subtype_is, aura_subtype_is_any, effect_parts, health_threshold,
5};
6
7/// Semantics the static passive pass never folds; applied even for folded passives.
8fn apply_non_passive_damage_effects(
9    mut aura: AuraDefinitionDraft,
10    lookup: EffectLookup<'_>,
11) -> Result<AuraDefinitionDraft, BuilderError> {
12    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
13    let subtype = data.effect_aura(idx, effect_index);
14    let value = data.base_points(idx, effect_index);
15
16    if aura_subtype_is(subtype, AuraSubtypeKind::ModAutoAttackDamageTakenFromCaster) {
17        aura = aura.push_effect(BuffEffect::AutoAttackDamageTaken(value))?;
18    }
19
20    if aura_subtype_is(subtype, AuraSubtypeKind::IgnoreArmorPercent) {
21        aura = aura.push_effect(BuffEffect::ArmorBypassFromCaster(value))?;
22    }
23
24    if aura_subtype_is(subtype, AuraSubtypeKind::OverrideAutoAttackWithAbility) {
25        let replacement = data.trigger_spell(idx, effect_index).ok_or(
26            crate::builder::BuilderErrorKind::MissingSpellData {
27                spell_id,
28                field: "override_auto_attack.trigger_spell",
29            },
30        )?;
31
32        aura = aura.push_effect(BuffEffect::AutoAttackReplacement(replacement.as_u32()))?;
33    }
34
35    Ok(aura)
36}
37
38fn apply_non_passive_school_effects(
39    mut aura: AuraDefinitionDraft,
40    lookup: EffectLookup<'_>,
41) -> Result<AuraDefinitionDraft, BuilderError> {
42    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
43    let subtype = data.effect_aura(idx, effect_index);
44    let property = data.effect_misc_value_0(idx, effect_index);
45
46    if aura_subtype_is(subtype, AuraSubtypeKind::ModSpellSchool) {
47        let school_mask = SpellSchoolMask::from_dbc(property);
48
49        if school_mask.is_empty() {
50            return Err(crate::builder::BuilderErrorKind::MissingSpellData {
51                spell_id,
52                field: "mod_spell_school.school_mask",
53            }
54            .into());
55        }
56
57        aura = aura.push_effect(BuffEffect::SpellSchoolFromEffect {
58            school: school_mask.primary_school(),
59            source_spell_id: spell_id,
60            effect_index,
61        })?;
62    }
63
64    if aura_subtype_is(subtype, AuraSubtypeKind::ModDamageDonePercent) {
65        aura = aura.push_effect(BuffEffect::DamageMultSchool(
66            1.0 + data.base_points(idx, effect_index) / HUNDRED,
67            SpellSchoolMask::from_dbc(property).primary_school(),
68        ))?;
69    }
70
71    aura = apply_incoming_damage_effects(aura, lookup)?;
72    aura = apply_healing_and_absorb_effects(aura, lookup)?;
73    aura = apply_immunity_effects(aura, lookup)?;
74    aura = apply_regen_effects(aura, lookup)?;
75    aura = apply_spell_gate_effects(aura, lookup)?;
76
77    Ok(aura)
78}
79
80// #t(fn: rust_cyclomatic_complexity) one ordered DBC subtype dispatch keeps each healing and absorb mapping visible
81
82fn apply_healing_and_absorb_effects(
83    mut aura: AuraDefinitionDraft,
84    lookup: EffectLookup<'_>,
85) -> Result<AuraDefinitionDraft, BuilderError> {
86    let (data, idx, _, effect_index) = effect_parts(lookup);
87    let subtype = data.effect_aura(idx, effect_index);
88    let value = data.base_points(idx, effect_index);
89    let schools = SpellSchoolMask::from_dbc(data.effect_misc_value_0(idx, effect_index));
90
91    let effect = if aura_subtype_is_any(
92        subtype,
93        &[
94            AuraSubtypeKind::ModHealingFlat,
95            AuraSubtypeKind::ModHealingDoneFlat,
96        ],
97    ) {
98        Some(BuffEffect::HealingDoneFlat {
99            amount: value,
100            schools,
101        })
102    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModHealingDonePercent) {
103        Some(BuffEffect::HealingDonePercent(value))
104    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModHealingReceivedPercent) {
105        Some(BuffEffect::HealingReceivedPercent(value))
106    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModHealingTakenFromCasterPercent) {
107        Some(BuffEffect::HealingReceivedFromCasterPercent(value))
108    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModPeriodicHealingPercent) {
109        Some(BuffEffect::PeriodicHealingPercent(value))
110    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModLeechPercent) {
111        Some(BuffEffect::LeechPercent(value))
112    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModAbsorbDonePercent) {
113        Some(BuffEffect::AbsorbDonePercent(value))
114    } else if aura_subtype_is(subtype, AuraSubtypeKind::ModAbsorbReceivedPercent) {
115        Some(BuffEffect::AbsorbTakenPercent(value))
116    } else if aura_subtype_is_any(
117        subtype,
118        &[
119            AuraSubtypeKind::AbsorbDamage,
120            AuraSubtypeKind::AbsorbHealing,
121            AuraSubtypeKind::AbsorbOverkillDamageFromSchool,
122        ],
123    ) {
124        let kind = if aura_subtype_is(subtype, AuraSubtypeKind::AbsorbHealing) {
125            AbsorbKind::Healing
126        } else if aura_subtype_is(subtype, AuraSubtypeKind::AbsorbOverkillDamageFromSchool) {
127            AbsorbKind::Overkill
128        } else {
129            AbsorbKind::Damage
130        };
131
132        Some(BuffEffect::Absorb(AbsorbEffect {
133            amount: value.max(0.0),
134            effect_index,
135            schools,
136            kind,
137            high_priority: false,
138            overkill_script: None,
139            priority: data.effect_misc_value_1(idx, effect_index),
140        }))
141    } else if aura_subtype_is_any(
142        subtype,
143        &[
144            AuraSubtypeKind::TransferDamagePercent,
145            AuraSubtypeKind::ShareDamagePercent,
146        ],
147    ) {
148        Some(BuffEffect::DamageTransfer(DamageTransfer {
149            fraction: value / HUNDRED,
150            schools,
151            timing: if aura_subtype_is(subtype, AuraSubtypeKind::TransferDamagePercent) {
152                DamageTransferTiming::PreMitigationSplit
153            } else {
154                DamageTransferTiming::PostMitigationShare
155            },
156        }))
157    } else {
158        None
159    };
160
161    if let Some(effect) = effect {
162        aura = aura.push_effect(effect)?;
163    }
164
165    Ok(aura)
166}
167
168fn apply_incoming_damage_effects(
169    mut aura: AuraDefinitionDraft,
170    lookup: EffectLookup<'_>,
171) -> Result<AuraDefinitionDraft, BuilderError> {
172    let (data, idx, _, effect_index) = effect_parts(lookup);
173    let subtype = data.effect_aura(idx, effect_index);
174    let property = data.effect_misc_value_0(idx, effect_index);
175    let school_mask = SpellSchoolMask::from_dbc(property);
176
177    if aura_subtype_is(subtype, AuraSubtypeKind::ModDamageTakenPercent) {
178        aura = aura.push_effect(BuffEffect::IncomingDamagePercent {
179            percent: data.base_points(idx, effect_index),
180            schools: school_mask,
181        })?;
182    }
183
184    if aura_subtype_is(subtype, AuraSubtypeKind::ModAoeDamageTakenPercent) {
185        aura = aura.push_effect(BuffEffect::IncomingAoeDamagePercent {
186            percent: data.base_points(idx, effect_index),
187            schools: school_mask,
188        })?;
189    }
190
191    if aura_subtype_is(
192        subtype,
193        AuraSubtypeKind::ModDamagePercentDoneByTargetAuraMechanic,
194    ) {
195        aura = aura.push_effect(BuffEffect::DamageToTargetAuraMechanicPercent {
196            percent: data.base_points(idx, effect_index),
197            mechanic_mask: wowlab_engine_domain::dbc::Mechanic::from_dbc(property).map_or(
198                wowlab_engine_domain::dbc::MechanicMask::empty(),
199                wowlab_engine_domain::dbc::Mechanic::mask,
200            ),
201        })?;
202    }
203
204    if aura_subtype_is(subtype, AuraSubtypeKind::ModMechanicDamageTakenPercent) {
205        aura = aura.push_effect(BuffEffect::MechanicDamageTakenPercent {
206            percent: data.base_points(idx, effect_index),
207            mechanic_mask: wowlab_engine_domain::dbc::Mechanic::from_dbc(property).map_or(
208                wowlab_engine_domain::dbc::MechanicMask::empty(),
209                wowlab_engine_domain::dbc::Mechanic::mask,
210            ),
211        })?;
212    }
213
214    if aura_subtype_is(subtype, AuraSubtypeKind::ModCreatureAoeDamageAvoidance) {
215        aura = aura.push_effect(BuffEffect::CreatureAoeDamageAvoidance {
216            percent: data.base_points(idx, effect_index),
217            schools: school_mask,
218        })?;
219    }
220
221    Ok(aura)
222}
223
224fn apply_regen_effects(
225    mut aura: AuraDefinitionDraft,
226    lookup: EffectLookup<'_>,
227) -> Result<AuraDefinitionDraft, BuilderError> {
228    let (data, idx, _, effect_index) = effect_parts(lookup);
229    let subtype = data.effect_aura(idx, effect_index);
230    let property = data.effect_misc_value_0(idx, effect_index);
231
232    if aura_subtype_is(subtype, AuraSubtypeKind::InterruptRegen) {
233        aura = aura.push_effect(BuffEffect::InterruptRegen)?;
234    }
235
236    if aura_subtype_is(subtype, AuraSubtypeKind::ModManaRegenInterrupted) {
237        aura = aura.push_effect(BuffEffect::InterruptedRegenPercent(
238            data.base_points(idx, effect_index),
239        ))?;
240    }
241
242    if aura_subtype_is(subtype, AuraSubtypeKind::PreventRegeneratePower) {
243        aura = aura.push_effect(BuffEffect::PreventResourceRegen(u64::from(
244            u32::from_ne_bytes(property.to_ne_bytes()),
245        )))?;
246    }
247
248    Ok(aura)
249}
250
251fn apply_immunity_effects(
252    mut aura: AuraDefinitionDraft,
253    lookup: EffectLookup<'_>,
254) -> Result<AuraDefinitionDraft, BuilderError> {
255    let (data, idx, _, effect_index) = effect_parts(lookup);
256    let subtype = data.effect_aura(idx, effect_index);
257    let property = data.effect_misc_value_0(idx, effect_index);
258
259    if aura_subtype_is(subtype, AuraSubtypeKind::SchoolImmunity) {
260        aura = aura.push_effect(BuffEffect::SchoolImmunity(SpellSchoolMask::from_dbc(
261            property,
262        )))?;
263    }
264
265    if aura_subtype_is(subtype, AuraSubtypeKind::MechanicImmunity) {
266        let mechanic = u32::try_from(property)
267            .ok()
268            .and_then(|mechanic| 1_u64.checked_shl(mechanic))
269            .unwrap_or(0);
270
271        aura = aura.push_effect(BuffEffect::MechanicImmunity(mechanic))?;
272    }
273
274    if aura_subtype_is(subtype, AuraSubtypeKind::CreatureImmunities) {
275        aura = aura.push_effect(BuffEffect::MechanicImmunity(u64::from(u32::from_ne_bytes(
276            property.to_ne_bytes(),
277        ))))?;
278    }
279
280    Ok(aura)
281}
282
283fn apply_spell_gate_effects(
284    mut aura: AuraDefinitionDraft,
285    lookup: EffectLookup<'_>,
286) -> Result<AuraDefinitionDraft, BuilderError> {
287    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
288    let subtype = data.effect_aura(idx, effect_index);
289
290    if aura_subtype_is(subtype, AuraSubtypeKind::ModSpellHastePercent) {
291        aura = aura.push_effect(BuffEffect::SpellCastSpeed(
292            data.base_points(idx, effect_index),
293        ))?;
294    }
295
296    let gate = if aura_subtype_is(subtype, AuraSubtypeKind::IgnoreAuraState) {
297        Some(ActiveSpellGate::IgnoreAuraState)
298    } else if aura_subtype_is(subtype, AuraSubtypeKind::DisableAbilities) {
299        Some(ActiveSpellGate::DisableAbilities)
300    } else {
301        None
302    };
303
304    if let Some(gate) = gate {
305        aura = aura.push_effect(BuffEffect::SpellGateFromEffect {
306            source_spell_id: spell_id,
307            effect_index,
308            gate,
309        })?;
310    }
311
312    if aura_subtype_is(subtype, AuraSubtypeKind::ModStanceMask) {
313        aura = aura.push_effect(BuffEffect::SpellStanceMaskFromEffect {
314            source_spell_id: spell_id,
315            effect_index,
316        })?;
317    }
318
319    Ok(aura)
320}
321
322/// Semantics the static passive pass never folds; applied even for folded passives.
323pub(in crate::builder::aura_builder) fn apply_non_passive_effect(
324    aura: AuraDefinitionDraft,
325    lookup: EffectLookup<'_>,
326) -> Result<AuraDefinitionDraft, BuilderError> {
327    let aura = apply_non_passive_damage_effects(aura, lookup)?;
328    let aura = apply_non_passive_stat_effects(aura, lookup)?;
329    let aura = health_threshold::apply_from_data(aura, lookup)?;
330
331    apply_non_passive_school_effects(aura, lookup)
332}
333
334/// Applies runtime state, such as aura 319/342, that the static passive pass cannot represent.
335fn apply_non_passive_stat_effects(
336    mut aura: AuraDefinitionDraft,
337    lookup: EffectLookup<'_>,
338) -> Result<AuraDefinitionDraft, BuilderError> {
339    let (data, idx, _, effect_index) = effect_parts(lookup);
340
341    if aura_subtype_is_any(
342        data.effect_aura(idx, effect_index),
343        &[
344            AuraSubtypeKind::ModMeleeAutoAttackSpeedPercent,
345            AuraSubtypeKind::ModRangedAndMeleeAutoAttackSpeedPercent,
346        ],
347    ) {
348        aura = aura.push_effect(BuffEffect::AttackSpeed(data.base_points(idx, effect_index)))?;
349    }
350
351    Ok(aura)
352}