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