feat: Implement item scattering when destroying blocks with inventories (#1078)

* feat: Implement item scatterer

* fix: Some issues

* fix: Formatting

* feat: Remove unused functions

* feat: Optimize blockentity check

* fix: Formatting

* fix: Ups

* fix: Remove log

* fix: Make clippy happy

* fix: Merge conflicts
This commit is contained in:
Rafael
2025-07-28 20:21:29 +02:00
committed by GitHub
parent a66fbc9671
commit c76c2f7cff
16 changed files with 117 additions and 56 deletions

View File

@@ -178,7 +178,7 @@ impl Default for BasicConfiguration {
scrub_ips: true,
use_favicon: true,
favicon_path: "icon.png".to_string(),
default_level_name: "World".to_string(),
default_level_name: "world".to_string(),
allow_chat_reports: false,
white_list: false,
enforce_whitelist: false,

View File

@@ -66,6 +66,14 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
);
}
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
Some(self)
}
fn is_dirty(&self) -> bool {
self.dirty.load(Ordering::Relaxed)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}

View File

@@ -160,7 +160,7 @@ impl FurnaceBlockEntity {
#[async_trait]
impl BlockEntity for FurnaceBlockEntity {
async fn tick(&self, world: &Arc<dyn SimpleWorld>) {
async fn tick(&self, world: Arc<dyn SimpleWorld>) {
let is_burning = self.is_burning();
let mut is_dirty = false;
if self.is_burning() {

View File

@@ -70,7 +70,7 @@ impl BlockEntity for HopperBlockEntity {
hopper
}
async fn tick(&self, world: &Arc<dyn SimpleWorld>) {
async fn tick(&self, world: Arc<dyn SimpleWorld>) {
self.ticked_game_time.store(
world.get_world_age().await,
std::sync::atomic::Ordering::Relaxed,
@@ -86,7 +86,7 @@ impl BlockEntity for HopperBlockEntity {
world.get_block_state(&self.position).await.id,
&Block::HOPPER,
);
self.try_move_items(&state, world).await;
self.try_move_items(&state, &world).await;
}
}

View File

@@ -41,7 +41,7 @@ pub trait BlockEntity: Send + Sync {
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
where
Self: Sized;
async fn tick(&self, _world: &Arc<dyn SimpleWorld>) {}
async fn tick(&self, _world: Arc<dyn SimpleWorld>) {}
fn resource_location(&self) -> &'static str;
fn get_position(&self) -> BlockPos;
async fn write_internal(&self, nbt: &mut NbtCompound) {
@@ -67,6 +67,11 @@ pub trait BlockEntity: Send + Sync {
None
}
fn set_block_state(&mut self, _block_state: BlockStateId) {}
async fn on_block_replaced(self: Arc<Self>, world: Arc<dyn SimpleWorld>, position: BlockPos) {
if let Some(inventory) = self.get_inventory() {
world.scatter_inventory(&position, &inventory).await;
}
}
fn is_dirty(&self) -> bool {
false
}

View File

@@ -60,7 +60,7 @@ impl BlockEntity for PistonBlockEntity {
self.position
}
async fn tick(&self, world: &Arc<dyn SimpleWorld>) {
async fn tick(&self, world: Arc<dyn SimpleWorld>) {
let current_progress = self.current_progress.load();
self.last_progress.store(current_progress);
if current_progress >= 1.0 {

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::BlockStateId;
use crate::block::entities::BlockEntity;
use crate::{BlockStateId, inventory::Inventory};
use async_trait::async_trait;
use bitflags::bitflags;
use pumpkin_data::{Block, BlockDirection, BlockState};
@@ -59,6 +59,13 @@ pub trait SimpleWorld: BlockAccessor + Send + Sync {
async fn remove_block_entity(&self, block_pos: &BlockPos);
async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>>;
async fn get_world_age(&self) -> i64;
/* ItemScatterer */
async fn scatter_inventory(
self: Arc<Self>,
position: &BlockPos,
inventory: &Arc<dyn Inventory>,
);
}
#[async_trait]

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::pumpkin_block::{OnPlaceArgs, OnStateReplacedArgs, PlacedArgs};
use crate::block::pumpkin_block::{OnPlaceArgs, PlacedArgs};
use crate::block::{
pumpkin_block::{NormalUseArgs, PumpkinBlock},
registry::BlockActionResult,
@@ -68,8 +68,4 @@ impl PumpkinBlock for BarrelBlock {
.add_block_entity(Arc::new(barrel_block_entity))
.await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
}

View File

@@ -13,7 +13,7 @@ use pumpkin_world::block::entities::chest::ChestBlockEntity;
use pumpkin_world::world::BlockFlags;
use crate::block::pumpkin_block::{
BlockMetadata, BrokenArgs, OnPlaceArgs, OnStateReplacedArgs, PlacedArgs, UseWithItemArgs,
BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs, UseWithItemArgs,
};
use crate::entity::EntityBase;
use crate::world::World;
@@ -88,10 +88,6 @@ impl PumpkinBlock for ChestBlock {
}
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
async fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult {
BlockActionResult::Consume
}

View File

@@ -3,8 +3,8 @@ use std::sync::{Arc, atomic::Ordering};
use crate::{
block::{
pumpkin_block::{
BlockHitResult, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
OnStateReplacedArgs, PlacedArgs, PumpkinBlock, UseWithItemArgs,
BlockHitResult, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
PumpkinBlock, UseWithItemArgs,
},
registry::BlockActionResult,
},
@@ -116,10 +116,6 @@ impl PumpkinBlock for ChiseledBookshelfBlock {
args.world.add_block_entity(Arc::new(block_entity)).await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
async fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position).await {
if let Some(block_entity) = block_entity

View File

@@ -1,7 +1,6 @@
use std::sync::Arc;
use crate::block::pumpkin_block::OnEntityCollisionArgs;
use crate::block::pumpkin_block::OnStateReplacedArgs;
use crate::block::pumpkin_block::PlacedArgs;
use crate::block::pumpkin_block::PumpkinBlock;
use async_trait::async_trait;
@@ -35,8 +34,4 @@ impl PumpkinBlock for EndPortalBlock {
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position)))
.await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
}

View File

@@ -1,9 +1,7 @@
use std::sync::Arc;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::pumpkin_block::{
OnNeighborUpdateArgs, OnPlaceArgs, OnStateReplacedArgs, PlacedArgs,
};
use crate::block::pumpkin_block::{OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
pumpkin_block::{NormalUseArgs, PumpkinBlock},
registry::BlockActionResult,
@@ -98,10 +96,6 @@ impl PumpkinBlock for HopperBlock {
)
.await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
}
async fn check_powered_state(

View File

@@ -1,7 +1,6 @@
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::pumpkin_block::{
NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs,
PlacedArgs, PumpkinBlock,
NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, PumpkinBlock,
};
use crate::block::registry::BlockActionResult;
use crate::entity::Entity;
@@ -107,10 +106,6 @@ impl PumpkinBlock for DropperBlock {
.await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
async fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &args.position.up()).await;

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::pumpkin_block::{BlockMetadata, OnPlaceArgs, OnStateReplacedArgs, PlacedArgs};
use crate::block::pumpkin_block::{BlockMetadata, OnPlaceArgs, PlacedArgs};
use crate::block::{
pumpkin_block::{NormalUseArgs, PumpkinBlock},
registry::BlockActionResult,
@@ -79,8 +79,4 @@ impl PumpkinBlock for ShulkerBoxBlock {
.add_block_entity(Arc::new(barrel_block_entity))
.await;
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
}

View File

@@ -8,7 +8,6 @@ use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_world::block::entities::sign::SignBlockEntity;
use crate::block::pumpkin_block::OnPlaceArgs;
use crate::block::pumpkin_block::OnStateReplacedArgs;
use crate::block::pumpkin_block::PlacedArgs;
use crate::block::pumpkin_block::PlayerPlacedArgs;
use crate::block::pumpkin_block::PumpkinBlock;
@@ -40,8 +39,4 @@ impl PumpkinBlock for SignBlock {
crate::net::ClientPlatform::Bedrock(_bedrock) => todo!(),
}
}
async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
args.world.remove_block_entity(args.position).await;
}
}

View File

@@ -93,16 +93,19 @@ use pumpkin_protocol::{
},
};
use pumpkin_registry::VanillaDimensionType;
use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2};
use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::text::{TextComponent, color::NamedColor};
use pumpkin_util::{
Difficulty,
math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3},
};
use pumpkin_util::{
math::{position::chunk_section_from_pos, vector2::Vector2},
random::{RandomImpl, get_seed, xoroshiro128::Xoroshiro},
};
use pumpkin_world::{
BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity,
chunk::io::Dirtiable, item::ItemStack, world::SimpleWorld,
chunk::io::Dirtiable, inventory::Inventory, item::ItemStack, world::SimpleWorld,
};
use pumpkin_world::{chunk::ChunkData, world::BlockAccessor};
use pumpkin_world::{
@@ -676,7 +679,7 @@ impl World {
for block_entity in tick_data.block_entities {
let world: Arc<dyn SimpleWorld> = self.clone();
block_entity.tick(&world).await;
block_entity.tick(world).await;
}
}
@@ -1849,6 +1852,7 @@ impl World {
}
/// Sets a block and returns the old block id
#[allow(clippy::too_many_lines)]
pub async fn set_block_state(
self: &Arc<Self>,
position: &BlockPos,
@@ -1884,8 +1888,20 @@ impl World {
let block_moved = flags.contains(BlockFlags::MOVED);
// WorldChunk.java line 310
if old_block != new_block && (flags.contains(BlockFlags::NOTIFY_NEIGHBORS) || block_moved) {
let is_new_block = old_block != new_block;
// WorldChunk.java line 305-314
if is_new_block
&& old_block.default_state.block_entity_type != u16::MAX
&& let Some(entity) = self.get_block_entity(position).await
{
let world: Arc<dyn SimpleWorld> = self.clone();
entity.on_block_replaced(world, *position).await;
self.remove_block_entity(position).await;
}
// WorldChunk.java line 317
if is_new_block && (flags.contains(BlockFlags::NOTIFY_NEIGHBORS) || block_moved) {
self.block_registry
.on_state_replaced(
self,
@@ -2073,6 +2089,60 @@ impl World {
self.spawn_entity(item_entity).await;
}
/* ItemScatterer.java */
pub async fn scatter_inventory(
self: &Arc<Self>,
position: &BlockPos,
inventory: &Arc<dyn Inventory>,
) {
for i in 0..inventory.size() {
self.scatter_stack(
f64::from(position.0.x),
f64::from(position.0.y),
f64::from(position.0.z),
inventory.remove_stack(i).await,
)
.await;
}
}
pub async fn scatter_stack(self: &Arc<Self>, x: f64, y: f64, z: f64, mut stack: ItemStack) {
const TRIANGULAR_DEVIATION: f64 = 0.114_850_001_711_398_36;
const XZ_MODE: f64 = 0.0;
const Y_MODE: f64 = 0.2;
let width = f64::from(EntityType::ITEM.dimension[0]);
let half_width = width / 2.0;
let spawn_area = 1.0 - width;
let mut rng = Xoroshiro::from_seed(get_seed());
// TODO: Use world random here: world.random.nextDouble()
let x = x.floor() + rng.next_f64() * spawn_area + half_width;
let y = y.floor() + rng.next_f64() * spawn_area;
let z = z.floor() + rng.next_f64() * spawn_area + half_width;
while !stack.is_empty() {
let item = stack.split((rng.next_bounded_i32(21) + 10) as u8);
let velocity = Vector3::new(
rng.next_triangular(XZ_MODE, TRIANGULAR_DEVIATION),
rng.next_triangular(Y_MODE, TRIANGULAR_DEVIATION),
rng.next_triangular(XZ_MODE, TRIANGULAR_DEVIATION),
);
let entity = Entity::new(
Uuid::new_v4(),
self.clone(),
Vector3::new(x, y, z),
EntityType::ITEM,
false,
);
let entity = Arc::new(ItemEntity::new_with_velocity(entity, item, velocity, 10).await);
self.spawn_entity(entity).await;
}
}
/* End ItemScatterer.java */
pub async fn sync_world_event(&self, world_event: WorldEvent, position: BlockPos, data: i32) {
self.broadcast_packet_all(&CWorldEvent::new(world_event as i32, position, data, false))
.await;
@@ -2557,6 +2627,14 @@ impl pumpkin_world::world::SimpleWorld for World {
async fn get_world_age(&self) -> i64 {
self.level_time.lock().await.world_age
}
async fn scatter_inventory(
self: Arc<Self>,
position: &BlockPos,
inventory: &Arc<dyn Inventory>,
) {
Self::scatter_inventory(&self, position, inventory).await;
}
}
#[async_trait]