From f15f0325c0a9d62f10ea22b891ab7134391b04fb Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 9 Dec 2023 21:45:36 -0600 Subject: [PATCH] add BlockState::property --- azalea-block/azalea-block-macros/src/lib.rs | 162 ++++++++++++++++---- azalea-block/src/generated.rs | 3 +- azalea-block/src/lib.rs | 11 +- azalea-world/src/heightmap.rs | 5 +- azalea/src/pathfinder/world.rs | 5 +- 5 files changed, 154 insertions(+), 32 deletions(-) diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 245b605e2..270b49c7b 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -42,9 +42,13 @@ struct PropertyDefinitions { /// `snowy: Snowy(false)` or `axis: properties::Axis::Y` #[derive(Debug)] struct PropertyWithNameAndDefault { + // "snowy" / "axis" name: Ident, + // Snowy / Axis property_type: Ident, + property_value_type: Ident, is_enum: bool, + // false / properties::Axis::Y default: proc_macro2::TokenStream, } @@ -60,7 +64,7 @@ struct BlockDefinition { } impl Parse for PropertyWithNameAndDefault { fn parse(input: ParseStream) -> Result { - // `snowy: false` or `axis: properties::Axis::Y` + // `snowy: Snowy(false)` or `axis: properties::Axis::Y` let property_name = input.parse()?; input.parse::()?; @@ -68,12 +72,14 @@ impl Parse for PropertyWithNameAndDefault { let mut property_default = quote! { #first_ident }; let property_type: Ident; + let property_value_type: Ident; let mut is_enum = false; if input.parse::().is_ok() { // enum is_enum = true; - property_type = first_ident; + property_type = first_ident.clone(); + property_value_type = first_ident; let variant = input.parse::()?; property_default = quote! { properties::#property_default::#variant }; } else { @@ -86,7 +92,8 @@ impl Parse for PropertyWithNameAndDefault { let unit_struct_inner_string = unit_struct_inner.to_string(); if matches!(unit_struct_inner_string.as_str(), "true" | "false") { - property_type = Ident::new("bool", first_ident.span()); + property_value_type = Ident::new("bool", first_ident.span()); + property_type = first_ident; property_default = quote! { #unit_struct_inner }; } else { return Err(input.error("Expected a boolean or an enum variant")); @@ -96,6 +103,7 @@ impl Parse for PropertyWithNameAndDefault { Ok(PropertyWithNameAndDefault { name: property_name, property_type, + property_value_type, is_enum, default: property_default, }) @@ -259,6 +267,12 @@ impl Parse for MakeBlockStates { } } +struct PropertyVariantData { + pub block_state_ids: Vec, + pub ident: Ident, + pub is_enum: bool, +} + #[proc_macro] pub fn make_block_states(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as MakeBlockStates); @@ -318,6 +332,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { #property_enum_variants } + // impl Property for #property_struct_name { + // type Value = Self; + + // fn try_from_block_state + // } + impl From for #property_struct_name { fn from(value: u32) -> Self { match value { @@ -337,6 +357,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { #[derive(Debug, Clone, Copy)] pub struct #property_struct_name(pub bool); + // impl Property for #property_struct_name { + // type Value = bool; + // } + impl From for #property_struct_name { fn from(value: u32) -> Self { match value { @@ -360,14 +384,19 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let mut from_registry_block_to_blockstate_match = quote! {}; let mut from_registry_block_to_blockstates_match = quote! {}; - // a list of block state ids that are waterlogged - let mut waterlogged_state_ids: Vec = Vec::new(); + // { + // Waterlogged: [ + // [ vec of waterlogged = true state ids ], + // [ vec of waterlogged = false state ids ] + // } + // } + let mut properties_to_state_ids: HashMap> = HashMap::new(); for block in &input.block_definitions.blocks { let block_property_names = &block .properties_and_defaults .iter() - .map(|p| p.property_type.to_string()) + .map(|p| p.property_value_type.to_string()) .collect::>(); let mut block_properties_vec = Vec::new(); for property_name in block_property_names { @@ -421,6 +450,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { properties_with_name.push(PropertyWithNameAndDefault { name: Ident::new(&property_name, proc_macro2::Span::call_site()), property_type: property.property_type.clone(), + property_value_type: property.property_value_type.clone(), is_enum: property.is_enum, default: property.default.clone(), }); @@ -436,7 +466,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // pub has_bottle_2: HasBottle, let mut block_struct_fields = quote! {}; for PropertyWithNameAndDefault { - property_type: struct_name, + property_value_type, name, is_enum, .. @@ -445,9 +475,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // let property_name_snake = // Ident::new(&property.to_string(), proc_macro2::Span::call_site()); block_struct_fields.extend(if *is_enum { - quote! { pub #name: properties::#struct_name, } + quote! { pub #name: properties::#property_value_type, } } else { - quote! { pub #name: #struct_name, } + quote! { pub #name: #property_value_type, } }); } @@ -483,15 +513,15 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { for i in 0..properties_with_name.len() { let property = &properties_with_name[i]; let property_name = &property.name; - let property_struct_name_ident = &property.property_type; + let property_value_name_ident = &property.property_type; let variant = Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site()); // this terrible code just gets the property default as a string - let property_default_as_string = if let TokenTree::Ident(i) = + let property_default_as_string = if let TokenTree::Ident(ident) = property.default.clone().into_iter().last().unwrap() { - i.to_string() + ident.to_string() } else { panic!() }; @@ -499,20 +529,31 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { is_default = false; } - let property_value = if property.is_enum { - quote! {properties::#property_struct_name_ident::#variant} + let property_variant = if property.is_enum { + quote! {properties::#property_value_name_ident::#variant} } else { quote! {#variant} }; from_block_to_state_combination_match_inner.extend(quote! { - #property_name: #property_value, + #property_name: #property_variant, }); - // if "waterlogged" is a property and it's true for this state then add it to - // waterlogged_state_ids - if property_name == "waterlogged" && property_value.to_string() == "true" { - waterlogged_state_ids.push(state_id) + // add to properties_to_state_ids + let property_variants = properties_to_state_ids + .entry(property_value_name_ident.to_string()) + .or_insert_with(Vec::new); + let property_variant_data = property_variants + .iter_mut() + .find(|v| v.ident.to_string() == variant.to_string()); + if let Some(property_variant_data) = property_variant_data { + property_variant_data.block_state_ids.push(state_id); + } else { + property_variants.push(PropertyVariantData { + block_state_ids: vec![state_id], + ident: variant, + is_enum: property.is_enum, + }); } } @@ -557,13 +598,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let PropertyWithNameAndDefault { property_type: property_struct_name_ident, name: property_name, + property_value_type, .. } = &properties_with_name[i]; let property_variants = &block_properties_vec[i]; let property_variants_count = property_variants.len() as u32; let conversion_code = { - if &property_struct_name_ident.to_string() == "bool" { + if &property_value_type.to_string() == "bool" { assert_eq!(property_variants_count, 2); // this is not a mistake, it starts with true for some reason quote! {(b / #division) % #property_variants_count == 0} @@ -659,8 +701,6 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { block_structs.extend(block_struct); } - let waterlogged_state_ids_match = quote! { #(#waterlogged_state_ids)|* }; - let last_state_id = state_id - 1; let mut generated = quote! { impl BlockState { @@ -670,21 +710,87 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { #last_state_id } - /// Whether the given block state is waterlogged. This checks for - /// the `waterlogged` field, so it'll return false for water. - pub fn waterlogged(&self) -> bool { - matches!(self.id, #waterlogged_state_ids_match) + /// Get a property from this block state. Will be `None` if the block can't have the property. + /// + /// ``` + /// fn is_waterlogged(block_state: azalea_block::BlockState) -> bool { + /// block_state.property::().unwrap_or_default() + /// } + /// ``` + pub fn property(self) -> Option { + P::try_from_block_state(self) } } + }; + + // now impl Property for every property + // ``` + // match state_id { + // // this is just an example of how it might look, these state ids are definitely not correct + // 0|3|6 => Some(Self::Axis::X), + // 1|4|7 => Some(Self::Axis::Y), + // 2|5|8 => Some(Self::Axis::Z), + // _ => None + // } + // ``` + let mut property_impls = quote! {}; + for (property_struct_name, property_values) in properties_to_state_ids { + let mut enum_inner_generated = quote! {}; + + let mut is_enum_ = false; + + for PropertyVariantData { + block_state_ids, + ident, + is_enum, + } in property_values + { + enum_inner_generated.extend(if is_enum { + quote! { + #(#block_state_ids)|* => Some(Self::#ident), + } + } else { + quote! { + #(#block_state_ids)|* => Some(#ident), + } + }); + is_enum_ = is_enum; + } + let is_enum = is_enum_; + + let property_struct_name = + Ident::new(&property_struct_name, proc_macro2::Span::call_site()); + + let value = if is_enum { + quote! { Self } + } else { + quote! { bool } + }; + + let property_impl = quote! { + impl Property for #property_struct_name { + type Value = #value; + + fn try_from_block_state(block_state: BlockState) -> Option { + match block_state.id { + #enum_inner_generated + _ => None + } + } + } + }; + property_impls.extend(property_impl); + } + generated.extend(quote! { pub mod properties { use super::*; #property_enums + + #property_impls } - }; - generated.extend(quote! { pub mod blocks { use super::*; diff --git a/azalea-block/src/generated.rs b/azalea-block/src/generated.rs index 3a903b8d6..97236b4ba 100755 --- a/azalea-block/src/generated.rs +++ b/azalea-block/src/generated.rs @@ -1,4 +1,4 @@ -use crate::{Block, BlockBehavior, BlockState, BlockStates}; +use crate::{Block, BlockBehavior, BlockState, BlockStates, Property}; use azalea_block_macros::make_block_states; use std::fmt::Debug; @@ -5380,3 +5380,4 @@ make_block_states! { }, } } + diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index b67a6e763..d590bcea4 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -33,6 +33,12 @@ impl dyn Block { } } +pub trait Property { + type Value; + + fn try_from_block_state(state: BlockState) -> Option; +} + /// A representation of a state a block can be in. /// /// For example, a stone block only has one state but each possible stair @@ -113,7 +119,10 @@ impl Default for FluidState { impl From for FluidState { fn from(state: BlockState) -> Self { - if state.waterlogged() { + if state + .property::() + .unwrap_or_default() + { Self { fluid: azalea_registry::Fluid::Water, height: 15, diff --git a/azalea-world/src/heightmap.rs b/azalea-world/src/heightmap.rs index 12aaa1590..c38f3edcc 100644 --- a/azalea-world/src/heightmap.rs +++ b/azalea-world/src/heightmap.rs @@ -32,7 +32,10 @@ fn blocks_motion(block_state: BlockState) -> bool { fn motion_blocking(block_state: BlockState) -> bool { // TODO - !block_state.is_air() || block_state.waterlogged() + !block_state.is_air() + || block_state + .property::() + .unwrap_or_default() } impl HeightmapKind { diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs index f73a6641b..4b48f9211 100644 --- a/azalea/src/pathfinder/world.rs +++ b/azalea/src/pathfinder/world.rs @@ -246,7 +246,10 @@ fn is_block_state_passable(block: BlockState) -> bool { if block == azalea_registry::Block::Water.into() { return false; } - if block.waterlogged() { + if block + .property::() + .unwrap_or_default() + { return false; } if block == azalea_registry::Block::Lava.into() {