Skip to main content

steel_core/inventory/slots/
furnace_slots.rs

1//! Furnace-specific input, fuel, and result slots.
2
3use std::sync::atomic::{AtomicI32, Ordering};
4
5use steel_registry::item_stack::ItemStack;
6use steel_registry::stat::vanilla_stat_types;
7use steel_registry::vanilla_items;
8use steel_utils::{DowncastType, DowncastTypeKey};
9
10use crate::block_entity::entities::{FurnaceContainer, pop_furnace_experience};
11use crate::entity::Entity as _;
12use crate::inventory::fuel_values::VANILLA_FUEL_VALUES;
13use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
14use crate::inventory::slots::{NormalSlot, Slot, SlotStorage};
15use crate::player::Player;
16
17/// Accepts furnace fuel and empty buckets, with Vanilla's bucket stack limit.
18pub struct FurnaceFuelSlot {
19    base: NormalSlot,
20}
21
22// SAFETY: This Steel-owned key uniquely identifies furnace fuel slots.
23unsafe impl DowncastType for FurnaceFuelSlot {
24    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:slot/furnace_fuel");
25}
26
27impl FurnaceFuelSlot {
28    /// Creates a furnace fuel slot.
29    #[must_use]
30    pub fn new(container: impl Into<ContainerRef>, index: usize) -> Self {
31        Self {
32            base: NormalSlot::new(container, index),
33        }
34    }
35}
36
37impl Slot for FurnaceFuelSlot {
38    fn storage(&self) -> &SlotStorage {
39        self.base.storage()
40    }
41
42    fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack {
43        self.base.get_item(guard)
44    }
45
46    fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack {
47        self.base.get_item_mut(guard)
48    }
49
50    fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack) {
51        self.base.set_item(guard, stack);
52    }
53
54    fn may_place(&self, stack: &ItemStack) -> bool {
55        VANILLA_FUEL_VALUES.is_fuel(stack.item()) || stack.is(&vanilla_items::BUCKET)
56    }
57
58    fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32 {
59        self.base.get_max_stack_size(guard)
60    }
61
62    fn get_max_stack_size_for_item(&self, guard: &ContainerLockGuard, stack: &ItemStack) -> i32 {
63        if stack.is(&vanilla_items::BUCKET) {
64            1
65        } else {
66            self.base.get_max_stack_size_for_item(guard, stack)
67        }
68    }
69
70    fn set_changed(&self, guard: &mut ContainerLockGuard) {
71        self.base.set_changed(guard);
72    }
73
74    fn get_container_slot(&self) -> usize {
75        self.base.get_container_slot()
76    }
77}
78
79/// Rejects insertion and awards accumulated cooking XP and crafted stats when
80/// output is taken.
81///
82/// Output taken by a hotbar swap awards no crafted stat, matching vanilla.
83pub struct FurnaceResultSlot {
84    base: NormalSlot,
85    remove_count: AtomicI32,
86}
87
88// SAFETY: This Steel-owned key uniquely identifies furnace result slots.
89unsafe impl DowncastType for FurnaceResultSlot {
90    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:slot/furnace_result");
91}
92
93impl FurnaceResultSlot {
94    /// Creates a furnace result slot.
95    #[must_use]
96    pub fn new(container: impl Into<ContainerRef>, index: usize) -> Self {
97        Self {
98            base: NormalSlot::new(container, index),
99            remove_count: AtomicI32::new(0),
100        }
101    }
102
103    fn check_take_achievements(&self, stack: &ItemStack, player: &Player) {
104        let count = self.remove_count.swap(0, Ordering::Relaxed);
105        if count > 0 {
106            player.award_stat_with_count(&vanilla_stat_types::ITEM_CRAFTED, stack.item(), count);
107        }
108    }
109}
110
111impl Slot for FurnaceResultSlot {
112    fn storage(&self) -> &SlotStorage {
113        self.base.storage()
114    }
115
116    fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack {
117        self.base.get_item(guard)
118    }
119
120    fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack {
121        self.base.get_item_mut(guard)
122    }
123
124    fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack) {
125        self.base.set_item(guard, stack);
126    }
127
128    fn may_place(&self, _stack: &ItemStack) -> bool {
129        false
130    }
131
132    fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32 {
133        self.base.get_max_stack_size(guard)
134    }
135
136    fn set_changed(&self, guard: &mut ContainerLockGuard) {
137        self.base.set_changed(guard);
138    }
139
140    fn get_container_slot(&self) -> usize {
141        self.base.get_container_slot()
142    }
143
144    fn on_quick_craft(&self, stack: &ItemStack, player: &Player) {
145        self.remove_count
146            .fetch_add(stack.count(), Ordering::Relaxed);
147        self.check_take_achievements(stack, player);
148    }
149
150    fn remove(&self, guard: &mut ContainerLockGuard, amount: i32) -> ItemStack {
151        let item = self.get_item_mut(guard);
152        if item.is_empty() || amount <= 0 {
153            return ItemStack::empty();
154        }
155        let removed = item.split(amount);
156        self.remove_count
157            .fetch_add(removed.count(), Ordering::Relaxed);
158        removed
159    }
160
161    fn on_take(
162        &self,
163        guard: &mut ContainerLockGuard,
164        stack: &ItemStack,
165        player: &Player,
166    ) -> Option<ItemStack> {
167        self.check_take_achievements(stack, player);
168        // TODO: Award used recipes and trigger RECIPE_CRAFTED once supported.
169        let (container, _) = self.storage().physical_backing()?;
170        let container_id = container.container_id();
171        let recipes = guard
172            .get_typed_mut::<FurnaceContainer>(container_id)
173            .map(FurnaceContainer::take_recipes_used);
174        self.base.set_changed(guard);
175        if let Some(recipes) = recipes {
176            guard.run_unlocked(|| {
177                pop_furnace_experience(&player.get_world(), player.position(), recipes);
178            });
179        }
180        None
181    }
182}