Skip to main content

codegen/app/
spec_file.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use wowlab_manifest_schema::{Manifest, SharedBindings};
4
5use crate::{
6    gen_aura::aura_functions,
7    gen_build::{build_function, handler_factory},
8    gen_constants::{
9        constants, damage_action_expr, effect_constant, effects, local_indices, metric_keys,
10        talent_table,
11    },
12    gen_descriptor::{descriptor, smoke_test},
13    gen_spells::spell_functions,
14    helpers::spec_from_wow_id,
15    rust_source::{FileComment, inspect_link, lit_int, lit_str, render_rust, rust_ident},
16};
17
18fn spec_imports(manifest: &Manifest) -> TokenStream {
19    let aura_imports = (!manifest.auras.is_empty()).then(|| {
20        quote! {
21            AuraDefinitionDraft,
22            LocalAuraIdx,
23        }
24    });
25    let spell_imports = (!manifest.spells.is_empty()).then(|| {
26        quote! {
27            LocalSpellIdx,
28            SpellDefinitionDraft,
29        }
30    });
31    let has_server_proc_entries = manifest
32        .spells
33        .values()
34        .any(|spell| !spell.proc_attributes.is_empty())
35        || manifest
36            .auras
37            .values()
38            .any(|aura| !aura.proc_attributes.is_empty());
39    let proc_import = has_server_proc_entries.then(|| {
40        quote!(
41            use wowlab_engine_domain::dbc::ProcAttributes;
42        )
43    });
44    let hero_import = (!manifest.hero_talents.is_empty()).then(|| quote!(HeroTalentTreeDesc,));
45    let server_proc_import = has_server_proc_entries.then(|| quote!(DeclaredServerProcEntry,));
46
47    quote! {
48        use wowlab_types::{
49            combat::ResourceType,
50            game::SpecId,
51        };
52        #proc_import
53        use wowlab_engine_combat::{
54            CombatSystemBuilder,
55            CombatSystemDraft,
56            #aura_imports
57            #spell_imports
58        };
59        use wowlab_engine_ports::{
60            DeclaredSpecMetadata,
61            SpecDescriptor,
62            SpecHandler,
63            #hero_import
64            #server_proc_import
65        };
66        use wowlab_engine_gamedata::ResolvedGameData;
67    }
68}
69
70pub(super) fn generate_spec_file(
71    manifest: &Manifest,
72    source_path: &str,
73    module_name: &str,
74    display_name: &str,
75) -> anyhow::Result<String> {
76    let spec_variant = format!("{:?}", spec_from_wow_id(manifest.spec.id)?);
77    let imports = spec_imports(manifest);
78    let identity_module = rust_ident(module_name)?;
79    let damage_namespace = quote!(
80        wowlab_engine_combat::generated_identities::specs::#identity_module::damage
81    );
82    let constants = constants(manifest, &damage_namespace)?;
83    let local_indices = local_indices(manifest, module_name)?;
84    let effects = effects(manifest)?;
85    let talents = talent_table(manifest)?;
86    let metric_keys = metric_keys(manifest)?;
87    let auras = aura_functions(manifest, module_name)?;
88    let spells = spell_functions(manifest, module_name)?;
89    let build = build_function(manifest, &spec_variant, module_name)?;
90    let handler = handler_factory(manifest)?;
91    let descriptor = descriptor(manifest, &spec_variant, display_name)?;
92    let smoke_test = smoke_test();
93    let preamble = [
94        FileComment::inner_doc(format!(
95            "@generated by codegen-cli from manifests/specs/{source_path} -- do not edit by hand."
96        ))?,
97        FileComment::line("Generated code: naming follows game data conventions")?,
98    ];
99    let lint_level = format_ident!("allow");
100
101    Ok(render_rust(
102        quote! {
103            #![#lint_level(non_snake_case, dead_code)]
104
105            #imports
106            #constants
107            #local_indices
108            #effects
109            #talents
110            #metric_keys
111            #auras
112            #spells
113            #build
114            #handler
115            #descriptor
116            #smoke_test
117        },
118        &preamble,
119    )?)
120}
121
122pub(super) fn generate_shared_file(
123    bindings: &SharedBindings,
124    source_path: &str,
125    identity_module: &str,
126) -> anyhow::Result<String> {
127    let identity_module = rust_ident(identity_module)?;
128    let damage_namespace = quote!(
129        wowlab_engine_combat::generated_identities::shared::#identity_module::damage
130    );
131    let reported_spells = bindings
132        .reported_spells
133        .iter()
134        .map(|(name, id)| {
135            let name = rust_ident(&name.to_uppercase())?;
136            let id_literal = lit_int(id);
137            let documentation = lit_str(&inspect_link("spell", id));
138
139            Ok::<_, syn::Error>(quote! {
140                #[doc = #documentation]
141                pub(crate) const #name: u32 = #id_literal;
142            })
143        })
144        .collect::<Result<Vec<_>, _>>()?;
145    let reported_spells = (!reported_spells.is_empty()).then(|| {
146        quote! {
147            pub(crate) mod REPORTED_SPELL {
148                #(#reported_spells)*
149            }
150        }
151    });
152    let effects = bindings
153        .effects
154        .iter()
155        .map(|(name, effect)| {
156            let documentation = lit_str(&inspect_link("spell", effect.spell_id));
157            let constant = effect_constant(name, effect.spell_id, effect.effect)?;
158
159            Ok::<_, anyhow::Error>(quote! {
160                #[doc = #documentation]
161                #constant
162            })
163        })
164        .collect::<anyhow::Result<Vec<_>>>()?;
165    let effects = (!effects.is_empty()).then(|| {
166        quote! {
167            pub(crate) mod EFFECT {
168                #(#effects)*
169            }
170        }
171    });
172    let damage_reported = bindings
173        .reported_spells
174        .iter()
175        .map(|(name, spell_id)| {
176            let name = rust_ident(&name.to_uppercase())?;
177            let action = damage_action_expr(&damage_namespace, *spell_id);
178
179            Ok::<_, anyhow::Error>(quote! {
180                pub(crate) const #name: wowlab_engine_combat::DamageActionId =
181                    #action;
182            })
183        })
184        .collect::<anyhow::Result<Vec<_>>>()?;
185    let damage_effects = bindings
186        .effects
187        .iter()
188        .map(|(name, effect)| {
189            let name = rust_ident(name)?;
190            let effect = crate::gen_constants::damage_effect_expr(
191                &damage_namespace,
192                effect.spell_id,
193                effect.effect,
194            );
195
196            Ok::<_, anyhow::Error>(quote! {
197                pub(crate) const #name: wowlab_engine_combat::DamageEffectId =
198                    #effect;
199            })
200        })
201        .collect::<anyhow::Result<Vec<_>>>()?;
202    let preamble = [
203        FileComment::inner_doc(format!(
204            "@generated by codegen-cli from manifests/specs/{source_path} -- do not edit by hand."
205        ))?,
206        FileComment::line("Generated shared manifest bindings")?,
207    ];
208    let lint_level = format_ident!("allow");
209
210    Ok(render_rust(
211        quote! {
212            #![#lint_level(non_snake_case, dead_code)]
213
214            #reported_spells
215            #effects
216            pub(crate) mod DAMAGE_REPORTED {
217                #(#damage_reported)*
218            }
219            pub(crate) mod DAMAGE_EFFECT {
220                #(#damage_effects)*
221            }
222        },
223        &preamble,
224    )?)
225}