chore: more redstone implemented

This commit is contained in:
Alexander Medvedev 2026-07-11 17:45:52 +02:00
parent 1f4030a28d
commit 43e3581e70
25 changed files with 1151 additions and 26 deletions

View File

@ -80,6 +80,23 @@ pub async fn create_generic_3x3(
.await
}
/// Creates a crafter container (9 slots, 3x3 layout).
pub async fn create_crafter_3x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Crafter3x3,
sync_id,
player_inventory,
inventory,
3,
3,
)
.await
}
/// Creates a hopper container (5 slots).
///
/// Hoppers have a single row of 5 slots.

View File

@ -29,7 +29,7 @@ impl GUIBuilder {
WindowType::Generic9x4 => (4, 9),
WindowType::Generic9x5 => (5, 9),
WindowType::Generic9x6 => (6, 9),
WindowType::Generic3x3 => (3, 3),
WindowType::Generic3x3 | WindowType::Crafter3x3 => (3, 3),
WindowType::Hopper => (1, 5),
_ => (3, 9), // Default to 9x3
};

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::{BlockFuture, OnPlaceArgs, PlacedArgs};
use crate::block::{BlockFuture, GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -86,4 +86,19 @@ impl BlockBehaviour for BarrelBlock {
args.world.add_block_entity(Arc::new(barrel_block_entity));
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -0,0 +1,24 @@
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs};
use pumpkin_data::BlockId;
use pumpkin_data::block_properties::{BeeNestLikeProperties, BlockProperties};
pub struct BeehiveBlock;
impl BlockMetadata for BeehiveBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::BEEHIVE, BlockId::BEE_NEST].into()
}
}
impl BlockBehaviour for BeehiveBlock {
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = BeeNestLikeProperties::from_state_id(state_id, args.block);
Some(props.honey_level)
})
}
}

View File

@ -22,8 +22,8 @@ use tokio::sync::Mutex;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
registry::BlockActionResult,
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -149,4 +149,19 @@ impl BlockBehaviour for BlastFurnaceBlock {
args.world.remove_block_entity(args.position);
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::{BlockFuture, PlacedArgs};
use crate::block::{BlockFuture, GetComparatorOutputArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -78,4 +78,27 @@ impl BlockBehaviour for BrewingStandBlock {
args.world.add_block_entity(Arc::new(be));
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let mut bottles = 0u8;
// Bottle slots are 0, 1, 2 in brewing stands
for slot in 0..3 {
let stack = inventory.get_stack(slot).await;
if !stack.lock().await.is_empty() {
bottles += 1;
}
}
Some(bottles)
} else {
None
}
})
}
}

View File

@ -2,9 +2,9 @@ use std::sync::Arc;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
OnPlaceArgs, OnScheduledTickArgs, UseWithItemArgs, blocks::candle_cakes::cake_from_candle,
registry::BlockActionResult,
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetComparatorOutputArgs,
GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
UseWithItemArgs, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
},
entity::player::Player,
world::World,
@ -200,6 +200,21 @@ impl BlockBehaviour for CakeBlock {
args.state_id
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
if properties.bites <= 6 {
Some((7 - properties.bites) * 2)
} else {
Some(0)
}
})
}
}
fn can_place_at(world: &dyn BlockAccessor, position: &BlockPos) -> bool {

View File

@ -0,0 +1,36 @@
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs};
use pumpkin_data::BlockId;
use pumpkin_data::block_properties::{BlockProperties, WaterCauldronLikeProperties};
pub struct CauldronBlock;
impl BlockMetadata for CauldronBlock {
fn ids() -> Box<[BlockId]> {
[
BlockId::CAULDRON,
BlockId::WATER_CAULDRON,
BlockId::LAVA_CAULDRON,
BlockId::POWDER_SNOW_CAULDRON,
]
.into()
}
}
impl BlockBehaviour for CauldronBlock {
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
match args.block.id {
BlockId::WATER_CAULDRON | BlockId::POWDER_SNOW_CAULDRON => {
let state_id = args.world.get_block_state_id(args.position);
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
Some(props.level)
}
BlockId::LAVA_CAULDRON => Some(3),
_ => Some(0),
}
})
}
}

