Skip to main content

codegen/
gen_damage.rs

1//! Typed emission of spell and channel-tick damage builder calls.
2
3use anyhow::bail;
4use proc_macro2::TokenStream;
5use quote::quote;
6use wowlab_manifest_schema::ManifestDamageDef;
7
8use crate::{
9    gen_emit::{damage_flags, effect_lookup, spell_idx},
10    rust_source::{lit_f64, lit_int, rust_ident},
11};
12
13fn weapon_ap_type(name: &str) -> anyhow::Result<TokenStream> {
14    Ok(match name {
15        "mainhand" => quote!(wowlab_engine_combat::WeaponApType::MainHand),
16        "offhand" => quote!(wowlab_engine_combat::WeaponApType::OffHand),
17        "both" => quote!(wowlab_engine_combat::WeaponApType::Both),
18        "none" => quote!(wowlab_engine_combat::WeaponApType::None),
19        other => bail!("unknown ap_type: {other}"),
20    })
21}
22
23pub(crate) fn apply_damage(
24    expression: &TokenStream,
25    damage: &ManifestDamageDef,
26) -> anyhow::Result<TokenStream> {
27    Ok(match damage {
28        ManifestDamageDef::EffectRef {
29            spell_id,
30            effect,
31            kind,
32            school: Some(school),
33            ap_type,
34        } => {
35            let spell_id = spell_idx(*spell_id);
36            let effect = lit_int(effect);
37            let flags = damage_flags(*school);
38
39            match (kind.as_str(), ap_type.as_deref()) {
40                ("ap", None) => {
41                    quote!(#expression.damage_ap(data.ap_coef(#spell_id, #effect), #flags))
42                }
43                ("ap", Some(ap_type)) => {
44                    let ap_type = weapon_ap_type(ap_type)?;
45
46                    quote! {
47                        #expression.damage_ap_typed(
48                            data.ap_coef(#spell_id, #effect),
49                            #flags,
50                            #ap_type,
51                        )
52                    }
53                }
54                ("sp", _) => {
55                    quote!(#expression.damage_sp(data.sp_coef(#spell_id, #effect), #flags))
56                }
57                (other, _) => bail!("unknown damage kind: {other}"),
58            }
59        }
60        ManifestDamageDef::EffectRef {
61            spell_id,
62            effect,
63            kind,
64            school: None,
65            ap_type,
66        } => {
67            let lookup = effect_lookup(&quote!(data), *spell_id, *effect);
68
69            match (kind.as_str(), ap_type.as_deref()) {
70                ("ap", None) => quote!(#expression.damage_ap_from_data(#lookup)?),
71                ("ap", Some(ap_type)) => {
72                    let ap_type = weapon_ap_type(ap_type)?;
73
74                    quote!(#expression.damage_ap_from_data_typed(#lookup, #ap_type)?)
75                }
76                ("sp", _) => quote!(#expression.damage_sp_from_data(#lookup)?),
77                (other, _) => bail!("unknown damage kind: {other}"),
78            }
79        }
80        ManifestDamageDef::Ap { ap_coef, school } => {
81            let coefficient = lit_f64(*ap_coef);
82            let flags = damage_flags(*school);
83
84            quote!(#expression.damage_ap(#coefficient, #flags))
85        }
86        ManifestDamageDef::Sp { sp_coef, school } => {
87            let coefficient = lit_f64(*sp_coef);
88            let flags = damage_flags(*school);
89
90            quote!(#expression.damage_sp(#coefficient, #flags))
91        }
92        ManifestDamageDef::Flat { flat } => {
93            let flat = lit_f64(*flat);
94
95            quote!(#expression.damage_flat(#flat))
96        }
97    })
98}
99
100pub(crate) fn apply_channel_tick_damage(
101    expression: &TokenStream,
102    damage: &ManifestDamageDef,
103) -> anyhow::Result<TokenStream> {
104    Ok(match damage {
105        ManifestDamageDef::EffectRef {
106            spell_id,
107            effect,
108            kind,
109            school: Some(school),
110            ..
111        } => {
112            let spell_id = spell_idx(*spell_id);
113            let effect = lit_int(effect);
114            let flags = damage_flags(*school);
115
116            match kind.as_str() {
117                "ap" => quote! {
118                    #expression.channel_tick_damage_ap(
119                        data.ap_coef(#spell_id, #effect),
120                        #flags,
121                    )
122                },
123                "sp" => quote! {
124                    #expression.channel_tick_damage_sp(
125                        data.sp_coef(#spell_id, #effect),
126                        #flags,
127                    )
128                },
129                other => bail!("unknown channel tick damage kind: {other}"),
130            }
131        }
132        ManifestDamageDef::EffectRef {
133            spell_id,
134            effect,
135            kind,
136            school: None,
137            ..
138        } => {
139            let lookup = effect_lookup(&quote!(data), *spell_id, *effect);
140
141            match kind.as_str() {
142                "ap" => quote!(#expression.channel_tick_damage_ap_from_data(#lookup)?),
143                "sp" => quote!(#expression.channel_tick_damage_sp_from_data(#lookup)?),
144                other => bail!("unknown channel tick damage kind: {other}"),
145            }
146        }
147        ManifestDamageDef::Ap { ap_coef, school } => {
148            let coefficient = lit_f64(*ap_coef);
149            let flags = damage_flags(*school);
150
151            quote!(#expression.channel_tick_damage_ap(#coefficient, #flags))
152        }
153        ManifestDamageDef::Sp { sp_coef, school } => {
154            let coefficient = lit_f64(*sp_coef);
155            let flags = damage_flags(*school);
156
157            quote!(#expression.channel_tick_damage_sp(#coefficient, #flags))
158        }
159        ManifestDamageDef::Flat { .. } => {
160            bail!("channel tick_damage does not support Flat form");
161        }
162    })
163}
164
165pub(crate) fn apply_channel_tick_damage_alt(
166    expression: &TokenStream,
167    damage: &ManifestDamageDef,
168    aura: &str,
169) -> anyhow::Result<TokenStream> {
170    let aura = rust_ident(aura)?;
171
172    Ok(match damage {
173        ManifestDamageDef::EffectRef {
174            spell_id,
175            effect,
176            kind,
177            school: Some(school),
178            ..
179        } => {
180            let spell_idx = spell_idx(*spell_id);
181            let raw_spell_id = lit_int(spell_id);
182            let effect = lit_int(effect);
183            let flags = damage_flags(*school);
184
185            match kind.as_str() {
186                "ap" => quote! {
187                    #expression.channel_tick_damage_alt_ap(
188                        data.ap_coef(#spell_idx, #effect),
189                        #flags,
190                        #raw_spell_id,
191                        AURA::#aura,
192                    )
193                },
194                "sp" => quote! {
195                    #expression.channel_tick_damage_alt_sp(
196                        data.sp_coef(#spell_idx, #effect),
197                        #flags,
198                        #raw_spell_id,
199                        AURA::#aura,
200                    )
201                },
202                other => bail!("unknown alternative channel tick damage kind: {other}"),
203            }
204        }
205        ManifestDamageDef::EffectRef {
206            spell_id,
207            effect,
208            kind,
209            school: None,
210            ..
211        } => {
212            let lookup = effect_lookup(&quote!(data), *spell_id, *effect);
213
214            match kind.as_str() {
215                "ap" => {
216                    quote!(#expression.channel_tick_damage_alt_ap_from_data(#lookup, AURA::#aura)?)
217                }
218                "sp" => {
219                    quote!(#expression.channel_tick_damage_alt_sp_from_data(#lookup, AURA::#aura)?)
220                }
221                other => bail!("unknown alternative channel tick damage kind: {other}"),
222            }
223        }
224        ManifestDamageDef::Ap { ap_coef, school } => {
225            let coefficient = lit_f64(*ap_coef);
226            let flags = damage_flags(*school);
227
228            quote! {
229                #expression.channel_tick_damage_alt_ap(
230                    #coefficient,
231                    #flags,
232                    0,
233                    AURA::#aura,
234                )
235            }
236        }
237        ManifestDamageDef::Sp { sp_coef, school } => {
238            let coefficient = lit_f64(*sp_coef);
239            let flags = damage_flags(*school);
240
241            quote! {
242                #expression.channel_tick_damage_alt_sp(
243                    #coefficient,
244                    #flags,
245                    0,
246                    AURA::#aura,
247                )
248            }
249        }
250        ManifestDamageDef::Flat { .. } => {
251            bail!("alternative channel tick damage does not support Flat form");
252        }
253    })
254}
255
256#[cfg(test)]
257mod tests {
258    use googletest::prelude::*;
259
260    use super::*;
261
262    #[gtest]
263    fn stage_qualified_manifest_damage_emits_the_existing_builder_call() -> Result<()> {
264        let damage: ManifestDamageDef = toml::from_str(
265            r#"
266                ap_coef = 1.25
267                school = "physical"
268            "#,
269        )
270        .or_fail()?;
271
272        verify_that!(
273            apply_damage(&quote!(spell), &damage).or_fail()?.to_string(),
274            eq("spell . damage_ap (1.25 , wowlab_engine_combat :: DamageFlags :: PHYSICAL)")
275        )
276    }
277}