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