steel_registry/data_components/components/
enchantments.rs1use rustc_hash::FxHashMap;
2use simdnbt::owned::{NbtCompound, NbtTag};
3use simdnbt::{FromNbtTag, ToNbtTag};
4use steel_utils::Identifier;
5use steel_utils::codec::VarInt;
6use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
7use steel_utils::serial::{ReadFrom, WriteTo};
8
9use crate::{REGISTRY, RegistryExt};
10
11#[derive(Debug, Clone, PartialEq)]
18pub struct ItemEnchantments {
19 pub levels: FxHashMap<Identifier, u32>,
20}
21
22impl ItemEnchantments {
23 #[must_use]
24 pub fn empty() -> Self {
25 Self {
26 levels: FxHashMap::default(),
27 }
28 }
29
30 #[must_use]
31 pub fn get_level(&self, enchantment: &Identifier) -> u32 {
32 self.levels.get(enchantment).copied().unwrap_or(0)
33 }
34
35 pub fn set(&mut self, enchantment: Identifier, level: u32) {
36 if level == 0 {
37 self.levels.remove(&enchantment);
38 } else {
39 self.levels.insert(enchantment, level);
40 }
41 }
42
43 pub fn upgrade(&mut self, enchantment: Identifier, level: u32) {
45 if level > 0 {
46 let existing = self.get_level(&enchantment);
47 self.levels
48 .insert(enchantment, existing.max(level).min(255));
49 }
50 }
51
52 #[must_use]
53 pub fn is_empty(&self) -> bool {
54 self.levels.is_empty()
55 }
56
57 #[must_use]
58 pub fn len(&self) -> usize {
59 self.levels.len()
60 }
61
62 pub fn iter(&self) -> impl Iterator<Item = (&Identifier, &u32)> {
63 self.levels.iter()
64 }
65}
66
67impl Default for ItemEnchantments {
68 fn default() -> Self {
69 Self::empty()
70 }
71}
72
73impl WriteTo for ItemEnchantments {
75 fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
76 VarInt(self.levels.len() as i32).write(writer)?;
77 for (key, &level) in &self.levels {
78 let id = REGISTRY
79 .enchantments
80 .id_from_key(key)
81 .ok_or_else(|| std::io::Error::other(format!("Unknown enchantment: {key}")))?;
82 VarInt(id as i32).write(writer)?;
83 VarInt(level as i32).write(writer)?;
84 }
85 Ok(())
86 }
87}
88
89impl ReadFrom for ItemEnchantments {
90 fn read(data: &mut std::io::Cursor<&[u8]>) -> std::io::Result<Self> {
91 let count = VarInt::read(data)?.0;
92 if !(0..=256).contains(&count) {
93 return Err(std::io::Error::other(format!(
94 "Enchantment count out of range: {count}"
95 )));
96 }
97 let count = count as usize;
98 let mut levels = FxHashMap::default();
99 for _ in 0..count {
100 let id = VarInt::read(data)?.0 as usize;
101 let level = VarInt::read(data)?.0 as u32;
102 let enchantment = REGISTRY
103 .enchantments
104 .by_id(id)
105 .ok_or_else(|| std::io::Error::other(format!("Unknown enchantment id: {id}")))?;
106 levels.insert(enchantment.key.clone(), level);
107 }
108 Ok(Self { levels })
109 }
110}
111
112impl ToNbtTag for ItemEnchantments {
114 fn to_nbt_tag(self) -> NbtTag {
115 let mut compound = NbtCompound::new();
116 for (key, level) in &self.levels {
117 compound.insert(key.to_string(), NbtTag::Int(*level as i32));
118 }
119 NbtTag::Compound(compound)
120 }
121}
122
123impl FromNbtTag for ItemEnchantments {
124 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
125 let compound = tag.compound()?;
126 let mut levels = FxHashMap::default();
127 for (key, value) in compound.iter() {
128 let key_str = key.to_str();
129 if let Ok(ident) = key_str.parse::<Identifier>()
130 && let Some(level) = value.int()
131 && level > 0
132 {
133 levels.insert(ident, level as u32);
134 }
135 }
136 Some(Self { levels })
137 }
138}
139
140impl HashComponent for ItemEnchantments {
141 fn hash_component(&self, hasher: &mut ComponentHasher) {
142 hasher.start_map();
143 let mut entries: Vec<_> = self
144 .levels
145 .iter()
146 .map(|(key, &level)| {
147 let mut key_hasher = ComponentHasher::new();
148 key_hasher.put_string(&key.to_string());
149 let mut value_hasher = ComponentHasher::new();
150 value_hasher.put_int(level as i32);
151 HashEntry::new(key_hasher, value_hasher)
152 })
153 .collect();
154 sort_map_entries(&mut entries);
155 for entry in &entries {
156 hasher.put_raw_bytes(&entry.key_bytes);
157 hasher.put_raw_bytes(&entry.value_bytes);
158 }
159 hasher.end_map();
160 }
161}