mc173/serde/chunk/
block_entity_nbt.rs

1//! NBT serialization and deserialization for [`BlockEntity`] type.
2
3use glam::IVec3;
4
5use crate::block_entity::note_block::NoteBlockBlockEntity;
6use crate::block_entity::dispenser::DispenserBlockEntity;
7use crate::block_entity::furnace::FurnaceBlockEntity;
8use crate::block_entity::jukebox::JukeboxBlockEntity;
9use crate::block_entity::spawner::SpawnerBlockEntity;
10use crate::block_entity::piston::PistonBlockEntity;
11use crate::block_entity::chest::ChestBlockEntity;
12use crate::block_entity::sign::SignBlockEntity;
13use crate::block_entity::BlockEntity;
14use crate::entity::EntityKind;
15use crate::item::ItemStack;
16use crate::geom::Face;
17
18use crate::serde::nbt::{NbtParseError, NbtCompound, NbtCompoundParse};
19
20use super::entity_kind_nbt;
21use super::slot_nbt;
22
23pub fn from_nbt(comp: NbtCompoundParse) -> Result<(IVec3, Box<BlockEntity>), NbtParseError> {
24
25    let x = comp.get_int("x")?;
26    let y = comp.get_int("y")?;
27    let z = comp.get_int("z")?;
28
29    let id = comp.get_string("id")?;
30    let block_entity = Box::new(match id {
31        "Chest" => {
32            let mut chest = ChestBlockEntity::default();
33            slot_nbt::from_nbt_to_inv(comp.get_list("Items")?, &mut chest.inv[..])?;
34            BlockEntity::Chest(chest)
35        }
36        "Furnace" => {
37            let mut inv = [ItemStack::EMPTY; 3];
38            slot_nbt::from_nbt_to_inv(comp.get_list("Items")?, &mut inv[..])?;
39            let mut furnace = FurnaceBlockEntity::default();
40            furnace.input_stack = inv[0];
41            furnace.fuel_stack = inv[1];
42            furnace.output_stack = inv[2];
43            furnace.burn_remaining_ticks = comp.get_short("BurnTime")?.max(0) as u16;
44            furnace.smelt_ticks = comp.get_short("CookTime")?.max(0) as u16;
45            // TODO: burn max ticks
46            BlockEntity::Furnace(furnace)
47        }
48        "Trap" => {
49            let mut dispenser = DispenserBlockEntity::default();
50            slot_nbt::from_nbt_to_inv(comp.get_list("Items")?, &mut dispenser.inv[..])?;
51            BlockEntity::Dispenser(dispenser)
52        }
53        "MobSpawner" => {
54            let mut spawner = SpawnerBlockEntity::default();
55            spawner.entity_kind = entity_kind_nbt::from_nbt(comp.get_string("EntityId")?).unwrap_or(EntityKind::Pig);
56            spawner.remaining_time = comp.get_short("Delay")? as u16;
57            BlockEntity::Spawner(spawner)
58        }
59        "Music" => {
60            let mut note_block = NoteBlockBlockEntity::default();
61            note_block.note = comp.get_byte("note")? as u8;
62            BlockEntity::NoteBlock(note_block)
63        }
64        "Piston" => {
65            let mut piston = PistonBlockEntity::default();
66            piston.block = comp.get_int("blockId")? as u8;
67            piston.metadata = comp.get_int("blockData")? as u8;
68            piston.face = match comp.get_int("facing")? {
69                0 => Face::NegY,
70                1 => Face::PosY,
71                2 => Face::NegZ,
72                3 => Face::PosZ,
73                4 => Face::NegX,
74                _ => Face::PosX,
75            };
76            piston.progress = comp.get_float("progress")?;
77            piston.extending = comp.get_boolean("extending")?;
78            BlockEntity::Piston(piston)
79        }
80        "Sign" => {
81            let mut sign = SignBlockEntity::default();
82            for (i, key) in ["Text1", "Text2", "Text3", "Text4"].into_iter().enumerate() {
83                sign.lines[i] = comp.get_string(key)?.to_string();
84            }
85            BlockEntity::Sign(sign)
86        }
87        "RecordPlayer" => {
88            BlockEntity::Jukebox(JukeboxBlockEntity { 
89                record: comp.get_int("Record")? as u32
90            })
91        }
92        _ => return Err(NbtParseError::new(format!("{}/id", comp.path()), "valid block entity id"))
93    });
94
95    Ok((IVec3::new(x, y, z), block_entity))
96
97}
98
99pub fn to_nbt<'a>(comp: &'a mut NbtCompound, pos: IVec3, block_entity: &BlockEntity) -> &'a mut NbtCompound {
100
101    comp.insert("x", pos.x);
102    comp.insert("y", pos.y);
103    comp.insert("z", pos.z);
104
105    match block_entity {
106        BlockEntity::Chest(chest) => {
107            comp.insert("id", "Chest");
108            comp.insert("Items", slot_nbt::to_nbt_from_inv(&chest.inv[..]));
109        }
110        BlockEntity::Furnace(furnace) => {
111            comp.insert("id", "Furnace");
112            comp.insert("Items", slot_nbt::to_nbt_from_inv(&[furnace.input_stack, furnace.fuel_stack, furnace.output_stack]));
113            comp.insert("BurnTime", furnace.burn_remaining_ticks);
114            comp.insert("CookTime", furnace.smelt_ticks);
115        }
116        BlockEntity::Dispenser(dispenser) => {
117            comp.insert("id", "Trap");
118            comp.insert("Items", slot_nbt::to_nbt_from_inv(&dispenser.inv[..]));
119        }
120        BlockEntity::Spawner(spawner) => {
121            comp.insert("id", "MobSpawner");
122            comp.insert("EntityId", entity_kind_nbt::to_nbt(spawner.entity_kind).unwrap_or(format!("Pig")));
123            comp.insert("Delay", spawner.remaining_time.min(i16::MAX as _) as i16);
124        }
125        BlockEntity::NoteBlock(note_block) => {
126            comp.insert("id", "Music");
127            comp.insert("note", note_block.note);
128        }
129        BlockEntity::Piston(piston) => {
130            comp.insert("id", "Piston");
131            comp.insert("blockId", piston.block as u32);
132            comp.insert("blockData", piston.metadata as u32);
133            comp.insert("facing", match piston.face {
134                Face::NegY => 0i32,
135                Face::PosY => 1,
136                Face::NegZ => 2,
137                Face::PosZ => 3,
138                Face::NegX => 4,
139                Face::PosX => 5,
140            });
141            comp.insert("progress", piston.progress);
142            comp.insert("extending", piston.extending);
143        }
144        BlockEntity::Sign(sign) => {
145            comp.insert("id", "Sign");
146            for (i, key) in ["Text1", "Text2", "Text3", "Text4"].into_iter().enumerate() {
147                comp.insert(key, sign.lines[i].as_str());
148            }
149        }
150        BlockEntity::Jukebox(jukebox) => {
151            comp.insert("id", "RecordPlayer");
152            comp.insert("Record", jukebox.record);
153        }
154    }
155
156    comp
157
158}