diff --git a/pumpkin-inventory/src/generic_container_screen_handler.rs b/pumpkin-inventory/src/generic_container_screen_handler.rs index 905080b4d..b24d0cbf8 100644 --- a/pumpkin-inventory/src/generic_container_screen_handler.rs +++ b/pumpkin-inventory/src/generic_container_screen_handler.rs @@ -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, + inventory: Arc, +) -> 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. diff --git a/pumpkin-inventory/src/gui_builder.rs b/pumpkin-inventory/src/gui_builder.rs index 39e01bf65..92f0bd4f0 100644 --- a/pumpkin-inventory/src/gui_builder.rs +++ b/pumpkin-inventory/src/gui_builder.rs @@ -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 }; diff --git a/pumpkin/src/block/blocks/barrel.rs b/pumpkin/src/block/blocks/barrel.rs index 5ea4ba244..c68dcf1e3 100644 --- a/pumpkin/src/block/blocks/barrel.rs +++ b/pumpkin/src/block/blocks/barrel.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/beehive.rs b/pumpkin/src/block/blocks/beehive.rs new file mode 100644 index 000000000..4842ca29a --- /dev/null +++ b/pumpkin/src/block/blocks/beehive.rs @@ -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> { + 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) + }) + } +} diff --git a/pumpkin/src/block/blocks/blast_furnace.rs b/pumpkin/src/block/blocks/blast_furnace.rs index 576fcc9d5..571fb6b21 100644 --- a/pumpkin/src/block/blocks/blast_furnace.rs +++ b/pumpkin/src/block/blocks/blast_furnace.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/brewing_stand.rs b/pumpkin/src/block/blocks/brewing_stand.rs index e77e6a978..43f015da8 100644 --- a/pumpkin/src/block/blocks/brewing_stand.rs +++ b/pumpkin/src/block/blocks/brewing_stand.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/cake.rs b/pumpkin/src/block/blocks/cake.rs index 626a9af45..0f7ea61e9 100644 --- a/pumpkin/src/block/blocks/cake.rs +++ b/pumpkin/src/block/blocks/cake.rs @@ -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> { + 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 { diff --git a/pumpkin/src/block/blocks/cauldron.rs b/pumpkin/src/block/blocks/cauldron.rs new file mode 100644 index 000000000..96b69fb73 --- /dev/null +++ b/pumpkin/src/block/blocks/cauldron.rs @@ -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> { + 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), + } + }) + } +} diff --git a/pumpkin/src/block/blocks/chests.rs b/pumpkin/src/block/blocks/chests.rs index 74fbadfda..03f51ed16 100644 --- a/pumpkin/src/block/blocks/chests.rs +++ b/pumpkin/src/block/blocks/chests.rs @@ -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( } } +async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Option { + 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> { + 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> { + 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> { + Box::pin(async move { get_chest_comparator_output(args).await }) + } } fn compute_chest_props( diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index af0513518..294f6f819 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/hopper.rs b/pumpkin/src/block/blocks/hopper.rs index 6bdf30fc9..9d9b8d6cc 100644 --- a/pumpkin/src/block/blocks/hopper.rs +++ b/pumpkin/src/block/blocks/hopper.rs @@ -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> { + 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( diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index d8b2cba39..7d97a9952 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -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; diff --git a/pumpkin/src/block/blocks/redstone/crafter.rs b/pumpkin/src/block/blocks/redstone/crafter.rs new file mode 100644 index 000000000..23ff5dd90 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/crafter.rs @@ -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); + +impl ScreenHandlerFactory for CrafterScreenFactory { + fn create_screen_handler<'a>( + &'a self, + sync_id: u8, + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + 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> { + Box::pin(async move { + if let Some(block_entity) = args.world.get_block_entity(args.position) { + let crafter = block_entity.as_any().downcast_ref::()?; + + 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 + } + }) + } +} diff --git a/pumpkin/src/block/blocks/redstone/dispenser.rs b/pumpkin/src/block/blocks/redstone/dispenser.rs index 47354c322..aeead2cb9 100644 --- a/pumpkin/src/block/blocks/redstone/dispenser.rs +++ b/pumpkin/src/block/blocks/redstone/dispenser.rs @@ -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); + +impl ScreenHandlerFactory for DispenserScreenFactory { + fn create_screen_handler<'a>( + &'a self, + sync_id: u8, + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + 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(rng: &mut R, min: f64, max: f64) -> f64 { + (rng.random::() - rng.random::()).mul_add(max, min) +} + +const fn to_normal(facing: Facing) -> Vector3 { + 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::() + 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::().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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/redstone/dropper.rs b/pumpkin/src/block/blocks/redstone/dropper.rs index 210ae39e1..710e81d36 100644 --- a/pumpkin/src/block/blocks/redstone/dropper.rs +++ b/pumpkin/src/block/blocks/redstone/dropper.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/blocks/redstone/lightning_rod.rs b/pumpkin/src/block/blocks/redstone/lightning_rod.rs new file mode 100644 index 000000000..3e002a7c8 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/lightning_rod.rs @@ -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, 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, + 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; + } + }) + } +} diff --git a/pumpkin/src/block/blocks/redstone/mod.rs b/pumpkin/src/block/blocks/redstone/mod.rs index c65273096..4627a8b05 100644 --- a/pumpkin/src/block/blocks/redstone/mod.rs +++ b/pumpkin/src/block/blocks/redstone/mod.rs @@ -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; diff --git a/pumpkin/src/block/blocks/redstone/redstone_ore.rs b/pumpkin/src/block/blocks/redstone/redstone_ore.rs new file mode 100644 index 000000000..12021863c --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/redstone_ore.rs @@ -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, 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; + } + }) + } +} diff --git a/pumpkin/src/block/blocks/redstone/sculk_sensor.rs b/pumpkin/src/block/blocks/redstone/sculk_sensor.rs new file mode 100644 index 000000000..a554e2312 --- /dev/null +++ b/pumpkin/src/block/blocks/redstone/sculk_sensor.rs @@ -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, 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 => {} + } + } + }) + } +} diff --git a/pumpkin/src/block/blocks/shulker_box.rs b/pumpkin/src/block/blocks/shulker_box.rs index a35d813ac..214075442 100644 --- a/pumpkin/src/block/blocks/shulker_box.rs +++ b/pumpkin/src/block/blocks/shulker_box.rs @@ -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> { + 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 { diff --git a/pumpkin/src/block/blocks/smoker.rs b/pumpkin/src/block/blocks/smoker.rs index 08d680dfa..ea573c92d 100644 --- a/pumpkin/src/block/blocks/smoker.rs +++ b/pumpkin/src/block/blocks/smoker.rs @@ -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> { + 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 + } + }) + } } diff --git a/pumpkin/src/block/entities/hopper.rs b/pumpkin/src/block/entities/hopper.rs index a1331f827..896f2843a 100644 --- a/pumpkin/src/block/entities/hopper.rs +++ b/pumpkin/src/block/entities/hopper.rs @@ -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 } diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index ca25468c8..322e5d6ae 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -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 +} diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index 8908bd146..c0f4dbfb2 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -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 { 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 { 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 { 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); diff --git a/pumpkin/src/entity/item.rs b/pumpkin/src/entity/item.rs index cacbb3cc2..816652c3e 100644 --- a/pumpkin/src/entity/item.rs +++ b/pumpkin/src/entity/item.rs @@ -107,6 +107,14 @@ impl ItemEntity { } } + pub const fn get_item_stack(&self) -> &Mutex { + &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) {