chore: more redstone implemented
This commit is contained in:
parent
1f4030a28d
commit
43e3581e70
@ -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.
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
24
pumpkin/src/block/blocks/beehive.rs
Normal file
24
pumpkin/src/block/blocks/beehive.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
36
pumpkin/src/block/blocks/cauldron.rs
Normal file
36
pumpkin/src/block/blocks/cauldron.rs
Normal 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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
215
pumpkin/src/block/blocks/redstone/crafter.rs
Normal file
215
pumpkin/src/block/blocks/redstone/crafter.rs
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
113
pumpkin/src/block/blocks/redstone/lightning_rod.rs
Normal file
113
pumpkin/src/block/blocks/redstone/lightning_rod.rs
Normal 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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
75
pumpkin/src/block/blocks/redstone/redstone_ore.rs
Normal file
75
pumpkin/src/block/blocks/redstone/redstone_ore.rs
Normal 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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
179
pumpkin/src/block/blocks/redstone/sculk_sensor.rs
Normal file
179
pumpkin/src/block/blocks/redstone/sculk_sensor.rs
Normal 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 => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user