azalea_block/
block_state.rs

1use std::{
2    fmt::{self, Debug},
3    io::{self, Cursor, Write},
4};
5
6use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
7
8use crate::Block;
9
10/// The type that's used internally to represent a block state ID.
11///
12/// This should be either `u16` or `u32`. If you choose to modify it, you must
13/// also change it in `azalea-block-macros/src/lib.rs`.
14///
15/// This does not affect protocol serialization, it just allows you to make the
16/// internal type smaller if you want.
17pub type BlockStateIntegerRepr = u16;
18
19/// A representation of a state a block can be in.
20///
21/// For example, a stone block only has one state but each possible stair
22/// rotation is a different state.
23///
24/// Note that this type is internally either a `u16` or `u32`, depending on
25/// [`BlockStateIntegerRepr`].
26#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
27pub struct BlockState {
28    /// The protocol ID for the block state. IDs may change every
29    /// version, so you shouldn't hard-code them or store them in databases.
30    pub id: BlockStateIntegerRepr,
31}
32
33impl BlockState {
34    /// A shortcut for getting the air block state, since it always has an ID of
35    /// 0.
36    pub const AIR: BlockState = BlockState { id: 0 };
37
38    /// Whether the block state is possible to exist in vanilla Minecraft.
39    ///
40    /// It's equivalent to checking that the state ID is not greater than
41    /// [`Self::MAX_STATE`].
42    #[inline]
43    pub fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool {
44        state_id <= Self::MAX_STATE
45    }
46
47    /// Returns true if the block is air. This only checks for normal air, not
48    /// other types like cave air.
49    #[inline]
50    pub fn is_air(&self) -> bool {
51        self == &Self::AIR
52    }
53}
54
55impl TryFrom<u32> for BlockState {
56    type Error = ();
57
58    /// Safely converts a u32 state id to a block state.
59    fn try_from(state_id: u32) -> Result<Self, Self::Error> {
60        let state_id = state_id as BlockStateIntegerRepr;
61        if Self::is_valid_state(state_id) {
62            Ok(BlockState { id: state_id })
63        } else {
64            Err(())
65        }
66    }
67}
68impl TryFrom<u16> for BlockState {
69    type Error = ();
70
71    /// Safely converts a u16 state id to a block state.
72    fn try_from(state_id: u16) -> Result<Self, Self::Error> {
73        let state_id = state_id as BlockStateIntegerRepr;
74        if Self::is_valid_state(state_id) {
75            Ok(BlockState { id: state_id })
76        } else {
77            Err(())
78        }
79    }
80}
81
82impl AzaleaRead for BlockState {
83    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
84        let state_id = u32::azalea_read_var(buf)?;
85        Self::try_from(state_id).map_err(|_| BufReadError::UnexpectedEnumVariant {
86            id: state_id as i32,
87        })
88    }
89}
90impl AzaleaWrite for BlockState {
91    fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
92        u32::azalea_write_var(&(self.id as u32), buf)
93    }
94}
95
96impl Debug for BlockState {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(
99            f,
100            "BlockState(id: {}, {:?})",
101            self.id,
102            Box::<dyn Block>::from(*self)
103        )
104    }
105}
106
107impl From<BlockState> for azalea_registry::Block {
108    fn from(value: BlockState) -> Self {
109        Box::<dyn Block>::from(value).as_registry_block()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_from_u32() {
119        assert_eq!(
120            BlockState::try_from(0 as BlockStateIntegerRepr).unwrap(),
121            BlockState::AIR
122        );
123
124        assert!(BlockState::try_from(BlockState::MAX_STATE).is_ok());
125        assert!(BlockState::try_from(BlockState::MAX_STATE + 1).is_err());
126    }
127
128    #[test]
129    fn test_from_blockstate() {
130        let block: Box<dyn Block> = Box::<dyn Block>::from(BlockState::AIR);
131        assert_eq!(block.id(), "air");
132
133        let block: Box<dyn Block> =
134            Box::<dyn Block>::from(BlockState::from(azalea_registry::Block::FloweringAzalea));
135        assert_eq!(block.id(), "flowering_azalea");
136    }
137
138    #[test]
139    fn test_debug_blockstate() {
140        let formatted = format!(
141            "{:?}",
142            BlockState::from(azalea_registry::Block::FloweringAzalea)
143        );
144        assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
145
146        let formatted = format!(
147            "{:?}",
148            BlockState::from(azalea_registry::Block::BigDripleafStem)
149        );
150        assert!(
151            formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
152            "{}",
153            formatted
154        );
155    }
156}