View File

@ -24,8 +24,8 @@ use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
use crate::block::{
BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetRedstonePowerArgs, NormalUseArgs,
OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, RandomTickArgs,
BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, RandomTickArgs,
};
use crate::entity::EntityBase;
use crate::world::World;
@ -128,6 +128,35 @@ async fn placed_chest_impl<E: BlockEntity + 'static>(
}
}
async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Option<u8> {
let state = args.world.get_block_state_id(args.position);
let first_chest = args.world.get_block_entity(args.position);
let first_inventory = first_chest.and_then(BlockEntity::get_inventory)?;
let chest_props = ChestLikeProperties::from_state_id(state, args.block);
let connected_towards = match chest_props.r#type {
ChestType::Single => None,
ChestType::Left => Some(chest_props.facing.rotate_clockwise()),
ChestType::Right => Some(chest_props.facing.rotate_counter_clockwise()),
};
if let Some(direction) = connected_towards
&& let Some(second_inventory) = args
.world
.get_block_entity(&args.position.offset(direction.to_offset()))
.and_then(BlockEntity::get_inventory)
{
let double_inventory = if matches!(chest_props.r#type, ChestType::Right) {
DoubleInventory::new(first_inventory, second_inventory)
} else {
DoubleInventory::new(second_inventory, first_inventory)
};
Some(crate::block::calculate_comparator_output(double_inventory.as_ref()).await)
} else {
Some(crate::block::calculate_comparator_output(first_inventory.as_ref()).await)
}
}
async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
args.player
.increment_stat(
@ -259,6 +288,13 @@ impl BlockBehaviour for ChestBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(broken_chest_impl(args))
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
}
}
/// Copper chests have the same behavior as wooden chests but also oxidize over time.
@ -311,6 +347,13 @@ impl BlockBehaviour for CopperChestBlock {
try_oxidize_copper_chest(args.world, args.position, args.block, chest_props).await;
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
}
}
/// Copper oxidation levels with their ordinal values
@ -522,6 +565,13 @@ impl BlockBehaviour for TrappedChestBlock {
}
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
}
}
fn compute_chest_props(

View File

@ -21,8 +21,8 @@ use tokio::sync::Mutex;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
registry::BlockActionResult,
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -144,4 +144,19 @@ impl BlockBehaviour for FurnaceBlock {
args.world.remove_block_entity(args.position);
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -1,7 +1,9 @@
use std::sync::Arc;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{BlockFuture, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
BlockFuture, GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -115,6 +117,21 @@ impl BlockBehaviour for HopperBlock {
.await;
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}
async fn check_powered_state(

View File

@ -92,7 +92,9 @@ pub mod sponge;
// Entities, hazards & misc gameplay blocks
pub mod anvil;
pub mod beehive;
pub mod cake;
pub mod cauldron;
pub mod composter;
pub mod ladder;
pub mod slime;

View File

@ -0,0 +1,215 @@
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::entities::crafter::CrafterBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use pumpkin_data::block_properties::{
BlockProperties, CrafterLikeProperties, HorizontalFacing, Orientation,
};
use pumpkin_data::sound::Sound;
use pumpkin_data::translation;
use pumpkin_data::world::WorldEvent;
use pumpkin_data::{BlockDirection, BlockStateId};
use pumpkin_inventory::generic_container_screen_handler::create_crafter_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
struct CrafterScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for CrafterScreenFactory {
fn create_screen_handler<'a>(
&'a self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_crafter_3x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
}
fn get_display_name(&self) -> TextComponent {
TextComponent::translate_cross(
translation::java::CONTAINER_CRAFTER,
translation::bedrock::CONTAINER_CRAFTER,
&[],
)
}
}
#[pumpkin_block("minecraft:crafter")]
pub struct CrafterBlock;
impl BlockBehaviour for CrafterBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&CrafterScreenFactory(inventory), Some(*args.position))
.await;
}
BlockActionResult::Success
})
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CrafterLikeProperties::default(args.block);
let facing = args.direction;
let horizontal = args.player.living_entity.entity.get_horizontal_facing();
props.orientation = match facing {
BlockDirection::Down => match horizontal {
HorizontalFacing::North => Orientation::DownNorth,
HorizontalFacing::South => Orientation::DownSouth,
HorizontalFacing::East => Orientation::DownEast,
HorizontalFacing::West => Orientation::DownWest,
},
BlockDirection::Up => match horizontal {
HorizontalFacing::North => Orientation::UpNorth,
HorizontalFacing::South => Orientation::UpSouth,
HorizontalFacing::East => Orientation::UpEast,
HorizontalFacing::West => Orientation::UpWest,
},
BlockDirection::North => Orientation::NorthUp,
BlockDirection::South => Orientation::SouthUp,
BlockDirection::East => Orientation::EastUp,
BlockDirection::West => Orientation::WestUp,
};
props.to_state_id(args.block)
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let crafter_block_entity = CrafterBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(crafter_block_entity));
})
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let powered = block_receives_redstone_power(args.world, args.position).await;
let mut props = CrafterLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if powered && !props.triggered {
props.triggered = true;
args.world
.schedule_block_tick(args.block, *args.position, 4, TickPriority::Normal);
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
} else if !powered && props.triggered {
props.triggered = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let mut props = CrafterLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
// Set to crafting state
props.crafting = true;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
// Recipes/crafting logic not fully implemented yet - play fail effects
args.world.play_sound(
Sound::BlockCrafterFail,
pumpkin_data::sound::SoundCategory::Blocks,
&args.position.to_f64(),
);
// Spawn fail smoke particles
args.world.sync_world_event(
WorldEvent::ParticlesShootSmoke,
*args.position,
match props.orientation {
Orientation::DownEast
| Orientation::DownNorth
| Orientation::DownSouth
| Orientation::DownWest => 0,
Orientation::UpEast
| Orientation::UpNorth
| Orientation::UpSouth
| Orientation::UpWest => 1,
Orientation::NorthUp => 2,
Orientation::SouthUp => 3,
Orientation::WestUp => 4,
Orientation::EastUp => 5,
},
);
// Set crafting state back to false
props.crafting = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position) {
let crafter = block_entity.as_any().downcast_ref::<CrafterBlockEntity>()?;
let mut occupied = 0u8;
for i in 0..9 {
let stack = crafter.get_stack(i).await;
if !stack.lock().await.is_empty() {
occupied += 1;
}
}
Some(occupied)
} else {
None
}
})
}
}

