use heck::ToPascalCase; use indexmap::IndexMap; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use serde::Deserialize; #[derive(Deserialize)] struct CustomStatisticEntry { id: i32, } #[derive(Deserialize)] struct StatisticData { id: i32, registry: String, entries: IndexMap, } pub fn build() -> TokenStream { let stats_json = std::fs::read_to_string("../assets/stats.json").expect("Failed to read stats.json"); let stats_data: IndexMap = serde_json::from_str(&stats_json).expect("Failed to parse stats.json"); let mut category_variants = Vec::new(); let mut category_from_id_arms = Vec::new(); let mut category_from_name_arms = Vec::new(); let mut custom_stat_variants = Vec::new(); let mut custom_stat_from_id_arms = Vec::new(); for (category_name, data) in &stats_data { let category_ident = format_ident!( "{}", category_name.replace("minecraft:", "").to_pascal_case() ); let category_id = data.id; category_variants.push(quote! { #category_ident = #category_id }); category_from_id_arms.push(quote! { #category_id => Some(Self::#category_ident) }); let category_name_stripped = category_name.replace("minecraft:", ""); category_from_name_arms .push(quote! { #category_name_stripped => Some(Self::#category_ident) }); if category_name == "minecraft:custom" { for (stat_name, entry) in &data.entries { let stat_ident = format_ident!("{}", stat_name.replace("minecraft:", "").to_pascal_case()); let stat_id = entry.id; custom_stat_variants.push(quote! { #stat_ident = #stat_id }); custom_stat_from_id_arms.push(quote! { #stat_id => Some(Self::#stat_ident) }); } } } quote! { use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(i32)] pub enum StatisticCategory { #(#category_variants,)* } impl StatisticCategory { #[must_use] pub const fn from_i32(id: i32) -> Option { match id { #(#category_from_id_arms,)* _ => None, } } #[must_use] pub fn from_registry_key(name: &str) -> Option { let name = name.strip_prefix("minecraft:").unwrap_or(name); match name { #(#category_from_name_arms,)* _ => None, } } } impl TryFrom for StatisticCategory { type Error = (); fn try_from(value: i32) -> Result { Self::from_i32(value).ok_or(()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(i32)] pub enum CustomStatistic { #(#custom_stat_variants,)* } impl CustomStatistic { #[must_use] pub const fn from_i32(id: i32) -> Option { match id { #(#custom_stat_from_id_arms,)* _ => None, } } } impl TryFrom for CustomStatistic { type Error = (); fn try_from(value: i32) -> Result { Self::from_i32(value).ok_or(()) } } } }