diff --git a/Cargo.lock b/Cargo.lock index 1caeddee1..c7f798fb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2001,6 +2001,7 @@ dependencies = [ name = "pumpkin-macros" version = "0.1.0" dependencies = [ + "itertools 0.13.0", "proc-macro2", "quote", "serde", diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index c677c7c01..5dca73b94 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -10,3 +10,6 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = "2.0" +serde.workspace = true +itertools.workspace = true +serde_json = "1.0.128" diff --git a/pumpkin-macros/src/block_id.rs b/pumpkin-macros/src/block_id.rs new file mode 100644 index 000000000..825de0b6f --- /dev/null +++ b/pumpkin-macros/src/block_id.rs @@ -0,0 +1,136 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use itertools::Itertools; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::Parser; + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockDefinition { + /// e.g. minecraft:door or minecraft:button + #[serde(rename = "type")] + pub category: String, + + /// Specifies the variant of the blocks category. + /// e.g. minecraft:iron_door has the variant iron + #[serde(rename = "block_set_type")] + pub variant: Option, +} + +/// One possible state of a Block. +/// This could e.g. be an extended piston facing left. +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockState { + pub id: i32, + + /// Whether this is the default state of the Block + #[serde(default, rename = "default")] + pub is_default: bool, + + /// The propertise active for this `BlockState`. + #[serde(default)] + pub properties: HashMap, +} + +/// A fully-fledged block definition. +/// Stores the category, variant, all of the possible states and all of the possible properties. +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +struct RegistryBlockType { + pub definition: RegistryBlockDefinition, + pub states: Vec, + + // TODO is this safe to remove? It's currently not used in the Project. @lukas0008 @Snowiiii + /// A list of valid property keys/values for a block. + #[serde(default, rename = "properties")] + valid_properties: HashMap>, +} + +static BLOCKS: LazyLock> = LazyLock::new(|| { + serde_json::from_str(include_str!("../../assets/blocks.json")) + .expect("Could not parse block.json registry.") +}); + +pub fn block_id_impl(item: TokenStream) -> TokenStream { + let data = syn::punctuated::Punctuated::::parse_terminated + .parse(item) + .unwrap(); + let block_name = data + .first() + .expect("The first argument should be a block name"); + + let block_name = match block_name { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!("The first argument should be a string"), + }, + _ => panic!("The first argument should be a string"), + }; + + let mut properties = HashMap::new(); + for expr_thingy in data.into_iter().skip(1) { + match expr_thingy { + syn::Expr::Assign(assign) => { + let left = match assign.left.as_ref() { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!( + "All not-first arguments should be assignments (\"foo\" = \"bar\")" + ), + }, + _ => { + panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")") + } + }; + let right = match assign.right.as_ref() { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(name) => name.value(), + _ => panic!( + "All not-first arguments should be assignments (\"foo\" = \"bar\")" + ), + }, + _ => { + panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")") + } + }; + properties.insert(left, right); + } + _ => panic!("All not-first arguments should be assignments (\"foo\" = \"bar\")"), + } + } + + // panic!("{:?}", properties); + + let block_info = &BLOCKS + .get(&block_name) + .expect("Block with that name does not exist"); + + let id = if properties.is_empty() { + block_info + .states + .iter() + .find(|state| state.is_default) + .expect("Error inside blocks.json file: Every Block should have at least 1 default state") + .id + } else { + match block_info + .states + .iter() + .find(|state| state.properties == properties) + { + Some(state) => state.id, + None => panic!( + "Could not find block with these properties, the following are valid properties: \n{}", + block_info + .valid_properties + .iter() + .map(|(name, values)| format!("{name} = {}", values.join(" | "))) + .join("\n") + ), + } + }; + + quote! { + pumpkin_world::block::block_id::BlockId::from_id(#id as u16) + } + .into() +} diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 346159229..da53c14be 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -22,3 +22,9 @@ pub fn packet(input: TokenStream, item: TokenStream) -> TokenStream { gen.into() } + +mod block_id; +#[proc_macro] +pub fn block_id(item: TokenStream) -> TokenStream { + block_id::block_id_impl(item) +}