Skip to main content

steel_registry/data_components/components/
villager_food.rs

1//! Vanilla `minecraft:villager_food` item component.
2
3use std::io::{Cursor, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry};
9use steel_utils::nbt::NbtNumeric as _;
10use steel_utils::serial::{ReadFrom, WriteTo};
11
12/// How much a villager's breeding food level is raised by consuming this item.
13///
14/// Vanilla: `ExtraCodecs.POSITIVE_INT.fieldOf("nutrition")` persistent,
15/// `ByteBufCodecs.VAR_INT` on the network (no range check on decode there,
16/// matching `VillagerFood`'s public constructor).
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct VillagerFood {
19    nutrition: i32,
20}
21
22impl VillagerFood {
23    #[must_use]
24    pub const fn new(nutrition: i32) -> Self {
25        Self { nutrition }
26    }
27
28    #[must_use]
29    pub const fn nutrition(self) -> i32 {
30        self.nutrition
31    }
32}
33
34impl WriteTo for VillagerFood {
35    fn write(&self, writer: &mut impl Write) -> Result<()> {
36        VarInt(self.nutrition).write(writer)
37    }
38}
39
40impl ReadFrom for VillagerFood {
41    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
42        Ok(Self::new(VarInt::read(data)?.0))
43    }
44}
45
46impl ToNbtTag for VillagerFood {
47    fn to_nbt_tag(self) -> NbtTag {
48        let mut compound = NbtCompound::new();
49        compound.insert("nutrition", NbtTag::Int(self.nutrition));
50        NbtTag::Compound(compound)
51    }
52}
53
54impl FromNbtTag for VillagerFood {
55    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
56        let compound = tag.compound()?;
57        let nutrition = compound.get("nutrition")?.codec_i32()?;
58        (nutrition > 0).then(|| Self::new(nutrition))
59    }
60}
61
62impl HashComponent for VillagerFood {
63    fn hash_component(&self, hasher: &mut ComponentHasher) {
64        let mut key_hasher = ComponentHasher::new();
65        key_hasher.put_string("nutrition");
66        let mut value_hasher = ComponentHasher::new();
67        value_hasher.put_int(self.nutrition);
68        let entry = HashEntry::new(key_hasher, value_hasher);
69
70        hasher.start_map();
71        hasher.put_raw_bytes(&entry.key_bytes);
72        hasher.put_raw_bytes(&entry.value_bytes);
73        hasher.end_map();
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use std::io::Cursor;
80
81    use simdnbt::FromNbtTag as _;
82    use simdnbt::ToNbtTag as _;
83    use simdnbt::borrow::read_tag;
84    use simdnbt::owned::{NbtCompound, NbtTag};
85    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
86
87    use super::VillagerFood;
88
89    fn parse(tag: NbtTag) -> Option<VillagerFood> {
90        let mut bytes = Vec::new();
91        tag.write(&mut bytes);
92        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
93        VillagerFood::from_nbt_tag(borrowed.as_tag())
94    }
95
96    fn compound_with(nutrition: NbtTag) -> NbtTag {
97        let mut compound = NbtCompound::new();
98        compound.insert("nutrition", nutrition);
99        NbtTag::Compound(compound)
100    }
101
102    #[test]
103    fn persistent_codec_round_trips_positive_nutrition() {
104        let value = VillagerFood::new(4);
105        assert_eq!(parse(value.to_nbt_tag()), Some(value));
106    }
107
108    #[test]
109    fn persistent_codec_rejects_non_positive_nutrition() {
110        assert_eq!(parse(compound_with(NbtTag::Int(0))), None);
111        assert_eq!(parse(compound_with(NbtTag::Int(-1))), None);
112    }
113
114    #[test]
115    fn persistent_codec_accepts_any_integral_nbt_tag_type() {
116        // `ExtraCodecs.POSITIVE_INT` reads through `Codec.INT`, which simdnbt's
117        // `codec_i32` mirrors by widening from any integral tag.
118        assert_eq!(
119            parse(compound_with(NbtTag::Byte(4))),
120            Some(VillagerFood::new(4))
121        );
122        assert_eq!(
123            parse(compound_with(NbtTag::Long(4))),
124            Some(VillagerFood::new(4))
125        );
126    }
127
128    #[test]
129    fn network_codec_round_trips_varint() {
130        let value = VillagerFood::new(4);
131        let mut network = Vec::new();
132        value.write(&mut network).expect("food should encode");
133        assert_eq!(
134            VillagerFood::read(&mut Cursor::new(network.as_slice())).expect("food should decode"),
135            value
136        );
137    }
138}