steel_core/inventory/slots/
slot.rs1use crate::player::player_inventory::Prediction;
4use steel_registry::item_stack::ItemStack;
5use steel_utils::ErasedType;
6
7use crate::inventory::lock::{ContainerId, ContainerLockGuard, ContainerRef};
8use crate::player::Player;
9
10pub struct SlotStorage {
16 physical: Option<(ContainerRef, usize)>,
17 dependencies: Vec<ContainerRef>,
18}
19
20impl SlotStorage {
21 #[must_use]
23 pub fn physical(container: impl Into<ContainerRef>, index: usize) -> Self {
24 Self {
25 physical: Some((container.into(), index)),
26 dependencies: Vec::new(),
27 }
28 }
29
30 #[must_use]
32 pub fn virtual_slot(dependencies: impl IntoIterator<Item = impl Into<ContainerRef>>) -> Self {
33 Self {
34 physical: None,
35 dependencies: dependencies.into_iter().map(Into::into).collect(),
36 }
37 }
38
39 #[must_use]
41 pub fn with_dependencies(
42 mut self,
43 dependencies: impl IntoIterator<Item = impl Into<ContainerRef>>,
44 ) -> Self {
45 self.dependencies
46 .extend(dependencies.into_iter().map(Into::into));
47 self
48 }
49
50 #[must_use]
52 pub fn physical_backing(&self) -> Option<(&ContainerRef, usize)> {
53 self.physical
54 .as_ref()
55 .map(|(container, index)| (container, *index))
56 }
57
58 #[must_use]
60 pub fn physical_key(&self) -> Option<(ContainerId, usize)> {
61 self.physical_backing()
62 .map(|(container, index)| (container.container_id(), index))
63 }
64
65 pub(crate) fn container_refs(&self) -> impl Iterator<Item = &ContainerRef> {
66 self.physical
67 .iter()
68 .map(|(container, _)| container)
69 .chain(self.dependencies.iter())
70 }
71}
72
73pub trait Slot: ErasedType + Send + Sync {
80 fn storage(&self) -> &SlotStorage;
82
83 fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack;
85
86 fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack;
88
89 fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack);
91
92 fn set_by_player(
94 &self,
95 guard: &mut ContainerLockGuard,
96 stack: ItemStack,
97 _previous: &ItemStack,
98 ) {
99 self.set_item(guard, stack);
100 }
101
102 fn has_item(&self, guard: &ContainerLockGuard) -> bool {
104 !self.get_item(guard).is_empty()
105 }
106
107 fn may_place(&self, _stack: &ItemStack) -> bool {
109 true
110 }
111
112 fn may_pickup(&self, _guard: &ContainerLockGuard, _player: &Player) -> bool {
114 true
115 }
116
117 fn allow_modification(&self, guard: &ContainerLockGuard, player: &Player) -> bool {
119 self.may_pickup(guard, player) && self.may_place(self.get_item(guard))
120 }
121
122 fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32;
124
125 fn get_max_stack_size_for_item(&self, guard: &ContainerLockGuard, stack: &ItemStack) -> i32 {
127 self.get_max_stack_size(guard).min(stack.max_stack_size())
128 }
129
130 fn remove(&self, guard: &mut ContainerLockGuard, amount: i32) -> ItemStack {
132 let item = self.get_item_mut(guard);
133 if item.is_empty() || amount <= 0 {
134 return ItemStack::empty();
135 }
136 item.split(amount)
137 }
138
139 fn try_remove(
141 &self,
142 guard: &mut ContainerLockGuard,
143 amount: i32,
144 max_amount: i32,
145 player: &Player,
146 ) -> Option<ItemStack> {
147 if !self.may_pickup(guard, player) {
148 return None;
149 }
150
151 let item_count = self.get_item(guard).count();
152
153 if !self.allow_modification(guard, player) && max_amount < item_count {
154 return None;
155 }
156
157 let take_amount = amount.min(max_amount);
158 let result = self.remove(guard, take_amount);
159 if result.is_empty() {
160 return None;
161 }
162
163 if self.get_item(guard).is_empty() {
164 self.set_by_player(guard, ItemStack::empty(), &result);
165 }
166
167 Some(result)
168 }
169
170 fn on_take(
172 &self,
173 guard: &mut ContainerLockGuard,
174 _stack: &ItemStack,
175 _player: &Player,
176 ) -> Option<ItemStack> {
177 self.set_changed(guard);
178 None
179 }
180
181 fn on_swap_craft(&self, _count: i32) {}
183
184 fn on_quick_craft(&self, _stack: &ItemStack, _player: &Player) {}
186
187 fn safe_take(
189 &self,
190 guard: &mut ContainerLockGuard,
191 amount: i32,
192 max_amount: i32,
193 player: &Player,
194 ) -> ItemStack {
195 if let Some(taken) = self.try_remove(guard, amount, max_amount, player) {
196 if let Some(remainder) = self.on_take(guard, &taken, player) {
197 player.add_item_or_drop_with_guard(guard, remainder, Prediction::Predicted);
198 }
199 taken
200 } else {
201 ItemStack::empty()
202 }
203 }
204
205 fn safe_insert(
207 &self,
208 guard: &mut ContainerLockGuard,
209 mut input: ItemStack,
210 amount: i32,
211 ) -> ItemStack {
212 if input.is_empty() || !self.may_place(&input) {
213 return input;
214 }
215
216 let slot_stack = self.get_item(guard).clone();
217 let transferable = amount
218 .min(input.count)
219 .min(self.get_max_stack_size_for_item(guard, &input) - slot_stack.count);
220 if transferable <= 0 {
221 return input;
222 }
223
224 if slot_stack.is_empty() {
225 self.set_by_player(guard, input.split(transferable), &slot_stack);
226 } else if ItemStack::is_same_item_same_components(&slot_stack, &input) {
227 input.shrink(transferable);
228 let mut new_slot_stack = slot_stack.clone();
229 new_slot_stack.grow(transferable);
230 self.set_by_player(guard, new_slot_stack, &slot_stack);
231 }
232
233 input
234 }
235
236 fn set_changed(&self, guard: &mut ContainerLockGuard);
238
239 fn get_container_slot(&self) -> usize;
241
242 fn is_fake(&self) -> bool {
248 false
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use std::sync::{
255 Arc,
256 atomic::{AtomicBool, Ordering},
257 };
258
259 use steel_registry::{init_vanilla_registry, vanilla_items};
260 use steel_utils::locks::IntoShared;
261 use steel_utils::{Downcast as _, DowncastType, DowncastTypeKey};
262
263 use super::*;
264 use crate::inventory::slots::normal_slot::NormalSlot;
265 use crate::inventory::{container::SimpleContainer, lock::ContainerRef};
266
267 struct SafeInsertOverrideSlot {
268 base: NormalSlot,
269 called: Arc<AtomicBool>,
270 }
271
272 unsafe impl DowncastType for SafeInsertOverrideSlot {
274 const TYPE_KEY: DowncastTypeKey =
275 DowncastTypeKey::new("steel:test/slot/safe_insert_override");
276 }
277
278 impl Slot for SafeInsertOverrideSlot {
279 fn storage(&self) -> &SlotStorage {
280 self.base.storage()
281 }
282
283 fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack {
284 self.base.get_item(guard)
285 }
286
287 fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack {
288 self.base.get_item_mut(guard)
289 }
290
291 fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack) {
292 self.base.set_item(guard, stack);
293 }
294
295 fn safe_insert(
296 &self,
297 _guard: &mut ContainerLockGuard,
298 input: ItemStack,
299 _amount: i32,
300 ) -> ItemStack {
301 self.called.store(true, Ordering::Relaxed);
302 input
303 }
304
305 fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32 {
306 self.base.get_max_stack_size(guard)
307 }
308
309 fn set_changed(&self, guard: &mut ContainerLockGuard) {
310 self.base.set_changed(guard);
311 }
312
313 fn get_container_slot(&self) -> usize {
314 self.base.get_container_slot()
315 }
316 }
317
318 #[test]
319 fn custom_slot_safe_insert_override_survives_erasure() {
320 init_vanilla_registry();
321 let container = SimpleContainer::new(1).into_shared();
322 let container_ref = ContainerRef::from(Arc::clone(&container));
323 let called = Arc::new(AtomicBool::new(false));
324 let slot: Box<dyn Slot> = Box::new(SafeInsertOverrideSlot {
325 base: NormalSlot::new(container_ref.clone(), 0),
326 called: Arc::clone(&called),
327 });
328 assert!(slot.downcast_ref::<SafeInsertOverrideSlot>().is_some());
329 let mut guard = ContainerLockGuard::lock_all(&[container_ref]);
330
331 let remaining = slot.safe_insert(&mut guard, ItemStack::new(&vanilla_items::STONE), 1);
332
333 assert!(called.load(Ordering::Relaxed));
334 assert!(remaining.is(&vanilla_items::STONE));
335 assert!(slot.get_item(&guard).is_empty());
336 }
337}