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}