aboutsummaryrefslogtreecommitdiff
path: root/azalea-block
diff options
context:
space:
mode:
authorUbuntu <github@matdoes.dev>2023-02-10 01:56:45 +0000
committerUbuntu <github@matdoes.dev>2023-02-10 01:56:45 +0000
commit9d4f738d4e66adf0796e163d1c9368aaba906bba (patch)
treeae7b3c7fca0ff054eb67c1311955e9321a37e559 /azalea-block
parent48b2a37aa09f0302b40d0678cdde2703f919ed18 (diff)
downloadazalea-drasl-9d4f738d4e66adf0796e163d1c9368aaba906bba.tar.xz
make blockstate good
Diffstat (limited to 'azalea-block')
-rwxr-xr-xazalea-block/Cargo.toml8
-rwxr-xr-xazalea-block/README.md51
-rwxr-xr-xazalea-block/azalea-block-macros/Cargo.toml3
-rwxr-xr-xazalea-block/azalea-block-macros/src/lib.rs117
-rwxr-xr-xazalea-block/src/blocks.rs11
-rwxr-xr-xazalea-block/src/lib.rs43
6 files changed, 166 insertions, 67 deletions
diff --git a/azalea-block/Cargo.toml b/azalea-block/Cargo.toml
index 0ab2edab..13b1758c 100755
--- a/azalea-block/Cargo.toml
+++ b/azalea-block/Cargo.toml
@@ -6,13 +6,11 @@ name = "azalea-block"
repository = "https://github.com/mat-1/azalea/tree/main/azalea-block"
version = "0.5.0"
-[features]
-full-debug = ["azalea-block-macros/full-debug"]
-
[lib]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-azalea-block-macros = {path = "./azalea-block-macros", version = "^0.5.0" }
-azalea-buf = {path = "../azalea-buf", version = "^0.5.0" }
+azalea-block-macros = { path = "./azalea-block-macros", version = "^0.5.0" }
+azalea-buf = { path = "../azalea-buf", version = "^0.5.0" }
+azalea-registry = { version = "0.5.0", path = "../azalea-registry" }
diff --git a/azalea-block/README.md b/azalea-block/README.md
index efeda675..a2a72352 100755
--- a/azalea-block/README.md
+++ b/azalea-block/README.md
@@ -1,12 +1,49 @@
-# Azalea Block
-
Representation of Minecraft block states.
-There's two main things here, the `BlockState` enum and the `Block` trait.
-`BlockState` is a simple enum with every possible block state as variant, and `Block` is a heavier trait which lets you access information about a block more easily.
+There's three block types, used for different things. You can (mostly) freely convert between them with `.into()`.
+
+## BlockState struct
+
+[`BlockState`] is a struct containing the numerical protocol ID of a block state. This is how blocks are stored in the world.
+
+
+```
+# use azalea_block::BlockState;
+let block_state: BlockState = azalea_registry::Block::Jukebox.into();
+```
+
+## Block trait
+
+The [`Block`] trait represents a type of a block. With the the [`Block`] trait, you can get some extra things like the string block ID and some information about the block's behavior. Also, the structs that implement the trait contain the block attributes as fields so it's more convenient to get them. Note that this is often used as `Box<dyn Block>`.
+If for some reason you don't want the `Block` trait, set default-features to false.
+
+```
+# use azalea_block::{Block, BlockState};
+# let block_state: BlockState = azalea_registry::Block::Jukebox.into();
+if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::JukeboxBlock>() {
+ // ...
+}
+```
+```
+# use azalea_block::BlockState;
+let block_state: BlockState = azalea_block::CobblestoneWallBlock {
+ east: azalea_block::EastWall::Low,
+ north: azalea_block::NorthWall::Low,
+ south: azalea_block::SouthWall::Low,
+ west: azalea_block::WestWall::Low,
+ up: false,
+ waterlogged: false,
+}
+.into();
+```
+
+
+## azalea_registry::Block enum
-Every block is a struct that implements `Block`. You can freely convert between `BlockState` and `Block` with .into().
+This one technically isn't from the `azalea-block` crate, but it's still very relevant. It's an enum that contains every block type as a variant *without* containing any state data (like `BlockState` and the `Block` trait). Converting this into any other block type will use the default state for that block.
-If you don't want the `Block` trait, set default-features to false.
+```
+# use azalea_block::BlockState;
+let block_state: BlockState = azalea_registry::Block::Jukebox.into();
+```
-Also, by default the `Debug` implementation for `BlockState` only logs the name of the block and not the name of the enum variant. If you want that, enable the `full-debug` feature (though it's not recommended).
diff --git a/azalea-block/azalea-block-macros/Cargo.toml b/azalea-block/azalea-block-macros/Cargo.toml
index d03dbba7..39744dcc 100755
--- a/azalea-block/azalea-block-macros/Cargo.toml
+++ b/azalea-block/azalea-block-macros/Cargo.toml
@@ -6,9 +6,6 @@ name = "azalea-block-macros"
repository = "https://github.com/mat-1/azalea/tree/main/azalea-block/azalea-block-macros"
version = "0.5.0"
-[features]
-full-debug = []
-
[lib]
proc-macro = true
diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs
index 7e304d57..f241d6c8 100755
--- a/azalea-block/azalea-block-macros/src/lib.rs
+++ b/azalea-block/azalea-block-macros/src/lib.rs
@@ -3,6 +3,7 @@
mod utils;
use proc_macro::TokenStream;
+use proc_macro2::TokenTree;
use quote::quote;
use std::collections::HashMap;
use std::fmt::Write;
@@ -234,7 +235,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut properties_map = HashMap::new();
let mut property_struct_names_to_names = HashMap::new();
- let mut state_id: usize = 0;
+ let mut state_id: u32 = 0;
for property in &input.property_definitions.properties {
let property_type_name: Ident;
@@ -282,8 +283,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#property_enum_variants
}
- impl From<usize> for #property_type_name {
- fn from(value: usize) -> Self {
+ impl From<u32> for #property_type_name {
+ fn from(value: u32) -> Self {
match value {
#property_from_number_variants
_ => panic!("Invalid property value: {}", value),
@@ -305,7 +306,11 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut block_state_enum_variants = quote! {};
let mut block_structs = quote! {};
+
let mut from_state_to_block_match = quote! {};
+ let mut from_registry_block_to_block_match = quote! {};
+ let mut from_registry_block_to_blockstate_match = quote! {};
+
for block in &input.block_definitions.blocks {
let block_property_names = &block
.properties_and_defaults
@@ -403,30 +408,18 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut from_block_to_state_match_inner = quote! {};
let first_state_id = state_id;
+ let mut default_state_id = None;
// if there's no properties, then the block is just a single state
if block_properties_vec.is_empty() {
block_state_enum_variants.extend(quote! {
#block_name_pascal_case,
});
+ default_state_id = Some(state_id);
state_id += 1;
}
for combination in combinations_of(&block_properties_vec) {
- state_id += 1;
- let variant_name = Ident::new(
- &format!(
- "{}_{}",
- block_name_pascal_case,
- combination
- .iter()
- .map(|v| v[0..1].to_uppercase() + &v[1..])
- .collect::<String>()
- ),
- proc_macro2::Span::call_site(),
- );
- block_state_enum_variants.extend(quote! {
- #variant_name,
- });
+ let mut is_default = true;
// face: properties::Face::Floor,
// facing: properties::Facing::North,
@@ -439,6 +432,18 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
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) =
+ property.default.clone().into_iter().last().unwrap()
+ {
+ i.to_string()
+ } else {
+ panic!()
+ };
+ if property_default_as_string != combination[i] {
+ is_default = false;
+ }
+
let property_type = if property.is_enum {
quote! {#property_struct_name_ident::#variant}
} else {
@@ -453,10 +458,21 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
from_block_to_state_match_inner.extend(quote! {
#block_struct_name {
#from_block_to_state_combination_match_inner
- } => BlockState::#variant_name,
+ } => BlockState { id: #state_id },
});
+
+ if is_default {
+ default_state_id = Some(state_id);
+ }
+
+ state_id += 1;
}
+ let Some(default_state_id) = default_state_id else {
+ let defaults = properties_with_name.iter().map(|p| if let TokenTree::Ident(i) = p.default.clone().into_iter().last().unwrap() { i.to_string() } else { panic!() }).collect::<Vec<_>>();
+ panic!("Couldn't get default state id for {}, combinations={:?}, defaults={:?}", block_name_pascal_case.to_string(), block_properties_vec, defaults)
+ };
+
// 7035..=7058 => {
// let b = b - 7035;
// &AcaciaButtonBlock {
@@ -466,7 +482,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// }
// }
let mut from_state_to_block_inner = quote! {};
- let mut division = 1usize;
+ let mut division = 1u32;
for i in (0..properties_with_name.len()).rev() {
let PropertyWithNameAndDefault {
property_type: property_struct_name_ident,
@@ -475,11 +491,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
} = &properties_with_name[i];
let property_variants = &block_properties_vec[i];
- let property_variants_count = property_variants.len();
+ let property_variants_count = property_variants.len() as u32;
let conversion_code = {
if &property_struct_name_ident.to_string() == "bool" {
assert_eq!(property_variants_count, 2);
- quote! {(b / #division) % #property_variants_count != 0}
+ // this is not a mistake, it starts with true for some reason
+ quote! {(b / #division) % #property_variants_count == 0}
} else {
quote! {#property_struct_name_ident::from((b / #division) % #property_variants_count)}
}
@@ -500,6 +517,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
})
},
});
+ from_registry_block_to_block_match.extend(quote! {
+ azalea_registry::Block::#block_name_pascal_case => Box::new(#block_struct_name::default()),
+ });
+ from_registry_block_to_blockstate_match.extend(quote! {
+ azalea_registry::Block::#block_name_pascal_case => BlockState { id: #default_state_id },
+ });
let mut block_default_fields = quote! {};
for PropertyWithNameAndDefault {
@@ -515,10 +538,10 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let block_id = block.name.to_string();
let from_block_to_state_match = if block.properties_and_defaults.is_empty() {
- quote! { BlockState::#block_name_pascal_case }
+ quote! { BlockState { id: #first_state_id } }
} else {
quote! {
- match b {
+ match self {
#from_block_to_state_match_inner
}
}
@@ -537,11 +560,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
fn id(&self) -> &'static str {
#block_id
}
+ fn as_blockstate(&self) -> BlockState {
+ #from_block_to_state_match
+ }
}
impl From<#block_struct_name> for BlockState {
fn from(b: #block_struct_name) -> Self {
- #from_block_to_state_match
+ b.as_blockstate()
}
}
@@ -561,26 +587,29 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut generated = quote! {
#property_enums
- #[repr(u32)]
- #[derive(Copy, Clone, PartialEq, Eq)]
- // the Debug impl is very large and slows down compilation
- #[cfg_attr(feature = "full-debug", derive(Debug))]
- pub enum BlockState {
- #block_state_enum_variants
+ /// A representation of a state a block can be in. (for example, a stone
+ /// block only has one state but each possible stair rotation is a
+ /// different state).
+ #[derive(Copy, Clone, PartialEq, Eq, Default)]
+ pub struct BlockState {
+ /// The protocol ID for the block state. IDs may change every
+ /// version, so you shouldn't hard-code them or store them in databases.
+ pub id: u32
}
impl BlockState {
- /// Returns the highest possible state
+ pub const AIR: BlockState = BlockState { id: 0 };
+
+ /// Returns the highest possible state ID.
#[inline]
pub fn max_state() -> u32 {
#last_state_id
}
}
- #[cfg(not(feature = "full-debug"))]
impl std::fmt::Debug for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "BlockState ({})", Box::<dyn Block>::from(*self).id())
+ write!(f, "BlockState(id: {}, {:?})", self.id, Box::<dyn Block>::from(*self))
}
}
};
@@ -589,14 +618,30 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
#block_structs
impl From<BlockState> for Box<dyn Block> {
- fn from(b: BlockState) -> Self {
- let b = b as usize;
+ fn from(block_state: BlockState) -> Self {
+ let b = block_state.id;
match b {
#from_state_to_block_match
_ => panic!("Invalid block state: {}", b),
}
}
}
+ impl From<azalea_registry::Block> for Box<dyn Block> {
+ fn from(block: azalea_registry::Block) -> Self {
+ match block {
+ #from_registry_block_to_block_match
+ _ => unreachable!("There should always be a block struct for every azalea_registry::Block variant")
+ }
+ }
+ }
+ impl From<azalea_registry::Block> for BlockState {
+ fn from(block: azalea_registry::Block) -> Self {
+ match block {
+ #from_registry_block_to_blockstate_match
+ _ => unreachable!("There should always be a block state for every azalea_registry::Block variant")
+ }
+ }
+ }
});
generated.into()
diff --git a/azalea-block/src/blocks.rs b/azalea-block/src/blocks.rs
index 226542dc..e6923d59 100755
--- a/azalea-block/src/blocks.rs
+++ b/azalea-block/src/blocks.rs
@@ -1,9 +1,18 @@
+use std::any::Any;
+
use crate::BlockBehavior;
use azalea_block_macros::make_block_states;
+use std::fmt::Debug;
-pub trait Block {
+pub trait Block: Debug + Any {
fn behavior(&self) -> BlockBehavior;
fn id(&self) -> &'static str;
+ fn as_blockstate(&self) -> BlockState;
+}
+impl dyn Block {
+ pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
+ (self as &dyn Any).downcast_ref::<T>()
+ }
}
make_block_states! {
diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs
index 4a35be00..7a62e588 100755
--- a/azalea-block/src/lib.rs
+++ b/azalea-block/src/lib.rs
@@ -1,4 +1,5 @@
#![doc = include_str!("../README.md")]
+#![feature(trait_upcasting)]
mod behavior;
mod blocks;
@@ -6,10 +7,7 @@ mod blocks;
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
pub use behavior::BlockBehavior;
pub use blocks::*;
-use std::{
- io::{Cursor, Write},
- mem,
-};
+use std::io::{Cursor, Write};
impl BlockState {
/// Transmutes a u32 to a block state.
@@ -17,8 +15,8 @@ impl BlockState {
/// # Safety
/// The `state_id` should be a valid block state.
#[inline]
- pub unsafe fn from_u32_unsafe(state_id: u32) -> Self {
- mem::transmute::<u32, BlockState>(state_id)
+ pub unsafe fn from_u32_unchecked(state_id: u32) -> Self {
+ BlockState { id: state_id }
}
#[inline]
@@ -33,7 +31,7 @@ impl TryFrom<u32> for BlockState {
/// Safely converts a state id to a block state.
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
if Self::is_valid_state(state_id) {
- Ok(unsafe { Self::from_u32_unsafe(state_id) })
+ Ok(unsafe { Self::from_u32_unchecked(state_id) })
} else {
Err(())
}
@@ -50,7 +48,7 @@ impl McBufReadable for BlockState {
}
impl McBufWritable for BlockState {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- u32::var_write_into(&(*self as u32), buf)
+ u32::var_write_into(&self.id, buf)
}
}
@@ -60,7 +58,7 @@ mod tests {
#[test]
fn test_from_u32() {
- assert_eq!(BlockState::try_from(0).unwrap(), BlockState::Air);
+ assert_eq!(BlockState::try_from(0).unwrap(), BlockState::AIR);
assert!(BlockState::try_from(BlockState::max_state()).is_ok());
assert!(BlockState::try_from(BlockState::max_state() + 1).is_err());
@@ -68,19 +66,34 @@ mod tests {
#[test]
fn test_from_blockstate() {
- let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::Air);
+ let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::AIR);
assert_eq!(block.id(), "air");
- let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::FloweringAzalea);
+ let block: Box<dyn Block> =
+ Box::<dyn Block>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
assert_eq!(block.id(), "flowering_azalea");
}
- #[cfg(not(feature = "full-debug"))]
#[test]
fn test_debug_blockstate() {
- assert_eq!(
- format!("{:?}", BlockState::FloweringAzalea),
- "BlockState (flowering_azalea)"
+ let formatted = format!(
+ "{:?}",
+ BlockState::from(azalea_registry::Block::FloweringAzalea)
+ );
+ assert!(
+ formatted.ends_with(", FloweringAzaleaBlock)"),
+ "{}",
+ formatted
+ );
+
+ let formatted = format!(
+ "{:?}",
+ BlockState::from(azalea_registry::Block::BigDripleafStem)
+ );
+ assert!(
+ formatted.ends_with(", BigDripleafStemBlock { facing: North, waterlogged: false })"),
+ "{}",
+ formatted
);
}
}