View File

@ -1,15 +1,105 @@
use crate::block::entities::dispenser::DispenserBlockEntity;
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs};
use crate::entity::EntityBase;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, DispenserLikeProperties};
use pumpkin_macros::pumpkin_block;
use rand::{Rng, RngExt, rng};
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::item::ItemEntity;
use crate::entity::{Entity, EntityBase};
use crate::block::entities::dispenser::DispenserBlockEntity;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, Facing};
use pumpkin_data::entity::EntityType;
use pumpkin_data::translation;
use pumpkin_data::world::WorldEvent;
use pumpkin_inventory::generic_container_screen_handler::create_generic_3x3;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
struct DispenserScreenFactory(Arc<dyn Inventory>);
impl ScreenHandlerFactory for DispenserScreenFactory {
fn create_screen_handler<'a>(
&'a self,
sync_id: u8,
player_inventory: &'a Arc<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone()).await;
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
})
}
fn get_display_name(&self) -> TextComponent {
TextComponent::translate_cross(
translation::java::CONTAINER_DISPENSER,
translation::bedrock::CONTAINER_DISPENSER,
&[],
)
}
}
#[pumpkin_block("minecraft:dispenser")]
pub struct DispenserBlock;
type DispenserLikeProperties = pumpkin_data::block_properties::DispenserLikeProperties;
fn triangle<R: Rng>(rng: &mut R, min: f64, max: f64) -> f64 {
(rng.random::<f64>() - rng.random::<f64>()).mul_add(max, min)
}
const fn to_normal(facing: Facing) -> Vector3<f64> {
match facing {
Facing::North => Vector3::new(0., 0., -1.),
Facing::East => Vector3::new(1., 0., 0.),
Facing::South => Vector3::new(0., 0., 1.),
Facing::West => Vector3::new(-1., 0., 0.),
Facing::Up => Vector3::new(0., 1., 0.),
Facing::Down => Vector3::new(0., -1., 0.),
}
}
const fn to_data3d(facing: Facing) -> i32 {
match facing {
Facing::North => 2,
Facing::East => 5,
Facing::South => 3,
Facing::West => 4,
Facing::Up => 1,
Facing::Down => 0,
}
}
impl BlockBehaviour for DispenserBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.open_handled_screen(&DispenserScreenFactory(inventory), Some(*args.position))
.await;
}
BlockActionResult::Success
})
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = DispenserLikeProperties::default(args.block);
@ -25,4 +115,110 @@ impl BlockBehaviour for DispenserBlock {
.add_block_entity(Arc::new(dispenser_block_entity));
})
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &args.position.up()).await;
let mut props = DispenserLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if powered && !props.triggered {
args.world
.schedule_block_tick(args.block, *args.position, 4, TickPriority::Normal);
props.triggered = true;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
} else if !powered && props.triggered {
props.triggered = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position) {
let Some(dispenser) = block_entity.as_any().downcast_ref::<DispenserBlockEntity>()
else {
return;
};
if let Some(mut item) = dispenser.get_random_slot().await {
let props = DispenserLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
// No specific dispenser behaviors are registered yet, so dispense item into the world
let drop_item = item.split(1);
let facing = to_normal(props.facing);
let mut position = args.position.to_centered_f64().add(&(facing * 0.7));
position.y -= match props.facing {
Facing::Up | Facing::Down => 0.125,
_ => 0.15625,
};
let entity = Entity::new(args.world.clone(), position, &EntityType::ITEM);
let rd = rng().random::<f64>().mul_add(0.1, 0.2);
let velocity = Vector3::new(
triangle(&mut rng(), facing.x * rd, 0.017_227_5 * 6.),
triangle(&mut rng(), 0.2, 0.017_227_5 * 6.),
triangle(&mut rng(), facing.z * rd, 0.017_227_5 * 6.),
);
let item_entity = Arc::new(ItemEntity::new_with_velocity(
entity, drop_item, velocity, 40,
));
args.world.spawn_entity(item_entity).await;
args.world.sync_world_event(
WorldEvent::SoundDispenserDispense,
*args.position,
0,
);
args.world.sync_world_event(
WorldEvent::ParticlesShootSmoke,
*args.position,
to_data3d(props.facing),
);
} else {
args.world
.sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0);
}
}
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -5,8 +5,8 @@ use tokio::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::item::ItemEntity;
use crate::entity::{Entity, EntityBase};
@ -230,4 +230,19 @@ impl BlockBehaviour for DropperBlock {
}
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -0,0 +1,113 @@
use std::sync::Arc;
use crate::block::{
BlockBehaviour, BlockFuture, EmitsRedstonePowerArgs, GetRedstonePowerArgs, OnPlaceArgs,
OnScheduledTickArgs,
};
use crate::world::World;
use pumpkin_data::block_properties::{BlockProperties, Facing, LightningRodLikeProperties};
use pumpkin_data::{BlockDirection, BlockStateId, FacingExt};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
#[pumpkin_block("minecraft:lightning_rod")]
pub struct LightningRodBlock;
impl LightningRodBlock {
pub async fn trigger(world: &Arc<World>, pos: &BlockPos) {
let (block, state_id) = world.get_block_and_state_id(pos);
let mut props = LightningRodLikeProperties::from_state_id(state_id, block);
if !props.powered {
props.powered = true;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
Self::update_neighbors(world, pos, &props).await;
// In vanilla, it stays powered for 8 ticks (4 redstone ticks) before scheduled tick turns it off.
world.schedule_block_tick(block, *pos, 8, TickPriority::Normal);
}
}
async fn update_neighbors(
world: &Arc<World>,
pos: &BlockPos,
props: &LightningRodLikeProperties,
) {
world.update_neighbors(pos, None).await;
// The block it is attached to is in the opposite of the facing direction
let attached_pos = pos.offset(props.facing.opposite().to_block_direction().to_offset());
world.update_neighbors(&attached_pos, None).await;
}
}
impl BlockBehaviour for LightningRodBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let facing = match args.direction {
BlockDirection::North => Facing::North,
BlockDirection::South => Facing::South,
BlockDirection::East => Facing::East,
BlockDirection::West => Facing::West,
BlockDirection::Up => Facing::Up,
BlockDirection::Down => Facing::Down,
};
let mut props = LightningRodLikeProperties::default(args.block);
props.facing = facing;
props.to_state_id(args.block)
})
}
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { true })
}
fn get_weak_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let props = LightningRodLikeProperties::from_state_id(args.state.id, args.block);
if props.powered { 15 } else { 0 }
})
}
fn get_strong_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let props = LightningRodLikeProperties::from_state_id(args.state.id, args.block);
// It emits strong power only in its facing direction (the direction pointing outward)
if props.powered && props.facing.to_block_direction() == args.direction {
15
} else {
0
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let mut props = LightningRodLikeProperties::from_state_id(state.id, args.block);
if props.powered {
props.powered = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
Self::update_neighbors(args.world, args.position, &props).await;
}
})
}
}

