From 22daa1e5d043e2e6635febb551aae77c45467f27 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 12 Aug 2026 20:46:43 +0200 Subject: [PATCH] feat(block): implement archaeology brushable blocks --- .../src/block/blocks/brushable_block.rs | 104 ++++++++++++++++++ crates/pumpkin/src/block/blocks/mod.rs | 1 + crates/pumpkin/src/block/registry.rs | 2 + 3 files changed, 107 insertions(+) create mode 100644 crates/pumpkin/src/block/blocks/brushable_block.rs diff --git a/crates/pumpkin/src/block/blocks/brushable_block.rs b/crates/pumpkin/src/block/blocks/brushable_block.rs new file mode 100644 index 000000000..dd22f333d --- /dev/null +++ b/crates/pumpkin/src/block/blocks/brushable_block.rs @@ -0,0 +1,104 @@ +use std::sync::Arc; + +use pumpkin_data::block_properties::{BlockProperties, SuspiciousSandLikeProperties}; +use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::{Block, BlockId, BlockStateId}; +use pumpkin_world::world::BlockFlags; + +use crate::block::entities::brushable_block::BrushableBlockBlockEntity; +use crate::block::{ + BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs, +}; + +pub struct BrushableBlock; + +impl BlockMetadata for BrushableBlock { + fn ids() -> Box<[BlockId]> { + [BlockId::SUSPICIOUS_SAND, BlockId::SUSPICIOUS_GRAVEL].into() + } +} + +impl BrushableBlock { + pub async fn brush( + world: &Arc, + pos: &pumpkin_util::math::position::BlockPos, + block: &Block, + ) { + let state = world.get_block_state(pos); + let mut props = SuspiciousSandLikeProperties::from_state_id(state.id, block); + + let is_gravel = block.id == BlockId::SUSPICIOUS_GRAVEL; + + if let Some(be) = world.get_block_entity(pos) + && let Some(brush_be) = be.as_any().downcast_ref::() + { + let mut hits = brush_be.hits.lock().await; + *hits += 1; + + if *hits >= 4 { + let item = brush_be.item.lock().await.take(); + if let Some(item_stack) = item { + world.drop_stack(pos, item_stack).await; + } + + let target_block = if is_gravel { + &Block::GRAVEL + } else { + &Block::SAND + }; + + world + .set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL) + .await; + + let sound = if is_gravel { + Sound::ItemBrushBrushingGravelComplete + } else { + Sound::ItemBrushBrushingSandComplete + }; + + world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64()); + } else { + props.dusted = (*hits as u8).min(3); + world + .set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL) + .await; + + let sound = if is_gravel { + Sound::ItemBrushBrushingGravel + } else { + Sound::ItemBrushBrushingSand + }; + + world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64()); + } + } + } +} + +impl BlockBehaviour for BrushableBlock { + fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { + Box::pin(async move { + let props = SuspiciousSandLikeProperties::default(args.block); + props.to_state_id(args.block) + }) + } + + fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + let entity = BrushableBlockBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(entity)); + }) + } + + fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { + Box::pin(async move { + if let Some(be) = args.world.get_block_entity(args.position) + && let Some(brush_be) = be.as_any().downcast_ref::() + && let Some(contained) = brush_be.item.lock().await.take() + { + args.world.drop_stack(args.position, contained).await; + } + }) + } +} diff --git a/crates/pumpkin/src/block/blocks/mod.rs b/crates/pumpkin/src/block/blocks/mod.rs index 6efad9025..ac644782c 100644 --- a/crates/pumpkin/src/block/blocks/mod.rs +++ b/crates/pumpkin/src/block/blocks/mod.rs @@ -110,6 +110,7 @@ pub mod coral; pub mod abstract_wall_mounting; pub mod beacon; +pub mod brushable_block; pub mod creaking_heart; pub mod decorated_pot; pub mod sniffer_egg; diff --git a/crates/pumpkin/src/block/registry.rs b/crates/pumpkin/src/block/registry.rs index 6a334a91b..bfad5cb22 100644 --- a/crates/pumpkin/src/block/registry.rs +++ b/crates/pumpkin/src/block/registry.rs @@ -129,6 +129,7 @@ use crate::block::blocks::signs::SignBlock; use crate::block::blocks::slabs::SlabBlock; use crate::block::blocks::slime::SlimeBlock; use crate::block::blocks::snow::LayeredSnowBlock; +use crate::block::blocks::brushable_block::BrushableBlock; use crate::block::blocks::sniffer_egg::SnifferEggBlock; use crate::block::blocks::spawner::SpawnerBlock; use crate::block::blocks::sponge::{SpongeBlock, WetSpongeBlock}; @@ -258,6 +259,7 @@ pub fn default_registry() -> Arc { manager.register(DecoratedPotBlock); manager.register(CreakingHeartBlock); manager.register(SnifferEggBlock); + manager.register(BrushableBlock); manager.register(BushBlock); manager.register(FlowerBlock); manager.register(PotatoBlock);