Skip to main content

steel_core/inventory/container/
helper.rs

1//! NBT loading shared by containers.
2
3use simdnbt::borrow::NbtCompound as NbtCompoundView;
4use steel_registry::item_stack::ItemStack;
5use steel_utils::nbt::NbtNumeric as _;
6
7/// Loads the `Items` list into `items`, skipping entries that fail to decode
8/// or whose slot is out of range.
9pub fn load_all_items(nbt: NbtCompoundView<'_, '_>, items: &mut [ItemStack]) {
10    let Some(entries) = nbt.list("Items").and_then(|list| list.compounds()) else {
11        return;
12    };
13    for entry in entries {
14        let Some(slot) = item_slot(&entry) else {
15            continue;
16        };
17        if let Some(target) = items.get_mut(slot)
18            && let Some(stack) = ItemStack::from_borrowed_compound(&entry)
19        {
20            *target = stack;
21        }
22    }
23}
24
25/// Reads the unsigned `Slot` byte, which is 0 when absent and `None` when not numeric.
26fn item_slot(entry: &NbtCompoundView<'_, '_>) -> Option<usize> {
27    let Some(tag) = entry.get("Slot") else {
28        return Some(0);
29    };
30    tag.codec_i8().map(|slot| usize::from(slot as u8))
31}
32
33#[cfg(test)]
34mod tests {
35    use std::io::Cursor;
36
37    use simdnbt::borrow::read_compound;
38    use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
39    use steel_registry::{init_vanilla_registry, vanilla_items};
40
41    use super::*;
42
43    fn item_entry(slot: Option<NbtTag>) -> NbtCompound {
44        let mut entry = NbtCompound::new();
45        entry.insert("id", "minecraft:stone");
46        entry.insert("count", 1);
47        if let Some(slot) = slot {
48            entry.insert("Slot", slot);
49        }
50        entry
51    }
52
53    fn load(entries: Vec<NbtCompound>) -> Vec<ItemStack> {
54        init_vanilla_registry();
55        let mut nbt = NbtCompound::new();
56        nbt.insert("Items", NbtList::Compound(entries));
57        let mut bytes = Vec::new();
58        nbt.write(&mut bytes);
59        let borrowed =
60            read_compound(&mut Cursor::new(bytes.as_slice())).expect("test NBT should reborrow");
61
62        let mut items = vec![ItemStack::empty(); 3];
63        load_all_items((&borrowed).into(), &mut items);
64        items
65    }
66
67    #[test]
68    fn missing_slot_defaults_to_zero() {
69        assert!(load(vec![item_entry(None)])[0].is(&vanilla_items::STONE));
70    }
71
72    #[test]
73    fn numeric_slot_narrows_to_a_byte() {
74        let items = load(vec![item_entry(Some(NbtTag::Int(0x102)))]);
75        assert!(items[2].is(&vanilla_items::STONE));
76    }
77
78    #[test]
79    fn negative_slot_is_unsigned_and_out_of_range() {
80        let items = load(vec![item_entry(Some(NbtTag::Byte(-1)))]);
81        assert!(items.iter().all(ItemStack::is_empty));
82    }
83
84    #[test]
85    fn non_numeric_slot_rejects_the_entry() {
86        let items = load(vec![item_entry(Some(NbtTag::String("0".into())))]);
87        assert!(items.iter().all(ItemStack::is_empty));
88    }
89}