steel_core/inventory/slots/
result_slot.rs1use 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
20pub struct ResultSlot {
22 handler: Arc<dyn ResultHandler + Send + Sync>,
23 storage: SlotStorage,
24 remove_count: Option<AtomicI32>,
25}
26
27unsafe impl DowncastType for ResultSlot {
29 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:slot/result");
30}
31
32impl ResultSlot {
33 pub fn new(handler: impl ResultHandler + 'static) -> Self {
35 Self::with_remove_count(handler, None)
36 }
37
38 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 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 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}