wowlab_engine_combat/builder/aura_builder/effects/
periodic.rs1use 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}