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