Skip to main content

steel_registry/items/
mod.rs

1use std::sync::OnceLock;
2
3use rustc_hash::FxHashMap;
4
5use steel_utils::Identifier;
6
7pub mod item;
8
9use crate::{
10    REGISTRY, RegistryExt, TaggedRegistryExt, blocks::BlockRef, data_components::DataComponentMap,
11    item_stack::ItemStack,
12};
13
14/// A Minecraft item type.
15pub struct Item {
16    pub key: Identifier,
17    pub components: DataComponentMap,
18    /// The item key returned when this item is used in crafting (e.g., "bucket" from milk_bucket).
19    /// Stored as an Identifier to avoid circular reference issues during initialization.
20    pub craft_remainder: Option<Identifier>,
21    /// Cached registry ID, set during registration for O(1) lookup on hot paths.
22    pub id: OnceLock<usize>,
23}
24
25impl std::fmt::Debug for Item {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_struct("Item").field("key", &self.key).finish()
28    }
29}
30
31impl Item {
32    #[must_use]
33    pub fn from_block(block: BlockRef) -> Self {
34        Self {
35            key: block.key.clone(),
36            components: DataComponentMap::common_item_components(),
37            craft_remainder: None,
38            id: OnceLock::new(),
39        }
40    }
41
42    #[must_use]
43    pub fn from_block_custom_name(_block: BlockRef, name: &'static str) -> Self {
44        Self {
45            key: Identifier::vanilla_static(name),
46            components: DataComponentMap::common_item_components(),
47            craft_remainder: None,
48            id: OnceLock::new(),
49        }
50    }
51
52    /// Builder method to set a component on this item. Used during static initialization.
53    #[must_use]
54    pub fn builder_set<T: crate::data_components::Component>(
55        mut self,
56        component: crate::data_components::DataComponentType<T>,
57        value: Option<T>,
58    ) -> Self {
59        self.components.set(component, value);
60        self
61    }
62
63    /// Returns the item stack that remains after this item is used in crafting.
64    /// For example, milk_bucket returns an empty bucket.
65    #[must_use]
66    pub fn get_crafting_remainder(&self) -> ItemStack {
67        match &self.craft_remainder {
68            Some(remainder_key) => {
69                if let Some(remainder_item) = REGISTRY.items.by_key(remainder_key) {
70                    ItemStack::new(remainder_item)
71                } else {
72                    ItemStack::empty()
73                }
74            }
75            None => ItemStack::empty(),
76        }
77    }
78
79    /// Returns `true` if this item is tagged with the given tag.
80    pub fn has_tag(&'static self, tag: &Identifier) -> bool {
81        REGISTRY.items.is_in_tag(self, tag)
82    }
83}
84
85pub type ItemRef = &'static Item;
86
87impl PartialEq for ItemRef {
88    #[expect(clippy::disallowed_methods)] // This IS the PartialEq impl; ptr::eq is correct here
89    fn eq(&self, other: &Self) -> bool {
90        std::ptr::eq(*self, *other)
91    }
92}
93
94impl Eq for ItemRef {}
95
96pub struct ItemRegistry {
97    items_by_id: Vec<ItemRef>,
98    items_by_key: FxHashMap<Identifier, usize>,
99    tags: FxHashMap<Identifier, Vec<Identifier>>,
100    allows_registering: bool,
101}
102
103impl Default for ItemRegistry {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl ItemRegistry {
110    #[must_use]
111    pub fn new() -> Self {
112        Self {
113            items_by_id: Vec::new(),
114            items_by_key: FxHashMap::default(),
115            tags: FxHashMap::default(),
116            allows_registering: true,
117        }
118    }
119
120    pub fn register(&mut self, item: ItemRef) -> usize {
121        assert!(
122            self.allows_registering,
123            "Cannot register items after the registry has been frozen"
124        );
125
126        let id = self.items_by_id.len();
127        let cached = item.id.get_or_init(|| id);
128        assert_eq!(*cached, id, "item registered with conflicting id");
129        self.items_by_key.insert(item.key.clone(), id);
130        self.items_by_id.push(item);
131
132        id
133    }
134
135    pub fn iter(&self) -> impl Iterator<Item = (usize, ItemRef)> + '_ {
136        self.items_by_id
137            .iter()
138            .enumerate()
139            .map(|(id, &item)| (id, item))
140    }
141}
142
143crate::impl_registry_ext!(ItemRegistry, Item, items_by_id, items_by_key);
144crate::impl_tagged_registry!(ItemRegistry, items_by_key, "item");
145
146impl crate::RegistryEntry for Item {
147    fn key(&self) -> &Identifier {
148        &self.key
149    }
150
151    fn try_id(&self) -> Option<usize> {
152        self.id.get().copied()
153    }
154}