Skip to content

Commit bb75b78

Browse files
authored
feat: add trapped chest block entity with redstone power support (#1550)
* feat: add trapped chest block entity with redstone power support * refactor: run cargo fmt * refactor: remove duplicated chest logic using common impl * fix: fix trapped chest redstone behavior for double chests and block updates * fix(trapped_chest): remove extraneous double chest logic * refactor(chest): abstract chest and trapped chest implementations with macros
1 parent cb60cbc commit bb75b78

File tree

6 files changed

+591
-497
lines changed

6 files changed

+591
-497
lines changed
Lines changed: 13 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,16 @@
1-
use std::{
2-
any::Any,
3-
array::from_fn,
4-
pin::Pin,
5-
sync::{
6-
Arc,
7-
atomic::{AtomicBool, Ordering},
8-
},
9-
};
1+
use std::sync::{Arc, atomic::AtomicBool};
2+
3+
use pumpkin_data::block_properties::BlockProperties;
4+
use pumpkin_util::math::position::BlockPos;
105

11-
use pumpkin_data::{
12-
Block, HorizontalFacingExt,
13-
block_properties::{BlockProperties, ChestLikeProperties, ChestType},
14-
sound::{Sound, SoundCategory},
15-
};
16-
use pumpkin_nbt::compound::NbtCompound;
17-
use pumpkin_util::{
18-
math::{position::BlockPos, vector3::Vector3},
19-
random::{RandomImpl, get_seed, xoroshiro128::Xoroshiro},
20-
};
216
use tokio::sync::Mutex;
227

238
use crate::{
24-
block::viewer::{ViewerCountListener, ViewerCountTracker, ViewerFuture},
25-
inventory::{Clearable, Inventory, InventoryFuture, split_stack},
9+
block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods,
10+
impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest,
2611
item::ItemStack,
27-
world::SimpleWorld,
2812
};
2913

30-
use super::BlockEntity;
31-
3214
pub struct ChestBlockEntity {
3315
pub position: BlockPos,
3416
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
@@ -38,222 +20,16 @@ pub struct ChestBlockEntity {
3820
viewers: ViewerCountTracker,
3921
}
4022

41-
impl BlockEntity for ChestBlockEntity {
42-
fn resource_location(&self) -> &'static str {
43-
Self::ID
44-
}
45-
46-
fn get_position(&self) -> BlockPos {
47-
self.position
48-
}
49-
50-
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
51-
where
52-
Self: Sized,
53-
{
54-
let chest = Self {
55-
position,
56-
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
57-
dirty: AtomicBool::new(false),
58-
viewers: ViewerCountTracker::new(),
59-
};
60-
61-
chest.read_data(nbt, &chest.items);
62-
63-
chest
64-
}
65-
66-
fn write_nbt<'a>(
67-
&'a self,
68-
nbt: &'a mut NbtCompound,
69-
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
70-
Box::pin(async move {
71-
self.write_data(nbt, &self.items, true).await;
72-
})
73-
// Safety precaution
74-
//self.clear().await;
75-
}
76-
77-
fn tick<'a>(
78-
&'a self,
79-
world: &'a Arc<dyn SimpleWorld>,
80-
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
81-
Box::pin(async move {
82-
self.viewers
83-
.update_viewer_count::<Self>(self, world, &self.position)
84-
.await;
85-
})
86-
}
87-
88-
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
89-
Some(self)
90-
}
91-
92-
fn is_dirty(&self) -> bool {
93-
self.dirty.load(Ordering::Relaxed)
94-
}
95-
96-
fn as_any(&self) -> &dyn Any {
97-
self
98-
}
99-
}
100-
101-
impl ViewerCountListener for ChestBlockEntity {
102-
fn on_container_open<'a>(
103-
&'a self,
104-
world: &'a Arc<dyn SimpleWorld>,
105-
_position: &'a BlockPos,
106-
) -> ViewerFuture<'a, ()> {
107-
Box::pin(async move {
108-
self.play_sound(world, Sound::BlockChestOpen).await;
109-
})
110-
}
111-
112-
fn on_container_close<'a>(
113-
&'a self,
114-
world: &'a Arc<dyn SimpleWorld>,
115-
_position: &'a BlockPos,
116-
) -> ViewerFuture<'a, ()> {
117-
Box::pin(async move {
118-
self.play_sound(world, Sound::BlockChestClose).await;
119-
})
120-
}
121-
122-
fn on_viewer_count_update<'a>(
123-
&'a self,
124-
world: &'a Arc<dyn SimpleWorld>,
125-
position: &'a BlockPos,
126-
_old: u16,
127-
new: u16,
128-
) -> ViewerFuture<'a, ()> {
129-
Box::pin(async move {
130-
world
131-
.add_synced_block_event(*position, Self::LID_ANIMATION_EVENT_TYPE, new as u8)
132-
.await;
133-
})
134-
}
135-
}
13623
impl ChestBlockEntity {
13724
pub const INVENTORY_SIZE: usize = 27;
13825
pub const LID_ANIMATION_EVENT_TYPE: u8 = 1;
13926
pub const ID: &'static str = "minecraft:chest";
140-
141-
/// Returns the number of players currently viewing this chest
142-
pub fn get_viewer_count(&self) -> u16 {
143-
self.viewers.get_viewer_count()
144-
}
145-
146-
#[must_use]
147-
pub fn new(position: BlockPos) -> Self {
148-
Self {
149-
position,
150-
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
151-
dirty: AtomicBool::new(false),
152-
viewers: ViewerCountTracker::new(),
153-
}
154-
}
155-
156-
async fn play_sound(&self, world: &Arc<dyn SimpleWorld>, sound: Sound) {
157-
let mut rng = Xoroshiro::from_seed(get_seed());
158-
159-
let state = world.get_block_state(&self.position).await;
160-
let properties = ChestLikeProperties::from_state_id(state.id, &Block::CHEST);
161-
let position = match properties.r#type {
162-
ChestType::Left => return,
163-
ChestType::Single => Vector3::new(
164-
self.position.0.x as f64 + 0.5,
165-
self.position.0.y as f64 + 0.5,
166-
self.position.0.z as f64 + 0.5,
167-
),
168-
ChestType::Right => {
169-
let direction = properties.facing.to_block_direction().to_offset();
170-
Vector3::new(
171-
self.position.0.x as f64 + 0.5 + direction.x as f64 * 0.5,
172-
self.position.0.y as f64 + 0.5,
173-
self.position.0.z as f64 + 0.5 + direction.z as f64 * 0.5,
174-
)
175-
}
176-
};
177-
178-
world
179-
.play_sound_fine(
180-
sound,
181-
SoundCategory::Blocks,
182-
&position,
183-
0.5,
184-
rng.next_f32() * 0.1 + 0.9,
185-
)
186-
.await;
187-
}
188-
}
189-
190-
impl Inventory for ChestBlockEntity {
191-
fn size(&self) -> usize {
192-
self.items.len()
193-
}
194-
195-
fn is_empty(&self) -> InventoryFuture<'_, bool> {
196-
Box::pin(async move {
197-
for slot in &self.items {
198-
if !slot.lock().await.is_empty() {
199-
return false;
200-
}
201-
}
202-
203-
true
204-
})
205-
}
206-
207-
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
208-
Box::pin(async move { self.items[slot].clone() })
209-
}
210-
211-
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
212-
Box::pin(async move {
213-
let mut removed = ItemStack::EMPTY.clone();
214-
let mut guard = self.items[slot].lock().await;
215-
std::mem::swap(&mut removed, &mut *guard);
216-
removed
217-
})
218-
}
219-
220-
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
221-
Box::pin(async move { split_stack(&self.items, slot, amount).await })
222-
}
223-
224-
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
225-
Box::pin(async move {
226-
*self.items[slot].lock().await = stack;
227-
})
228-
}
229-
230-
fn on_open(&self) -> InventoryFuture<'_, ()> {
231-
Box::pin(async move {
232-
self.viewers.open_container();
233-
})
234-
}
235-
236-
fn on_close(&self) -> InventoryFuture<'_, ()> {
237-
Box::pin(async move {
238-
self.viewers.close_container();
239-
})
240-
}
241-
242-
fn mark_dirty(&self) {
243-
self.dirty.store(true, Ordering::Relaxed);
244-
}
245-
246-
fn as_any(&self) -> &dyn Any {
247-
self
248-
}
27+
pub const EMITS_REDSTONE: bool = false;
24928
}
25029

251-
impl Clearable for ChestBlockEntity {
252-
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
253-
Box::pin(async move {
254-
for slot in &self.items {
255-
*slot.lock().await = ItemStack::EMPTY.clone();
256-
}
257-
})
258-
}
259-
}
30+
// Apply macros to generate trait implementations
31+
impl_block_entity_for_chest!(ChestBlockEntity);
32+
impl_inventory_for_chest!(ChestBlockEntity);
33+
impl_clearable_for_chest!(ChestBlockEntity);
34+
impl_viewer_count_listener_for_chest!(ChestBlockEntity);
35+
impl_chest_helper_methods!(ChestBlockEntity);

0 commit comments

Comments
 (0)