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::item_stack::ItemStack;
17use steel_utils::ErasedType;
18
19pub const DEFAULT_DISTANCE_BUFFER: f32 = 4.0;
21
22pub trait Container: ErasedType + Send + Sync {
40 fn items(&self) -> &[ItemStack];
42 fn items_mut(&mut self) -> &mut [ItemStack];
44
45 fn get_container_size(&self) -> usize {
47 self.items().len()
48 }
49
50 fn is_empty(&self) -> bool {
52 for i in 0..self.get_container_size() {
53 if !self.get_item(i).is_empty() {
54 return false;
55 }
56 }
57 true
58 }
59
60 fn get_item(&self, slot: usize) -> &ItemStack {
62 &self.items()[slot]
63 }
64
65 fn contains_stack(&self, search_stack: &ItemStack) -> bool {
69 (0..self.get_container_size()).any(|slot| {
70 let item = self.get_item(slot);
71 !item.is_empty() && ItemStack::is_same_item_same_components(item, search_stack)
72 })
73 }
74
75 fn get_item_mut(&mut self, slot: usize) -> &mut ItemStack {
77 &mut self.items_mut()[slot]
78 }
79
80 fn set_item(&mut self, slot: usize, stack: ItemStack) {
82 self.items_mut()[slot] = stack;
83 }
84
85 fn remove_item(&mut self, slot: usize, count: i32) -> ItemStack {
87 let item = self.get_item_mut(slot);
88 if item.is_empty() || count <= 0 {
89 return ItemStack::empty();
90 }
91 item.split(count)
92 }
93
94 fn remove_item_no_update(&mut self, slot: usize) -> ItemStack {
96 mem::take(self.get_item_mut(slot))
97 }
98
99 fn get_max_stack_size(&self) -> i32 {
101 99
102 }
103
104 fn get_max_stack_size_for_item(&self, item: &ItemStack) -> i32 {
109 self.get_max_stack_size().min(item.max_stack_size())
110 }
111
112 fn set_changed(&mut self);
114
115 fn can_place_item(&self, _slot: usize, _stack: &ItemStack) -> bool {
117 true
118 }
119
120 fn can_take_item(&self, _slot: usize, _stack: &ItemStack) -> bool {
122 true
123 }
124
125 fn clear_content(&mut self) -> i32 {
127 let mut count = 0;
128 for item in self.items_mut() {
129 count += item.count();
130 *item = ItemStack::empty();
131 }
132 if count > 0 {
133 self.set_changed();
134 }
135 count
136 }
137
138 fn clear_content_matching(&mut self, predicate: &mut dyn FnMut(&mut ItemStack) -> bool) -> i32 {
140 let mut count = 0;
141 for item in self.items_mut() {
142 if predicate(item) {
143 count += item.count();
144 *item = ItemStack::empty();
145 }
146 }
147 if count > 0 {
148 self.set_changed();
149 }
150 count
151 }
152
153 fn clear_or_count_matching_items(
155 &mut self,
156 predicate: &dyn Fn(&ItemStack) -> bool,
157 amount_to_remove: i32,
158 counting_only: bool,
159 ) -> i32 {
160 let mut count = 0;
161 for slot in 0..self.get_container_size() {
162 let stack_count = self.get_item(slot).count();
163 let amount_removed = matching_item_count(
164 self.get_item(slot),
165 predicate,
166 amount_to_remove - count,
167 counting_only,
168 );
169 if amount_removed > 0 && !counting_only {
170 if amount_removed == stack_count {
171 self.set_item(slot, ItemStack::empty());
172 } else {
173 self.get_item_mut(slot).shrink(amount_removed);
174 }
175 }
176 count += amount_removed;
177 }
178 if count > 0 && !counting_only {
179 self.set_changed();
180 }
181 count
182 }
183
184 fn with_indices<const N: usize>(&mut self, indices: [usize; N]) -> [&mut ItemStack; N]
190 where
191 Self: Sized,
192 {
193 let items = self.items_mut();
194 let size = items.len();
195 for (position, index) in indices.iter().copied().enumerate() {
196 assert!(
197 index < size,
198 "with_indices: index {index} out of bounds (container size {size})",
199 );
200 assert!(
201 !indices[..position].contains(&index),
202 "with_indices: duplicate index {index}",
203 );
204 }
205 let Ok(items) = items.get_disjoint_mut(indices) else {
206 unreachable!("with_indices validated distinct in-bounds indices");
207 };
208 items
209 }
210
211 fn add(&mut self, stack: &mut ItemStack) -> bool {
219 if stack.is_empty() {
220 return true;
221 }
222
223 let size = self.get_container_size();
224 let max_size = self.get_max_stack_size_for_item(stack);
225 let mut changed = false;
226
227 if stack.is_stackable() {
229 for slot in 0..size {
230 if stack.is_empty() {
231 if changed {
232 self.set_changed();
233 }
234 return true;
235 }
236 if !self.can_place_item(slot, stack) {
237 continue;
238 }
239 let existing = self.get_item_mut(slot);
240 if !existing.is_empty() && ItemStack::is_same_item_same_components(existing, stack)
241 {
242 let space = max_size - existing.count();
243 if space > 0 {
244 let to_add = stack.count().min(space);
245 existing.grow(to_add);
246 stack.shrink(to_add);
247 changed = true;
248 }
249 }
250 }
251 }
252
253 for slot in 0..size {
255 if stack.is_empty() {
256 if changed {
257 self.set_changed();
258 }
259 return true;
260 }
261 if self.get_item(slot).is_empty() && self.can_place_item(slot, stack) {
262 let to_place = stack.count().min(max_size);
263 self.set_item(slot, stack.split(to_place));
264 changed = true;
265 }
266 }
267
268 if changed {
269 self.set_changed();
270 }
271 stack.is_empty()
272 }
273
274 fn iter(&self) -> Box<dyn Iterator<Item = &ItemStack> + '_> {
276 Box::new(self.items().iter())
277 }
278
279 fn iter_mut(&mut self) -> Box<dyn Iterator<Item = &mut ItemStack> + '_> {
281 Box::new(self.items_mut().iter_mut())
282 }
283}
284
285pub(crate) fn clear_or_count_matching_stack(
287 stack: &mut ItemStack,
288 predicate: &dyn Fn(&ItemStack) -> bool,
289 amount_to_remove: i32,
290 counting_only: bool,
291) -> i32 {
292 let amount_removed = matching_item_count(stack, predicate, amount_to_remove, counting_only);
293 if !counting_only {
294 stack.shrink(amount_removed);
295 }
296 amount_removed
297}
298
299fn matching_item_count(
300 stack: &ItemStack,
301 predicate: &dyn Fn(&ItemStack) -> bool,
302 amount_to_remove: i32,
303 counting_only: bool,
304) -> i32 {
305 if stack.is_empty() || !predicate(stack) {
306 return 0;
307 }
308 if counting_only {
309 return stack.count();
310 }
311
312 if amount_to_remove < 0 {
313 stack.count()
314 } else {
315 amount_to_remove.min(stack.count())
316 }
317}
318
319#[must_use]
333pub fn calculate_redstone_signal_from_container(container: &dyn Container) -> i32 {
334 let size = container.get_container_size();
335 if size == 0 {
336 return 0;
337 }
338
339 let mut total_percent: f32 = 0.0;
340
341 for i in 0..size {
342 let item = container.get_item(i);
343 if !item.is_empty() {
344 let max_stack = container.get_max_stack_size_for_item(item);
345 total_percent += item.count() as f32 / max_stack as f32;
346 }
347 }
348
349 total_percent /= size as f32;
350
351 (total_percent * 14.0).floor() as i32 + i32::from(total_percent > 0.0)
354}
355
356#[cfg(test)]
357mod tests {
358 use std::array;
359
360 use steel_registry::{init_vanilla_registry, vanilla_items};
361
362 use super::*;
363 use steel_utils::{DowncastType, DowncastTypeKey};
364
365 struct TestContainer {
366 items: Vec<ItemStack>,
367 }
368
369 impl TestContainer {
370 fn new(size: usize) -> Self {
371 Self {
372 items: (0..size).map(|_| ItemStack::empty()).collect(),
373 }
374 }
375 }
376
377 unsafe impl DowncastType for TestContainer {
379 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:test/inventory/container");
380 }
381
382 impl Container for TestContainer {
383 fn items(&self) -> &[ItemStack] {
384 &self.items
385 }
386
387 fn items_mut(&mut self) -> &mut [ItemStack] {
388 &mut self.items
389 }
390
391 fn get_container_size(&self) -> usize {
392 self.items.len()
393 }
394
395 fn set_changed(&mut self) {}
396 }
397
398 struct RedirectingContainer {
399 items: [ItemStack; 2],
400 }
401
402 unsafe impl DowncastType for RedirectingContainer {
404 const TYPE_KEY: DowncastTypeKey =
405 DowncastTypeKey::new("steel:test/inventory/redirecting_container");
406 }
407
408 impl Container for RedirectingContainer {
409 fn items(&self) -> &[ItemStack] {
410 &self.items
411 }
412
413 fn items_mut(&mut self) -> &mut [ItemStack] {
414 &mut self.items
415 }
416
417 fn get_item_mut(&mut self, _slot: usize) -> &mut ItemStack {
418 &mut self.items[0]
419 }
420
421 fn set_changed(&mut self) {}
422 }
423
424 #[test]
425 fn test_with_indices_disjoint() {
426 let mut container = TestContainer::new(4);
427 let [a, b] = container.with_indices([1, 3]);
428 a.count = 10;
429 b.count = 20;
430 assert_eq!(container.items[1].count, 10);
431 assert_eq!(container.items[3].count, 20);
432 assert_eq!(container.items[0].count, 0);
434 assert_eq!(container.items[2].count, 0);
435 }
436
437 #[test]
438 fn test_with_indices_single() {
439 let mut container = TestContainer::new(4);
440 let [a] = container.with_indices([2]);
441 a.count = 42;
442 assert_eq!(container.items[2].count, 42);
443 }
444
445 #[test]
446 fn test_with_indices_empty() {
447 let mut container = TestContainer::new(4);
448 let [] = container.with_indices([]);
449 }
450
451 #[test]
452 fn with_indices_uses_physical_item_storage() {
453 let mut container = RedirectingContainer {
454 items: array::from_fn(|_| ItemStack::empty()),
455 };
456
457 let [first, second] = container.with_indices([0, 1]);
458 first.count = 1;
459 second.count = 2;
460
461 assert_eq!(container.items[0].count, 1);
462 assert_eq!(container.items[1].count, 2);
463 }
464
465 #[test]
466 fn clear_or_count_matching_items_counts_without_mutating() {
467 init_vanilla_registry();
468 let mut container = TestContainer::new(3);
469 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
470 container.set_item(1, ItemStack::with_count(&vanilla_items::DIRT, 4));
471 container.set_item(2, ItemStack::with_count(&vanilla_items::STONE, 2));
472
473 let count = container.clear_or_count_matching_items(
474 &|stack| stack.is(&vanilla_items::STONE),
475 0,
476 true,
477 );
478
479 assert_eq!(count, 5);
480 assert_eq!(container.get_item(0).count(), 3);
481 assert_eq!(container.get_item(2).count(), 2);
482 }
483
484 #[test]
485 fn clear_or_count_matching_items_applies_cap_in_slot_order() {
486 init_vanilla_registry();
487 let mut container = TestContainer::new(2);
488 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
489 container.set_item(1, ItemStack::with_count(&vanilla_items::STONE, 4));
490
491 let count = container.clear_or_count_matching_items(
492 &|stack| stack.is(&vanilla_items::STONE),
493 5,
494 false,
495 );
496
497 assert_eq!(count, 5);
498 assert!(container.get_item(0).is_empty());
499 assert_eq!(container.get_item(1).count(), 2);
500 }
501
502 #[test]
503 fn clear_or_count_matching_items_removes_every_match_for_negative_limit() {
504 init_vanilla_registry();
505 let mut container = TestContainer::new(2);
506 container.set_item(0, ItemStack::with_count(&vanilla_items::STONE, 3));
507 container.set_item(1, ItemStack::with_count(&vanilla_items::STONE, 4));
508
509 let count = container.clear_or_count_matching_items(
510 &|stack| stack.is(&vanilla_items::STONE),
511 -1,
512 false,
513 );
514
515 assert_eq!(count, 7);
516 assert!(container.is_empty());
517 }
518
519 #[test]
520 fn comparator_signal_uses_vanilla_discrete_non_empty_floor() {
521 init_vanilla_registry();
522 let mut container = TestContainer::new(27);
523 container.set_item(0, ItemStack::new(&vanilla_items::STONE));
524 assert_eq!(calculate_redstone_signal_from_container(&container), 1);
525
526 for slot in 0..container.get_container_size() {
527 container.set_item(slot, ItemStack::with_count(&vanilla_items::STONE, 64));
528 }
529 assert_eq!(calculate_redstone_signal_from_container(&container), 15);
530 }
531
532 #[test]
533 #[should_panic(expected = "duplicate index")]
534 fn test_with_indices_duplicate_panics() {
535 let mut container = TestContainer::new(4);
536 let _ = container.with_indices([1, 1]);
537 }
538
539 #[test]
540 #[should_panic(expected = "out of bounds")]
541 fn test_with_indices_out_of_bounds_panics() {
542 let mut container = TestContainer::new(4);
543 let _ = container.with_indices([5]);
544 }
545
546 fn _compile_fail_docs_only() {}
572}