Skip to main content

steel_registry/data_components/components/
enchantable.rs

1//! Vanilla `minecraft:enchantable` item component.
2
3use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::io::{Cursor, Error as IoError, Result as IoResult, Write};
6
7use simdnbt::owned::{NbtCompound, NbtTag};
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::codec::VarInt;
10use steel_utils::hash::{ComponentHasher, HashComponent};
11use steel_utils::nbt::NbtNumeric as _;
12use steel_utils::serial::{ReadFrom, WriteTo};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct InvalidEnchantableValue {
16    pub value: i32,
17}
18
19impl Display for InvalidEnchantableValue {
20    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
21        write!(
22            formatter,
23            "Enchantment value must be positive, but was {}",
24            self.value
25        )
26    }
27}
28
29impl Error for InvalidEnchantableValue {}
30
31/// Positive value used to perturb enchanting-table levels.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Enchantable {
34    value: i32,
35}
36
37impl Enchantable {
38    pub const fn new(value: i32) -> Result<Self, InvalidEnchantableValue> {
39        if value <= 0 {
40            return Err(InvalidEnchantableValue { value });
41        }
42        Ok(Self { value })
43    }
44
45    /// Constructs a value already validated by the extracted-item build step.
46    pub(crate) const fn from_extracted_value(value: i32) -> Self {
47        assert!(value > 0, "extracted enchantability must be positive");
48        Self { value }
49    }
50
51    #[must_use]
52    pub const fn value(self) -> i32 {
53        self.value
54    }
55}
56
57impl WriteTo for Enchantable {
58    fn write(&self, writer: &mut impl Write) -> IoResult<()> {
59        VarInt(self.value).write(writer)
60    }
61}
62
63impl ReadFrom for Enchantable {
64    fn read(data: &mut Cursor<&[u8]>) -> IoResult<Self> {
65        Self::new(VarInt::read(data)?.0).map_err(IoError::other)
66    }
67}
68
69impl ToNbtTag for Enchantable {
70    fn to_nbt_tag(self) -> NbtTag {
71        let mut compound = NbtCompound::new();
72        compound.insert("value", self.value);
73        NbtTag::Compound(compound)
74    }
75}
76
77impl FromNbtTag for Enchantable {
78    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
79        let value = tag.compound()?.get("value")?.codec_i32()?;
80        Self::new(value).ok()
81    }
82}
83
84impl HashComponent for Enchantable {
85    fn hash_component(&self, hasher: &mut ComponentHasher) {
86        self.to_nbt_tag().hash_component(hasher);
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use std::io::Cursor;
93
94    use simdnbt::borrow::read_tag;
95    use simdnbt::owned::{NbtCompound, NbtTag};
96    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
97    use steel_utils::codec::VarInt;
98    use steel_utils::hash::HashComponent as _;
99    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
100
101    use super::Enchantable;
102
103    fn parse(tag: NbtTag) -> Option<Enchantable> {
104        let mut bytes = Vec::new();
105        tag.write(&mut bytes);
106        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
107        Enchantable::from_nbt_tag(borrowed.as_tag())
108    }
109
110    #[test]
111    fn persistent_codec_requires_a_positive_value() {
112        for value in [-1, 0] {
113            let mut compound = NbtCompound::new();
114            compound.insert("value", value);
115            assert_eq!(parse(NbtTag::Compound(compound)), None);
116        }
117
118        let mut compound = NbtCompound::new();
119        compound.insert("value", 15_i8);
120        assert_eq!(
121            parse(NbtTag::Compound(compound)),
122            Some(Enchantable::new(15).expect("15 is positive"))
123        );
124    }
125
126    #[test]
127    fn network_codec_uses_a_validated_varint() {
128        let value = Enchantable::new(22).expect("22 is positive");
129        let mut encoded = Vec::new();
130        value.write(&mut encoded).expect("value should encode");
131        assert_eq!(
132            Enchantable::read(&mut Cursor::new(encoded.as_slice())).expect("value should decode"),
133            value
134        );
135
136        let mut zero = Vec::new();
137        VarInt(0).write(&mut zero).expect("zero should encode");
138        assert!(Enchantable::read(&mut Cursor::new(zero.as_slice())).is_err());
139    }
140
141    #[test]
142    fn persistent_hash_uses_the_record_codec_shape() {
143        let value = Enchantable::new(15).expect("15 is positive");
144        assert_eq!(value.compute_hash(), value.to_nbt_tag().compute_hash());
145    }
146}