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