steel_registry/data_components/components/
villager_food.rs1use 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#[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 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}