Skip to main content

steel_core/block_entity/
block_state_nbt.rs

1//! Vanilla block-state NBT codec shape used by block entities.
2
3use std::str::FromStr;
4
5use simdnbt::borrow::NbtCompound as BorrowedNbtCompound;
6use simdnbt::owned::NbtCompound;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::{REGISTRY, RegistryExt as _};
9use steel_utils::{BlockStateId, Identifier};
10
11#[must_use]
12pub(crate) fn save(state: BlockStateId) -> NbtCompound {
13    let mut encoded = NbtCompound::new();
14    encoded.insert("Name", state.get_block().key.to_string());
15
16    let state_properties = REGISTRY.blocks.get_properties(state);
17    if !state_properties.is_empty() {
18        let mut properties = NbtCompound::new();
19        for (name, value) in state_properties {
20            properties.insert(name, value);
21        }
22        encoded.insert("Properties", properties);
23    }
24
25    encoded
26}
27
28#[must_use]
29pub(crate) fn load(encoded: BorrowedNbtCompound<'_, '_>) -> Option<BlockStateId> {
30    let name = encoded.string("Name")?;
31    let identifier = Identifier::from_str(name.to_str().as_ref()).ok()?;
32    let block = REGISTRY.blocks.by_key(&identifier)?;
33
34    let mut properties = Vec::new();
35    if let Some(encoded_properties) = encoded.compound("Properties") {
36        for (name, value) in encoded_properties.iter() {
37            let value = value.string()?;
38            properties.push((name.to_str().into_owned(), value.to_str().into_owned()));
39        }
40    }
41
42    REGISTRY.blocks.state_id_from_block_defaulted_properties(
43        block,
44        properties
45            .iter()
46            .map(|(name, value)| (name.as_str(), value.as_str())),
47    )
48}
49
50#[cfg(test)]
51mod tests {
52    use std::io::Cursor;
53
54    use simdnbt::borrow::read_compound as read_borrowed_compound;
55    use simdnbt::owned::NbtTag;
56    use steel_registry::blocks::properties::{BlockStateProperties, Direction, PistonType};
57    use steel_registry::init_vanilla_registry;
58    use steel_registry::vanilla_blocks;
59
60    use super::*;
61
62    #[test]
63    fn propertyful_block_state_round_trips_vanilla_nbt_shape() {
64        init_vanilla_registry();
65        let state = vanilla_blocks::PISTON_HEAD
66            .default_state()
67            .set_value(&BlockStateProperties::FACING, Direction::East)
68            .set_value(&BlockStateProperties::PISTON_TYPE, PistonType::Sticky)
69            .set_value(&BlockStateProperties::SHORT, true);
70
71        let encoded = save(state);
72        assert_eq!(
73            encoded.string("Name").map(|name| name.to_str()),
74            Some("minecraft:piston_head".into())
75        );
76        assert!(matches!(
77            encoded.get("Properties"),
78            Some(NbtTag::Compound(_))
79        ));
80
81        let mut bytes = Vec::new();
82        encoded.write(&mut bytes);
83        let borrowed = read_borrowed_compound(&mut Cursor::new(bytes.as_slice()))
84            .expect("test NBT should reborrow");
85        let view = BorrowedNbtCompound::from(&borrowed);
86
87        assert_eq!(load(view), Some(state));
88    }
89}