Skip to main content

wowlab_engine_combat/builder/aura_builder/effects/
periodic.rs

1use super::{
2    AuraDefinitionDraft, AuraSubtypeKind, BuilderError, DamageFlags, EffectLookup, HUNDRED,
3    PeriodicDef, PeriodicEffectDef, aura_subtype_is, aura_subtype_is_any, effect_parts,
4};
5
6fn apply_periodic_energize_from_data(
7    aura: AuraDefinitionDraft,
8    lookup: EffectLookup<'_>,
9) -> Result<AuraDefinitionDraft, BuilderError> {
10    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
11    let power_type = data.effect_misc_value_0(idx, effect_index);
12    let resource_type = u8::try_from(power_type)
13        .ok()
14        .and_then(|value| wowlab_types::combat::ResourceType::try_from(value).ok())
15        .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
16            spell_id,
17            field: "periodic_energize.resource_type",
18        })?;
19    let tick_ms =
20        data.aura_tick_ms(idx)
21            .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
22                spell_id,
23                field: "periodic_energize.tick_ms",
24            })?;
25    let amount = data.scaled_power_amount(power_type, data.base_points(idx, effect_index));
26
27    let mut aura = aura.periodic_resource_type(tick_ms, amount, resource_type);
28
29    if let Some(periodic) = &mut aura.periodic {
30        periodic.effect_ref = Some(crate::state::DamageEffectRef::new(spell_id, effect_index));
31    }
32
33    Ok(aura)
34}
35
36fn apply_periodic_damage_from_data(
37    aura: AuraDefinitionDraft,
38    lookup: EffectLookup<'_>,
39) -> Result<AuraDefinitionDraft, BuilderError> {
40    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
41    let effect_period = data.period(idx, effect_index);
42    let tick_ms = if effect_period > 0.0 {
43        crate::validated_milliseconds(effect_period, spell_id, "periodic_damage.tick_ms")?
44    } else {
45        data.aura_tick_ms(idx).filter(|period| *period > 0).ok_or(
46            crate::builder::BuilderErrorKind::MissingSpellData {
47                spell_id,
48                field: "periodic_damage.tick_ms",
49            },
50        )?
51    };
52    let is_physical = wowlab_engine_domain::dbc::is_physical(data, idx).ok_or(
53        crate::builder::BuilderErrorKind::MissingSpellData {
54            spell_id,
55            field: "periodic_damage.is_physical",
56        },
57    )?;
58    let flags = if is_physical {
59        DamageFlags::PHYSICAL
60    } else {
61        DamageFlags::empty()
62    };
63    let ap_coef = data.ap_coef(idx, effect_index);
64    let sp_coef = data.sp_coef(idx, effect_index);
65    let mut aura = if ap_coef.abs() > f64::EPSILON {
66        aura.periodic_damage_from_spell(
67            tick_ms,
68            crate::DamagePayload::new(spell_id, ap_coef, flags),
69        )
70    } else if sp_coef.abs() > f64::EPSILON {
71        aura.periodic_damage_sp_from_spell(
72            tick_ms,
73            crate::DamagePayload::new(spell_id, sp_coef, flags),
74        )
75    } else {
76        aura.periodic_flat_damage_from_spell(
77            tick_ms,
78            crate::DamagePayload::new(spell_id, data.base_points(idx, effect_index), flags),
79        )
80    };
81
82    if let Some(periodic) = &mut aura.periodic {
83        periodic.effect_ref = Some(crate::state::DamageEffectRef::new(spell_id, effect_index));
84    }
85
86    aura.apply_periodic_behavior_from_data(data, spell_id)
87}
88
89fn apply_periodic_trigger_from_data(
90    mut aura: AuraDefinitionDraft,
91    lookup: EffectLookup<'_>,
92) -> Result<AuraDefinitionDraft, BuilderError> {
93    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
94
95    if data.trigger_spell(idx, effect_index).is_none() {
96        return Ok(aura);
97    }
98
99    let tick_ms =
100        data.aura_tick_ms(idx)
101            .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
102                spell_id,
103                field: "periodic_trigger_spell.tick_ms",
104            })?;
105
106    aura.periodic = Some(PeriodicDef::new(
107        tick_ms,
108        data.hasted_ticks(idx).unwrap_or(false),
109        PeriodicEffectDef::Hook,
110    ));
111
112    aura.apply_periodic_behavior_from_data(data, spell_id)
113}
114
115fn apply_periodic_leech_from_data(
116    mut aura: AuraDefinitionDraft,
117    lookup: EffectLookup<'_>,
118) -> Result<AuraDefinitionDraft, BuilderError> {
119    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
120    let tick_ms =
121        data.aura_tick_ms(idx)
122            .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
123                spell_id,
124                field: "periodic_leech.tick_ms",
125            })?;
126    let is_physical = wowlab_engine_domain::dbc::is_physical(data, idx).ok_or(
127        crate::builder::BuilderErrorKind::MissingSpellData {
128            spell_id,
129            field: "periodic_leech.is_physical",
130        },
131    )?;
132    let ap_coef = data.ap_coef(idx, effect_index);
133    let sp_coef = data.sp_coef(idx, effect_index);
134    let base = data.base_points(idx, effect_index).max(0.0);
135
136    if base <= f64::EPSILON && ap_coef.abs() <= f64::EPSILON && sp_coef.abs() <= f64::EPSILON {
137        return Err(crate::builder::BuilderErrorKind::MissingSpellData {
138            spell_id,
139            field: "periodic_leech.payload",
140        }
141        .into());
142    }
143
144    aura.periodic = Some(PeriodicDef::new(
145        tick_ms,
146        data.hasted_ticks(idx).unwrap_or(false),
147        PeriodicEffectDef::Leech {
148            base,
149            ap_coef,
150            sp_coef,
151            is_physical,
152            heal_multiplier: data.amplitude(idx, effect_index).max(0.0),
153        },
154    ));
155
156    if let Some(periodic) = &mut aura.periodic {
157        periodic.damage_spell_id = Some(spell_id);
158        periodic.effect_ref = Some(crate::state::DamageEffectRef::new(spell_id, effect_index));
159    }
160
161    Ok(aura)
162}
163
164fn apply_periodic_heal_from_data(
165    mut aura: AuraDefinitionDraft,
166    lookup: EffectLookup<'_>,
167) -> Result<AuraDefinitionDraft, BuilderError> {
168    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
169    let tick_ms =
170        data.aura_tick_ms(idx)
171            .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
172                spell_id,
173                field: "periodic_heal.tick_ms",
174            })?;
175    let subtype = data.effect_aura(idx, effect_index);
176    let percent_of_max = if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicHealPercent) {
177        data.base_points(idx, effect_index) / HUNDRED
178    } else {
179        0.0
180    };
181    let base = if percent_of_max > 0.0 {
182        0.0
183    } else {
184        data.base_points(idx, effect_index).max(0.0)
185    };
186
187    aura.periodic = Some(PeriodicDef::new(
188        tick_ms,
189        data.hasted_ticks(idx).unwrap_or(false),
190        PeriodicEffectDef::Heal {
191            base,
192            ap_coef: data.ap_coef(idx, effect_index),
193            sp_coef: data.sp_coef(idx, effect_index),
194            percent_of_max,
195        },
196    ));
197
198    if let Some(periodic) = &mut aura.periodic {
199        periodic.effect_ref = Some(crate::state::DamageEffectRef::new(spell_id, effect_index));
200    }
201
202    Ok(aura)
203}
204
205fn apply_periodic_max_health_damage_from_data(
206    aura: AuraDefinitionDraft,
207    lookup: EffectLookup<'_>,
208) -> Result<AuraDefinitionDraft, BuilderError> {
209    let (data, idx, spell_id, effect_index) = effect_parts(lookup);
210    let tick_ms =
211        data.aura_tick_ms(idx)
212            .ok_or(crate::builder::BuilderErrorKind::MissingSpellData {
213                spell_id,
214                field: "periodic_max_health_damage.tick_ms",
215            })?;
216    let is_physical = wowlab_engine_domain::dbc::is_physical(data, idx).ok_or(
217        crate::builder::BuilderErrorKind::MissingSpellData {
218            spell_id,
219            field: "periodic_max_health_damage.is_physical",
220        },
221    )?;
222
223    let mut aura =
224        aura.periodic_max_health_damage(tick_ms, data.base_points(idx, effect_index), is_physical);
225
226    if let Some(periodic) = &mut aura.periodic {
227        periodic.effect_ref = Some(crate::state::DamageEffectRef::new(spell_id, effect_index));
228    }
229
230    Ok(aura)
231}
232
233pub(in crate::builder::aura_builder) fn apply_automatic_periodic_effect(
234    aura: AuraDefinitionDraft,
235    lookup: EffectLookup<'_>,
236) -> Result<AuraDefinitionDraft, BuilderError> {
237    if aura.periodic.is_some() {
238        return Ok(aura);
239    }
240
241    let subtype = lookup.effect_aura();
242
243    if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicDamage) {
244        apply_periodic_damage_from_data(aura, lookup)
245    } else if aura_subtype_is_any(
246        subtype,
247        &[
248            AuraSubtypeKind::PeriodicHeal,
249            AuraSubtypeKind::PeriodicHealPercent,
250        ],
251    ) {
252        apply_periodic_heal_from_data(aura, lookup)
253    } else if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicEnergize) {
254        apply_periodic_energize_from_data(aura, lookup)
255    } else if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicTriggerSpell) {
256        apply_periodic_trigger_from_data(aura, lookup)
257    } else if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicLeech) {
258        apply_periodic_leech_from_data(aura, lookup)
259    } else if aura_subtype_is(subtype, AuraSubtypeKind::PeriodicMaxHealthDamage) {
260        apply_periodic_max_health_damage_from_data(aura, lookup)
261    } else {
262        Ok(aura)
263    }
264}