View File

@ -13,17 +13,21 @@ pub mod bell;
pub mod buttons;
pub mod comparator;
pub mod copper_bulb;
pub mod crafter;
pub mod daylight_detector;
pub mod dropper;
pub mod lever;
pub mod lightning_rod;
pub mod observer;
pub mod pressure_plate;
pub mod rails;
pub mod redstone_block;
pub mod redstone_lamp;
pub mod redstone_ore;
pub mod redstone_torch;
pub mod redstone_wire;
pub mod repeater;
pub mod sculk_sensor;
pub mod target_block;
pub mod tripwire;
pub mod tripwire_hook;

View File

@ -0,0 +1,75 @@
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, NormalUseArgs, OnEntityCollisionArgs,
OnEntityStepArgs, OnScheduledTickArgs, registry::BlockActionResult,
};
use crate::world::World;
use pumpkin_data::block_properties::{BlockProperties, RedstoneOreLikeProperties};
use pumpkin_data::{Block, BlockId, BlockState};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
pub struct RedstoneOreBlock;
impl BlockMetadata for RedstoneOreBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::REDSTONE_ORE, BlockId::DEEPSLATE_REDSTONE_ORE].into()
}
}
impl RedstoneOreBlock {
async fn light_up(world: &Arc<World>, pos: &BlockPos, block: &Block, state: &BlockState) {
let mut props = RedstoneOreLikeProperties::from_state_id(state.id, block);
if !props.lit {
props.lit = true;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
}
// In vanilla Minecraft, it stays lit for 30 seconds (600 ticks) before scheduled tick turns it off.
// We use 30 ticks to fit in the u8 parameter range.
world.schedule_block_tick(block, *pos, 30, TickPriority::Normal);
}
}
impl BlockBehaviour for RedstoneOreBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
Self::light_up(args.world, args.position, args.block, state).await;
BlockActionResult::Success
})
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
Self::light_up(args.world, args.position, args.block, state).await;
})
}
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
Self::light_up(args.world, args.position, args.block, state).await;
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let mut props = RedstoneOreLikeProperties::from_state_id(state.id, args.block);
if props.lit {
props.lit = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
}
})
}
}

