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