Skip to main content

steel_core/inventory/slots/
result_slot.rs

1use std::{
2    mem,
3    sync::{
4        Arc,
5        atomic::{AtomicI32, Ordering},
6    },
7};
8
9use steel_registry::{item_stack::ItemStack, stat::vanilla_stat_types};
10use steel_utils::{DowncastType, DowncastTypeKey};
11
12use crate::{
13    inventory::{
14        lock::{ContainerLockGuard, ContainerRef},
15        slots::{ResultHandler, Slot, SlotStorage},
16    },
17    player::Player,
18};
19
20/// A fake Slot that contains the resulting item of f.e. a craft
21pub struct ResultSlot {
22    handler: Arc<dyn ResultHandler + Send + Sync>,
23    storage: SlotStorage,
24    remove_count: Option<AtomicI32>,
25}
26
27// SAFETY: This key uniquely identifies Steel's `ResultSlot`.
28unsafe impl DowncastType for ResultSlot {
29    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:slot/result");
30}
31
32impl ResultSlot {
33    /// Creates a result slot that does not award `ITEM_CRAFTED`.
34    pub fn new(handler: impl ResultHandler + 'static) -> Self {
35        Self::with_remove_count(handler, None)
36    }
37
38    /// Creates a crafting result slot whose taken items award `ITEM_CRAFTED`.
39    pub fn crafting(handler: impl ResultHandler + 'static) -> Self {
40        Self::with_remove_count(handler, Some(AtomicI32::new(0)))
41    }
42
43    fn with_remove_count(
44        handler: impl ResultHandler + 'static,
45        remove_count: Option<AtomicI32>,
46    ) -> Self {
47        let result_container = handler.result_container();
48        let storage =
49            SlotStorage::physical(result_container, 0).with_dependencies(handler.dependencies());
50        Self {
51            handler: Arc::new(handler),
52            storage,
53            remove_count,
54        }
55    }
56
57    /// Vanilla also runs `ItemStack.onCraftedBy` here. Its only override is
58    /// `MapItem`, and Steel does not implement maps.
59    fn check_take_achievements(&self, stack: &ItemStack, player: &Player) {
60        let Some(remove_count) = &self.remove_count else {
61            return;
62        };
63        let count = remove_count.swap(0, Ordering::Relaxed);
64        if count > 0 {
65            player.award_stat_with_count(&vanilla_stat_types::ITEM_CRAFTED, stack.item(), count);
66        }
67    }
68
69    /// Returns the result container this slot derived from its handler.
70    pub(crate) fn result_container(&self) -> &ContainerRef {
71        let Some((container, _)) = self.storage.physical_backing() else {
72            unreachable!("ResultSlot always has physical storage");
73        };
74        container
75    }
76}
77
78impl Slot for ResultSlot {
79    fn storage(&self) -> &SlotStorage {
80        &self.storage
81    }
82
83    fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack {
84        guard
85            .get(self.result_container().container_id())
86            .expect("failed to get item from result container")
87            .get_item(0)
88    }
89
90    fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack {
91        guard
92            .get_mut(self.result_container().container_id())
93            .expect("failed to get item mutabily from result container")
94            .get_item_mut(0)
95    }
96
97    fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack) {
98        guard
99            .get_mut(self.result_container().container_id())
100            .expect("failed to get item mutabily from result container")
101            .set_item(0, stack);
102    }
103
104    fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32 {
105        guard
106            .get(self.result_container().container_id())
107            .expect("result container not locked")
108            .get_max_stack_size()
109    }
110
111    fn set_changed(&self, guard: &mut ContainerLockGuard) {
112        guard
113            .get_mut(self.result_container().container_id())
114            .expect("result container not locked")
115            .set_changed();
116    }
117
118    fn get_container_slot(&self) -> usize {
119        0
120    }
121
122    fn on_take(
123        &self,
124        guard: &mut ContainerLockGuard,
125        stack: &ItemStack,
126        player: &Player,
127    ) -> Option<ItemStack> {
128        self.check_take_achievements(stack, player);
129        self.handler.on_result_taken(guard, player)
130    }
131
132    fn remove(&self, guard: &mut ContainerLockGuard, amount: i32) -> ItemStack {
133        if let Some(remove_count) = &self.remove_count {
134            let count = self.get_item(guard).count();
135            if count > 0 {
136                remove_count.fetch_add(amount.min(count), Ordering::Relaxed);
137            }
138        }
139        mem::take(self.get_item_mut(guard))
140    }
141
142    fn on_quick_craft(&self, stack: &ItemStack, player: &Player) {
143        let Some(remove_count) = &self.remove_count else {
144            return;
145        };
146        remove_count.fetch_add(stack.count(), Ordering::Relaxed);
147        self.check_take_achievements(stack, player);
148    }
149
150    fn on_swap_craft(&self, count: i32) {
151        if let Some(remove_count) = &self.remove_count {
152            remove_count.fetch_add(count, Ordering::Relaxed);
153        }
154    }
155
156    fn is_fake(&self) -> bool {
157        true
158    }
159
160    fn allow_modification(&self, _guard: &ContainerLockGuard, _player: &Player) -> bool {
161        false
162    }
163
164    fn may_place(&self, _stack: &ItemStack) -> bool {
165        false
166    }
167
168    fn may_pickup(&self, guard: &ContainerLockGuard, player: &Player) -> bool {
169        self.handler.is_result_valid(guard, player)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use steel_utils::locks::IntoShared;
176
177    use super::*;
178    use crate::inventory::container::SimpleContainer;
179
180    struct NoopResultHandler(ContainerRef);
181
182    impl ResultHandler for NoopResultHandler {
183        fn result_container(&self) -> ContainerRef {
184            self.0.clone()
185        }
186
187        fn dependencies(&self) -> Vec<ContainerRef> {
188            Vec::new()
189        }
190
191        fn update_result(&self, _guard: &mut ContainerLockGuard) {}
192
193        fn on_result_taken(
194            &self,
195            _guard: &mut ContainerLockGuard,
196            _player: &Player,
197        ) -> Option<ItemStack> {
198            None
199        }
200
201        fn is_result_valid(&self, _guard: &ContainerLockGuard, _player: &Player) -> bool {
202            true
203        }
204    }
205
206    #[test]
207    fn constructor_uses_the_handlers_result_container() {
208        let container = ContainerRef::from(SimpleContainer::new(1).into_shared());
209        let expected = container.container_id();
210
211        let slot = ResultSlot::new(NoopResultHandler(container));
212
213        assert_eq!(slot.storage().physical_key(), Some((expected, 0)));
214    }
215}