steel_registry/
enchantment.rs1use crate::items::ItemRef;
2pub use crate::loot_table::EquipmentSlotGroup;
3use crate::{REGISTRY, RegistryEntry, RegistryExt, TaggedRegistryExt};
4use rustc_hash::FxHashMap;
5use simdnbt::ToNbtTag;
6use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
7use steel_utils::Identifier;
8
9#[derive(Debug, Clone, Copy)]
11pub struct EnchantmentCost {
12 pub base: i32,
13 pub per_level_above_first: i32,
14}
15
16#[derive(Debug)]
17pub struct Enchantment {
18 pub key: Identifier,
19 pub max_level: u32,
20 pub min_cost: EnchantmentCost,
21 pub max_cost: EnchantmentCost,
22 pub anvil_cost: i32,
23 pub weight: u32,
24 pub slots: &'static [EquipmentSlotGroup],
25 pub supported_items: &'static str,
26 pub primary_items: Option<&'static str>,
27 pub exclusive_set: Option<&'static str>,
28 }
30
31impl RegistryEntry for Enchantment {
32 fn key(&self) -> &Identifier {
33 &self.key
34 }
35
36 fn try_id(&self) -> Option<usize> {
37 REGISTRY.enchantments.id_from_key(&self.key)
38 }
39}
40
41impl ToNbtTag for &Enchantment {
42 fn to_nbt_tag(self) -> NbtTag {
43 let mut compound = NbtCompound::new();
44
45 let mut desc = NbtCompound::new();
47 desc.insert(
48 "translate",
49 format!("enchantment.{}.{}", self.key.namespace, self.key.path).as_str(),
50 );
51 compound.insert("description", NbtTag::Compound(desc));
52
53 compound.insert("supported_items", self.supported_items);
55 if let Some(primary) = self.primary_items {
56 compound.insert("primary_items", primary);
57 }
58 compound.insert("weight", self.weight as i32);
59 compound.insert("max_level", self.max_level as i32);
60
61 let mut min_cost = NbtCompound::new();
62 min_cost.insert("base", self.min_cost.base);
63 min_cost.insert("per_level_above_first", self.min_cost.per_level_above_first);
64 compound.insert("min_cost", NbtTag::Compound(min_cost));
65
66 let mut max_cost = NbtCompound::new();
67 max_cost.insert("base", self.max_cost.base);
68 max_cost.insert("per_level_above_first", self.max_cost.per_level_above_first);
69 compound.insert("max_cost", NbtTag::Compound(max_cost));
70
71 compound.insert("anvil_cost", self.anvil_cost);
72
73 let slots: Vec<String> = self.slots.iter().map(|s| s.as_str().to_owned()).collect();
74 compound.insert("slots", NbtTag::List(NbtList::from(slots)));
75
76 if let Some(exclusive) = self.exclusive_set {
77 compound.insert("exclusive_set", exclusive);
78 }
79
80 NbtTag::Compound(compound)
83 }
84}
85
86fn parse_tag_ref(tag_ref: &str) -> Option<Identifier> {
88 let without_hash = tag_ref.strip_prefix('#')?;
89 Some(if let Some((ns, path)) = without_hash.split_once(':') {
90 Identifier::new(ns.to_owned(), path.to_owned())
91 } else {
92 Identifier::vanilla(without_hash.to_owned())
93 })
94}
95
96impl Enchantment {
97 pub fn can_enchant(&self, item: ItemRef) -> bool {
99 let Some(tag) = parse_tag_ref(self.supported_items) else {
100 return false;
101 };
102 REGISTRY.items.is_in_tag(item, &tag)
103 }
104
105 pub fn are_compatible(a: EnchantmentRef, b: EnchantmentRef) -> bool {
107 if a == b {
108 return false;
109 }
110 if let Some(set) = a.exclusive_set
111 && let Some(tag) = parse_tag_ref(set)
112 && REGISTRY.enchantments.is_in_tag(b, &tag)
113 {
114 return false;
115 }
116 if let Some(set) = b.exclusive_set
117 && let Some(tag) = parse_tag_ref(set)
118 && REGISTRY.enchantments.is_in_tag(a, &tag)
119 {
120 return false;
121 }
122 true
123 }
124
125 pub fn is_compatible_with_existing(
127 enchantment: EnchantmentRef,
128 item: &crate::item_stack::ItemStack,
129 ) -> bool {
130 let Some(enchantments) = item.get_enchantments() else {
131 return true;
132 };
133 for (existing_key, _) in enchantments.iter() {
134 if *existing_key == enchantment.key {
135 continue;
136 }
137 let Some(existing) = REGISTRY.enchantments.by_key(existing_key) else {
138 continue;
139 };
140 if !Self::are_compatible(enchantment, existing) {
141 return false;
142 }
143 }
144 true
145 }
146}
147
148pub type EnchantmentRef = &'static Enchantment;
149
150impl PartialEq for EnchantmentRef {
151 #[expect(clippy::disallowed_methods)] fn eq(&self, other: &Self) -> bool {
153 std::ptr::eq(*self, *other)
154 }
155}
156
157impl Eq for EnchantmentRef {}
158
159pub struct EnchantmentRegistry {
160 enchantments_by_id: Vec<EnchantmentRef>,
161 enchantments_by_key: FxHashMap<Identifier, usize>,
162 tags: FxHashMap<Identifier, Vec<Identifier>>,
163 allows_registering: bool,
164}
165
166impl EnchantmentRegistry {
167 #[must_use]
168 pub fn new() -> Self {
169 Self {
170 enchantments_by_id: Vec::new(),
171 enchantments_by_key: FxHashMap::default(),
172 tags: FxHashMap::default(),
173 allows_registering: true,
174 }
175 }
176}
177
178crate::impl_registry_ext!(
179 EnchantmentRegistry,
180 Enchantment,
181 enchantments_by_id,
182 enchantments_by_key
183);
184
185crate::impl_standard_methods!(
186 EnchantmentRegistry,
187 EnchantmentRef,
188 enchantments_by_id,
189 enchantments_by_key,
190 allows_registering
191);
192
193crate::impl_tagged_registry!(EnchantmentRegistry, enchantments_by_key, "enchantment");