View File

@ -0,0 +1,179 @@
use std::sync::Arc;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, EmitsRedstonePowerArgs, GetRedstonePowerArgs,
OnPlaceArgs, OnScheduledTickArgs,
};
use crate::world::World;
use pumpkin_data::block_properties::{
BlockProperties, CalibratedSculkSensorLikeProperties, SculkSensorLikeProperties,
SculkSensorPhase,
};
use pumpkin_data::{Block, BlockId, BlockStateId};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
pub struct SculkSensorBlock;
impl BlockMetadata for SculkSensorBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::SCULK_SENSOR, BlockId::CALIBRATED_SCULK_SENSOR].into()
}
}
impl SculkSensorBlock {
pub async fn trigger(world: &Arc<World>, pos: &BlockPos, block: &Block, power: u8) {
if block.id == BlockId::SCULK_SENSOR {
let state = world.get_block_state(pos);
let mut props = SculkSensorLikeProperties::from_state_id(state.id, block);
if props.sculk_sensor_phase == SculkSensorPhase::Inactive {
props.sculk_sensor_phase = SculkSensorPhase::Active;
props.power = power;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.update_neighbors(pos, None).await;
world.schedule_block_tick(block, *pos, 30, TickPriority::Normal);
}
} else if block.id == BlockId::CALIBRATED_SCULK_SENSOR {
let state = world.get_block_state(pos);
let mut props = CalibratedSculkSensorLikeProperties::from_state_id(state.id, block);
if props.sculk_sensor_phase == SculkSensorPhase::Inactive {
props.sculk_sensor_phase = SculkSensorPhase::Active;
props.power = power;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.update_neighbors(pos, None).await;
world.schedule_block_tick(block, *pos, 30, TickPriority::Normal);
}
}
}
}
impl BlockBehaviour for SculkSensorBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.block.id == BlockId::CALIBRATED_SCULK_SENSOR {
let mut props = CalibratedSculkSensorLikeProperties::default(args.block);
props.facing = args.player.living_entity.entity.get_horizontal_facing();
props.to_state_id(args.block)
} else {
let props = SculkSensorLikeProperties::default(args.block);
props.to_state_id(args.block)
}
})
}
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { true })
}
fn get_weak_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
if args.block.id == BlockId::SCULK_SENSOR {
let props = SculkSensorLikeProperties::from_state_id(args.state.id, args.block);
if props.sculk_sensor_phase == SculkSensorPhase::Active {
props.power
} else {
0
}
} else if args.block.id == BlockId::CALIBRATED_SCULK_SENSOR {
let props =
CalibratedSculkSensorLikeProperties::from_state_id(args.state.id, args.block);
if props.sculk_sensor_phase == SculkSensorPhase::Active {
props.power
} else {
0
}
} else {
0
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
if args.block.id == BlockId::SCULK_SENSOR {
let mut props = SculkSensorLikeProperties::from_state_id(state.id, args.block);
match props.sculk_sensor_phase {
SculkSensorPhase::Active => {
props.sculk_sensor_phase = SculkSensorPhase::Cooldown;
props.power = 0;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.schedule_block_tick(
args.block,
*args.position,
10,
TickPriority::Normal,
);
args.world.update_neighbors(args.position, None).await;
}
SculkSensorPhase::Cooldown => {
props.sculk_sensor_phase = SculkSensorPhase::Inactive;
props.power = 0;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.update_neighbors(args.position, None).await;
}
SculkSensorPhase::Inactive => {}
}
} else if args.block.id == BlockId::CALIBRATED_SCULK_SENSOR {
let mut props =
CalibratedSculkSensorLikeProperties::from_state_id(state.id, args.block);
match props.sculk_sensor_phase {
SculkSensorPhase::Active => {
props.sculk_sensor_phase = SculkSensorPhase::Cooldown;
props.power = 0;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.schedule_block_tick(
args.block,
*args.position,
10,
TickPriority::Normal,
);
args.world.update_neighbors(args.position, None).await;
}
SculkSensorPhase::Cooldown => {
props.sculk_sensor_phase = SculkSensorPhase::Inactive;
props.power = 0;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.update_neighbors(args.position, None).await;
}
SculkSensorPhase::Inactive => {}
}
}
})
}
}

