steel_core/inventory/container/
mod.rs1mod crafting;
7mod result;
8mod simple;
9
10pub use crafting::CraftingContainer;
11pub use result::ResultContainer;
12pub use simple::SimpleContainer;
13
14use std::mem;
15
16use steel_registry::blocks::properties::Direction;
17use steel_registry::item_stack::ItemStack;
18use steel_utils::ErasedType;
19
20pub const DEFAULT_DISTANCE_BUFFER: f32 = 4.0;
22
23pub trait Container: ErasedType + Send + Sync {
41 fn items(&self) -> &[ItemStack];
43 fn items_mut(&mut self) -> &mut [ItemStack];
45
46 fn get_container_size(&self) -> usize {
48 self.items().len()
49 }
50
51 fn is_empty(&self) -> bool {
53 for i in 0..self.get_container_size() {
54 if !self.get_item(i).is_empty() {
55 return false;
56 }
57 }
58 true
59 }
60
61 fn get_item(&self, slot: usize) -> &ItemStack {
63 &self.items()[slot]
64 }
65
66 fn contains_stack(&self, search_stack: &ItemStack) -> bool {
70 (0..self.get_container_size()).any(|slot| {
71 let item = self.get_item(slot);
72 !item.is_empty() && ItemStack::is_same_item_same_components(item, search_stack)
73 })
74 }
75
76 fn get_item_mut(&mut self, slot: usize) -> &mut ItemStack {
78 &mut self.items_mut()[slot]
79 }
80
81 fn set_item(&mut self, slot: usize, stack: ItemStack) {
83 self.items_mut()[slot] = stack;
84 }
85
86 fn remove_item(&mut self, slot: usize, count: i32) -> ItemStack {
88 let item = self.get_item_mut(slot);
89 if item.is_empty() || count <= 0 {
90 return ItemStack::empty();
91 }
92 item.split(count)
93 }
94
95 fn remove_item_no_update(&mut self, slot: usize) -> ItemStack {
97 mem::take(self.get_item_mut(slot))
98 }
99
100 fn get_max_stack_size(&self) -> i32 {
102 99
103 }
104
105 fn get_max_stack_size_for_item(&self, item: &ItemStack) -> i32 {
110 self.get_max_stack_size().min(item.max_stack_size())
111 }
112
113 fn set_changed(&mut self);
115
116 fn can_place_item(&self, _slot: usize, _stack: &ItemStack) -> bool {
118 true
119 }
120
121 fn can_take_item(
127 &self,
128 _destination: &dyn Container,
129 _slot: usize,
130 _stack: &ItemStack,
131 ) -> bool {
132 true
133 }
134
135 fn slots_for_face(&self, _direction: Direction) -> Option<&'static [usize]> {
139 None
140 }
141
142 fn can_place_item_through_face(
144 &self,
145 slot: usize,
146 stack: &ItemStack,
147 _direction: Direction,
148 ) -> bool {
149 self.can_place_item(slot, stack)
150 }
151
152 fn can_take_item_through_face(
154 &self,
155 _slot: usize,
156 _stack: &ItemStack,
157 _direction: Direction,
158 ) -> bool {
159 true
160 }
161
162 fn clear_content(&mut self) -> i32 {
164 let mut count = 0;
165 for item in self.items_mut() {
166 count += item.count();
167 *item = ItemStack::empty();
168 }
169 if count > 0 {
170 self.set_changed();
171 }
172 count
173 }
174
175 fn clear_content_matching(&mut self, predicate: &mut dyn FnMut(&mut ItemStack) -> bool) -> i32 {
177 let mut count = 0;
178 for item in self.items_mut() {
179 if predicate(item) {
180 count += item.count();
181 *item = ItemStack::empty();
182 }
183 }
184 if count > 0 {
185 self.set_changed();
186 }
187 count
188 }
189
190 fn clear_or_count_matching_items(
192 &mut self,
193 predicate: &dyn Fn(&ItemStack) -> bool,
194 amount_to_remove: i32,
195 counting_only: bool,
196 ) -> i32 {
197 let mut count = 0;
198 for slot in 0..self.get_container_size() {
199 let stack_count = self.get_item(slot).count();
200 let amount_removed = matching_item_count(
201 self.get_item(slot),
202 predicate,
203 amount_to_remove - count,
204 counting_only,
205 );
206 if amount_removed > 0 && !counting_only {
207 if amount_removed == stack_count {
208 self.set_item(slot, ItemStack::empty());
209 } else {
210 self.get_item_mut(slot).shrink(amount_removed);
211 }
212 }
213 count += amount_removed;
214 }
215 if count > 0 && !counting_only {
216 self.set_changed();
217 }
218 count
219 }
220
221 fn with_indices<const N: usize>(&mut self, indices: [usize; N]) -> [&mut ItemStack; N]
227 where
228 Self: Sized,
229 {
230 let items = self.items_mut();
231 let size = items.len();
232 for (position, index) in indices.iter().copied().enumerate() {
233 assert!(
234 index < size,
235 "with_indices: index {index} out of bounds (container size {size})",
236 );
237 assert!(
238 !indices[..position].contains(&index),
239 "with_indices: duplicate index {index}",
240 );
241 }
242 let Ok(items) = items.get_disjoint_mut(indices) else {
243 unreachable!("with_indices validated distinct in-bounds indices");
244 };
245 items
246 }
247
248 fn add(&mut self, stack: &mut ItemStack) -> bool {
256 if stack.is_empty() {
257 return true;
258 }
259
260 let size = self.get_container_size();
261 let max_size = self.get_max_stack_size_for_item(stack);
262 let mut changed = false;
263
264 if stack.is_stackable() {
266 for slot in 0..size {
267 if stack.is_empty() {
268 if changed {
269 self.set_changed();
270 }
271 return true;
272 }
273 if !self.can_place_item(slot, stack) {
274 continue;
275 }
276 let existing = self.get_item_mut(slot);
277 if !existing.is_empty() && ItemStack::is_same_item_same_components(existing, stack)
278 {
279 let space = max_size - existing.count();
280 if space > 0 {
281 let to_add = stack.count().min(space);
282 existing.grow(to_add);
283 stack.shrink(to_add);
284 changed = true;
285 }
286 }
287 }
288 }
289
290 for slot in 0..size {
292 if stack.is_empty() {
293 if changed {
294 self.set_changed();
295 }
296 return true;
297 }
298 if self.get_item(slot).is_empty() && self.can_place_item(slot, stack) {
299 let to_place = stack.count().min(max_size);
300 self.set_item(slot, stack.split(to_place));
301 changed = true;
302 }
303 }
304
305 if changed {
306 self.set_changed();
307 }
308 stack.is_empty()
309 }
310
311 fn iter(&self) -> Box<dyn Iterator<Item = &ItemStack> + '_> {
313 Box::new(self.items().iter())
314 }
315
316 fn iter_mut(&mut self) -> Box<dyn Iterator<Item = &mut ItemStack> + '_> {
318 Box::new(self.items_mut().iter_mut())
319 }
320}
321
322pub(crate) fn clear_or_count_matching_stack(
324 stack: &mut ItemStack,
325 predicate: &dyn Fn(&ItemStack) -> bool,
326 amount_to_remove: i32,
327 counting_only: bool,
328) -> i32 {
329 let amount_removed = matching_item_count(stack, predicate, amount_to_remove, counting_only);
330 if !counting_only {
331 stack.shrink(amount_removed);
332 }
333 amount_removed
334}
335
336fn matching_item_count(
337 stack: &ItemStack,
338 predicate: &dyn Fn(&ItemStack) -> bool,
339 amount_to_remove: i32,
340 counting_only: bool,
341) -> i32 {
342 if stack.is_empty() || !predicate(stack) {
343 return 0;
344 }
345 if counting_only {
346 return stack.count();
347 }
348
349 if amount_to_remove < 0 {
350 stack.count()
351 } else {
352 amount_to_remove.min(stack.count())
353 }
354}
355
356#[must_use]
370pub fn calculate_redstone_signal_from_container(container: &dyn Container) -> i32 {
371 let size = container.get_container_size();
372 if size == 0 {
373 return 0;
374 }
375
376 let mut total_percent: f32 = 0.0;
377
378 for i in 0..size {
379 let item = container.get_item(i);
380 if !item.is_empty() {
381 let max_stack = container.get_max_stack_size_for_item(item);
382 total_percent += item.count() as f32 / max_stack as f32;
383 }
384 }
385
386 total_percent /= size as f32;
387
388 (total_percent * 14.0).floor() as i32 + i32::from(total_percent > 0.0)
391}
392
393#[cfg(test)]
394mod tests {
395 use std::array;
396
397 use steel_registry::{init_vanilla_registry, vanilla_items};
398
399 use super::*;
400 use steel_utils::{DowncastType, DowncastTypeKey};
401
402 struct TestContainer {
403 items: Vec<ItemStack>,
404 }
405
406 impl TestContainer {
407 fn new(size: usize) -> Self {
408 Self {
409 items: (0..size).map(|_| ItemStack::empty()).collect(),
410 }
411 }
412 }
413
414 unsafe impl DowncastType for TestContainer {
416 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:test/inventory/container");
417 }
418
419 impl Container for TestContainer {
420 fn items(&self) -> &[ItemStack] {
421 &self.items
422 }
423
424 fn items_mut(&mut self) -> &mut [ItemStack] {
425 &mut self.items
426 }
427
428 fn get_container_size(&self) -> usize {
429 self.items.len()
430 }
431
432 fn set_changed(&mut self) {}
433 }
434
435 struct RedirectingContainer {
436 items: [ItemStack; 2],
437 }
438
439 unsafe impl DowncastType for RedirectingContainer {
441 const TYPE_KEY: DowncastTypeKey =
442 DowncastTypeKey::new("steel:test/inventory/redirecting_container");
443 }
444
445 impl Container for RedirectingContainer {
446 fn items(&self) -> &[ItemStack] {
447 &self.items
448 }
449
450 fn items_mut(&mut self) -> &mut [ItemStack] {
451 &mut self.items
452 }
453
454 fn get_item_mut(&mut self, _slot: usize) -> &mut ItemStack {
455 &mut self.items[0]
456 }
457
458 fn set_changed(&mut self) {}
459 }
460
461 #[test]
462 fn test_with_indices_disjoint() {
463 let mut container = TestContainer::new(4);
464 let [a, b] = container.with_indices([1, 3]);
465 a.count = 10;
466 b.count = 20;
467 assert_eq!(container.items[1].count, 10);
468 assert_eq!(container.items[3].count, 20);
469 assert_eq!(container.items[0].count, 0);
471 assert_eq!(container.items[2].count, 0);
472 }
473
474 #[test]
475 fn test_with_indices_single() {
476 let mut container = TestContainer::new(4);
477 let [a] = container.with_indices([2]);
478 a.count = 42;
479 assert_eq!(container.items[2].count, 42);
480 }
481
482 #[test]
483 fn test_with_indices_empty() {
484 let mut container = TestContainer::new(4);
485 let [] = container.with_indices([]);
486 }
487
488 #[test]
489 fn with_indices_uses_physical_item_storage() {
490 let mut container = RedirectingContainer {
491 items: array::from_fn(|_| ItemStack::empty()),
492 };
493
494 let [first, second] = container.with_indices([0, 1]);
495 first.count = 1;
496 second.count = 2;
497
498 assert_eq!(container.items[0].count, 1);
499 assert_eq!(container.items[1].count, 2);
500 }
501
502 #[test]
503 fn clear_or_count_matching_items_counts_without_mutating() {
504 init_vanilla_registry();
505 let mut container = TestContainer::new(3);
506 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
507 container.set_item(1, ItemStack::with_count(&vanilla_items::DIRT, 4));
508 container.set_item(2, ItemStack::with_count(&vanilla_items::STONE, 2));
509
510 let count = container.clear_or_count_matching_items(
511 &|stack| stack.is(&vanilla_items::STONE),
512 0,
513 true,
514 );
515
516 assert_eq!(count, 5);
517 assert_eq!(container.get_item(0).count(), 3);
518 assert_eq!(container.get_item(2).count(), 2);
519 }
520
521 #[test]
522 fn clear_or_count_matching_items_applies_cap_in_slot_order() {
523 init_vanilla_registry();
524 let mut container = TestContainer::new(2);
525 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
526 container.set_item(1, ItemStack::with_count(&vanilla_items::STONE, 4));
527
528 let count = container.clear_or_count_matching_items(
529 &|stack| stack.is(&vanilla_items::STONE),
530 5,
531 false,
532 );
533
534 assert_eq!(count, 5);
535 assert!(container.get_item(0).is_empty());
536 assert_eq!(container.get_item(1).count(), 2);
537 }
538
539 #[test]
540 fn clear_or_count_matching_items_removes_every_match_for_negative_limit() {
541 init_vanilla_registry();
542 let mut container = TestContainer::new(2);
543 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
544 container.set_item(1, ItemStack::with_count(&vanilla_items::STONE, 4));
545
546 let count = container.clear_or_count_matching_items(
547 &|stack| stack.is(&vanilla_items::STONE),
548 -1,
549 false,
550 );
551
552 assert_eq!(count, 7);
553 assert!(container.is_empty());
554 }
555
556 #[test]
557 fn comparator_signal_uses_vanilla_discrete_non_empty_floor() {
558 init_vanilla_registry();
559 let mut container = TestContainer::new(27);
560 container.set_item(0, ItemStack::new(&vanilla_items::STONE));
561 assert_eq!(calculate_redstone_signal_from_container(&container), 1);
562
563 for slot in 0..container.get_container_size() {
564 container.set_item(slot, ItemStack::with_count(&vanilla_items::STONE, 64));
565 }
566 assert_eq!(calculate_redstone_signal_from_container(&container), 15);
567 }
568
569 #[test]
570 #[should_panic(expected = "duplicate index")]
571 fn test_with_indices_duplicate_panics() {
572 let mut container = TestContainer::new(4);
573 let _ = container.with_indices([1, 1]);
574 }
575
576 #[test]
577 #[should_panic(expected = "out of bounds")]
578 fn test_with_indices_out_of_bounds_panics() {
579 let mut container = TestContainer::new(4);
580 let _ = container.with_indices([5]);
581 }
582
583 fn _compile_fail_docs_only() {}
609}