steel_core/block_entity/entities/
barrel.rs1use std::{
7 mem,
8 sync::{Arc, Weak},
9};
10
11use simdnbt::ToNbtTag;
12use simdnbt::borrow::{BaseNbtCompound as BorrowedNbtCompound, NbtCompound as NbtCompoundView};
13use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
14use steel_registry::item_stack::ItemStack;
15use steel_registry::vanilla_block_entity_types;
16use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, locks::SyncMutex};
17
18use crate::block_entity::{BlockEntity, BlockEntityBase};
19use crate::inventory::container::Container;
20use crate::inventory::container::load_all_items;
21use crate::inventory::lock::{ContainerRef, SharedContainer};
22use crate::world::World;
23
24pub const BARREL_SLOTS: usize = 27;
26
27pub struct BarrelBlockEntity {
31 base: Arc<BlockEntityBase>,
32 container: Arc<SyncMutex<BarrelContainer>>,
33 container_ref: ContainerRef,
34}
35
36struct BarrelContainer {
37 items: Vec<ItemStack>,
38}
39
40unsafe impl DowncastType for BarrelBlockEntity {
42 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/barrel");
43}
44
45unsafe impl DowncastType for BarrelContainer {
48 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:container/barrel");
49}
50
51impl BarrelBlockEntity {
52 #[must_use]
54 pub fn new(level: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
55 let base = Arc::new(BlockEntityBase::new(
56 &vanilla_block_entity_types::BARREL,
57 level,
58 pos,
59 state,
60 ));
61 let container = Arc::new(SyncMutex::new(BarrelContainer {
62 items: vec![ItemStack::empty(); BARREL_SLOTS],
63 }));
64 let shared_container: SharedContainer =
65 Arc::<SyncMutex<BarrelContainer>>::clone(&container);
66 Self {
67 container_ref: ContainerRef::owned_by_block_entity(shared_container, Arc::clone(&base)),
68 base,
69 container,
70 }
71 }
72}
73
74impl BlockEntity for BarrelBlockEntity {
75 fn base(&self) -> &BlockEntityBase {
76 &self.base
77 }
78
79 fn pre_remove_side_effects(&self, pos: BlockPos, _state: BlockStateId) {
80 let items = {
81 let mut container = self.container.lock();
82 mem::replace(&mut container.items, vec![ItemStack::empty(); BARREL_SLOTS])
83 };
84 let Some(world) = self.get_level() else {
85 return;
86 };
87 for item in items {
88 world.drop_item_stack(pos, item);
89 }
90 }
91
92 fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
93 let nbt_view: NbtCompoundView<'_, '_> = nbt.into();
95 let mut container = self.container.lock();
96 container.items.fill(ItemStack::empty());
97
98 load_all_items(nbt_view, &mut container.items);
99 }
100
101 fn save_additional(&self, nbt: &mut NbtCompound) {
102 let container = self.container.lock();
104 let mut items: Vec<NbtCompound> = Vec::new();
105 for (slot, item) in container.items.iter().enumerate() {
106 if !item.is_empty() {
107 if let NbtTag::Compound(mut item_nbt) = item.clone().to_nbt_tag() {
109 item_nbt.insert("Slot", slot as i8);
110 items.push(item_nbt);
111 }
112 }
113 }
114 nbt.insert("Items", NbtList::Compound(items));
115 }
116
117 fn get_update_tag(&self) -> Option<NbtCompound> {
118 None
121 }
122
123 fn container_ref(&self) -> Option<ContainerRef> {
124 Some(self.container_ref.clone())
125 }
126}
127
128impl Container for BarrelContainer {
129 fn items(&self) -> &[ItemStack] {
130 &self.items
131 }
132
133 fn items_mut(&mut self) -> &mut [ItemStack] {
134 &mut self.items
135 }
136
137 fn get_container_size(&self) -> usize {
138 BARREL_SLOTS
139 }
140
141 fn set_item(&mut self, slot: usize, mut stack: ItemStack) {
142 if slot < BARREL_SLOTS {
143 let max_stack_size = self.get_max_stack_size_for_item(&stack);
144 if !stack.is_empty() && stack.count() > max_stack_size {
145 stack.set_count(max_stack_size);
146 }
147 self.items[slot] = stack;
148 }
149 }
150
151 fn get_max_stack_size(&self) -> i32 {
152 64
153 }
154
155 fn set_changed(&mut self) {}
156}
157
158#[cfg(test)]
159mod tests {
160 use steel_registry::{init_vanilla_registry, vanilla_blocks, vanilla_items};
161
162 use super::*;
163
164 fn test_barrel() -> BarrelBlockEntity {
165 init_vanilla_registry();
166 BarrelBlockEntity::new(
167 Weak::new(),
168 BlockPos::new(1, 2, 3),
169 vanilla_blocks::BARREL.default_state(),
170 )
171 }
172
173 #[test]
174 fn set_item_limits_stack_to_vanilla_container_maximum() {
175 let barrel = test_barrel();
176 barrel
177 .container
178 .lock()
179 .set_item(0, ItemStack::with_count(&vanilla_items::STONE, 100));
180
181 assert_eq!(barrel.container.lock().get_item(0).count(), 64);
182 }
183
184 #[test]
185 fn pre_remove_preserves_slots_for_existing_menu_references() {
186 let barrel = test_barrel();
187 barrel
188 .container
189 .lock()
190 .set_item(0, ItemStack::new(&vanilla_items::STONE));
191
192 barrel.pre_remove_side_effects(
193 BlockPos::new(1, 2, 3),
194 vanilla_blocks::BARREL.default_state(),
195 );
196
197 let container = barrel.container.lock();
198 assert_eq!(container.items.len(), BARREL_SLOTS);
199 assert!(container.items.iter().all(ItemStack::is_empty));
200 }
201}