View File

@ -1,6 +1,8 @@
use std::sync::Arc;
use crate::block::{BlockFuture, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs};
use crate::block::{
BlockFuture, GetComparatorOutputArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -98,6 +100,21 @@ impl BlockBehaviour for ShulkerBoxBlock {
BlockActionResult::Success
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}
impl ShulkerBoxBlock {

View File

@ -21,8 +21,8 @@ use tokio::sync::Mutex;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
registry::BlockActionResult,
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -143,4 +143,19 @@ impl BlockBehaviour for SmokerBlock {
args.world.remove_block_entity(args.position);
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
}
}

View File

@ -195,8 +195,28 @@ impl HopperBlockEntity {
}
let (block, state) = world.get_block_and_state(pos_up);
if !(state.is_solid() && block.has_tag(&tag::Block::MINECRAFT_DOES_NOT_BLOCK_HOPPERS)) {
// TODO getItemsAtAndAbove(level, hopper)
return false;
let pos_up_f = pos_up.to_f64();
let search_box = pumpkin_util::math::boundingbox::BoundingBox::new(
pos_up_f,
pos_up_f.add_raw(1.0, 1.0, 1.0),
);
let entities = world.get_entities_at_box(&search_box);
for entity_base in entities {
if let Some(item_entity) = entity_base.clone().get_item_entity() {
let mut stack = item_entity.get_item_stack().lock().await;
if !stack.is_empty() {
let backup = stack.clone();
let one_item = stack.split(1);
if Self::add_one_item(self, self, one_item).await {
if stack.is_empty() {
item_entity.get_entity().remove().await;
}
return true;
}
*stack = backup;
}
}
}
}
false
}

View File

@ -499,3 +499,30 @@ impl BlockIsReplacing {
}
}
}
pub async fn calculate_comparator_output(
inventory: &dyn pumpkin_world::inventory::Inventory,
) -> u8 {
let size = inventory.size();
if size == 0 {
return 0;
}
let mut fill_sum = 0.0;
let mut non_empty_count = 0;
for i in 0..size {
let stack_mutex = inventory.get_stack(i).await;
let stack = stack_mutex.lock().await;
if !stack.is_empty() {
let max_stack = stack.get_max_stack_size() as f32;
let count = stack.item_count as f32;
fill_sum += count / max_stack;
non_empty_count += 1;
}
}
if non_empty_count == 0 {
return 0;
}
let percentage = fill_sum / (size as f32);
let output = 1.0 + percentage * 14.0;
output.floor() as u8
}

View File

@ -5,6 +5,7 @@ use crate::block::blocks::barrel::BarrelBlock;
use crate::block::blocks::barrier::BarrierBlock;
use crate::block::blocks::beacon::BeaconBlock;
use crate::block::blocks::bed::BedBlock;
use crate::block::blocks::beehive::BeehiveBlock;
use crate::block::blocks::brewing_stand::BrewingStandBlock;
use crate::block::blocks::cake::CakeBlock;
use crate::block::blocks::campfire::CampfireBlock;
@ -12,6 +13,7 @@ use crate::block::blocks::candle_cakes::CandleCakeBlock;
use crate::block::blocks::candles::CandleBlock;
use crate::block::blocks::carpet::{CarpetBlock, MossCarpetBlock, PaleMossCarpetBlock};
use crate::block::blocks::carved_pumpkin::CarvedPumpkinBlock;
use crate::block::blocks::cauldron::CauldronBlock;
use crate::block::blocks::chests::{ChestBlock, CopperChestBlock, TrappedChestBlock};
use crate::block::blocks::chiseled_bookshelf::ChiseledBookshelfBlock;
use crate::block::blocks::command::CommandBlock;
@ -93,10 +95,12 @@ use crate::block::blocks::redstone::bell::BellBlock;
use crate::block::blocks::redstone::buttons::ButtonBlock;
use crate::block::blocks::redstone::comparator::ComparatorBlock;
use crate::block::blocks::redstone::copper_bulb::CopperBulbBlock;
use crate::block::blocks::redstone::crafter::CrafterBlock;
use crate::block::blocks::redstone::daylight_detector::DaylightDetectorBlock;
use crate::block::blocks::redstone::dispenser::DispenserBlock;
use crate::block::blocks::redstone::dropper::DropperBlock;
use crate::block::blocks::redstone::lever::LeverBlock;
use crate::block::blocks::redstone::lightning_rod::LightningRodBlock;
use crate::block::blocks::redstone::observer::ObserverBlock;
use crate::block::blocks::redstone::pressure_plate::plate::PressurePlateBlock;
use crate::block::blocks::redstone::pressure_plate::weighted::WeightedPressurePlateBlock;
@ -106,9 +110,11 @@ use crate::block::blocks::redstone::rails::powered_rail::PoweredRailBlock;
use crate::block::blocks::redstone::rails::rail::RailBlock;
use crate::block::blocks::redstone::redstone_block::RedstoneBlock;
use crate::block::blocks::redstone::redstone_lamp::RedstoneLamp;
use crate::block::blocks::redstone::redstone_ore::RedstoneOreBlock;
use crate::block::blocks::redstone::redstone_torch::RedstoneTorchBlock;
use crate::block::blocks::redstone::redstone_wire::RedstoneWireBlock;
use crate::block::blocks::redstone::repeater::RepeaterBlock;
use crate::block::blocks::redstone::sculk_sensor::SculkSensorBlock;
use crate::block::blocks::redstone::target_block::TargetBlock;
use crate::block::blocks::redstone::tripwire::TripwireBlock;
use crate::block::blocks::redstone::tripwire_hook::TripwireHookBlock;
@ -266,6 +272,8 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(CommandBlock);
manager.register(JigsawBlock);
manager.register(ComposterBlock);
manager.register(CauldronBlock);
manager.register(BeehiveBlock);
manager.register(PressurePlateBlock);
manager.register(WeightedPressurePlateBlock);
manager.register(EndPortalBlock);
@ -318,6 +326,8 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(ButtonBlock);
manager.register(BellBlock);
manager.register(LeverBlock);
manager.register(LightningRodBlock);
manager.register(SculkSensorBlock);
manager.register(ObserverBlock);
manager.register(TripwireBlock);
manager.register(TripwireHookBlock);
@ -329,11 +339,13 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(RedstoneBlock);
manager.register(RedstoneLamp);
manager.register(RedstoneOreBlock);
manager.register(CopperBulbBlock);
manager.register(RedstoneTorchBlock);
manager.register(RedstoneWireBlock);
manager.register(RepeaterBlock);
manager.register(ComparatorBlock);
manager.register(CrafterBlock);
manager.register(TargetBlock);
manager.register(BarrelBlock);
manager.register(HopperBlock);

View File

@ -107,6 +107,14 @@ impl ItemEntity {
}
}
pub const fn get_item_stack(&self) -> &Mutex<ItemStack> {
&self.item_stack
}
pub const fn get_entity(&self) -> &Entity {
&self.entity
}
async fn can_merge(&self) -> bool {
if self.never_pickup.load(Ordering::Relaxed) || self.entity.removed.load(Ordering::Relaxed)
{