From f79167cece312de5ff9fbcc35618e2ec9f749325 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Tue, 13 May 2025 19:29:18 +0200 Subject: [PATCH] Implement Piston 1.5/2 --- pumpkin-world/src/block/entities/mod.rs | 5 + pumpkin-world/src/block/mod.rs | 4 + pumpkin-world/src/level.rs | 12 ++ pumpkin/src/block/blocks/piston/mod.rs | 49 +++--- pumpkin/src/block/blocks/piston/piston.rs | 166 +++++++++++++----- .../src/block/blocks/piston/piston_head.rs | 44 ++++- pumpkin/src/block/blocks/redstone/mod.rs | 1 - pumpkin/src/block/blocks/redstone/piston.rs | 93 ---------- pumpkin/src/block/mod.rs | 5 +- pumpkin/src/world/mod.rs | 9 +- 10 files changed, 214 insertions(+), 174 deletions(-) delete mode 100644 pumpkin/src/block/blocks/redstone/piston.rs diff --git a/pumpkin-world/src/block/entities/mod.rs b/pumpkin-world/src/block/entities/mod.rs index 42d2d1fda..b7685ccbb 100644 --- a/pumpkin-world/src/block/entities/mod.rs +++ b/pumpkin-world/src/block/entities/mod.rs @@ -6,6 +6,7 @@ use bed::BedBlockEntity; use chest::ChestBlockEntity; use comparator::ComparatorBlockEntity; use piston::PistonBlockEntity; +use pumpkin_data::{Block, block_properties::BLOCK_ENTITY_TYPES}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use sign::SignBlockEntity; @@ -82,3 +83,7 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option> _ => None, } } + +pub fn has_block_block_entity(block: &Block) -> bool { + BLOCK_ENTITY_TYPES.contains(&block.name) +} diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 329613349..7fbc79c74 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -67,6 +67,10 @@ impl BlockDirection { } } + pub fn by_index(index: usize) -> Option { + Self::all().get(index % Self::all().len()).cloned() + } + pub fn to_offset(&self) -> Vector3 { match self { BlockDirection::Down => (0, -1, 0), diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 1dd687360..8329dd82a 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -26,6 +26,7 @@ use crate::{ }, generation::{Seed, WorldGenerator, get_world_gen}, lock::{LevelLocker, anvil::AnvilLevelLocker}, + world::SimpleWorld, world_info::{ LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter, anvil::{AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME}, @@ -321,6 +322,17 @@ impl Level { }); } + pub async fn tick_block_entities(&self, world: Arc) { + for chunk in self.loaded_chunks.iter() { + let chunk = chunk.read().await; + let cloned_entities = chunk.block_entities.clone(); + drop(chunk); + for block_entity in &cloned_entities { + block_entity.1.1.tick(&world).await; + } + } + } + pub async fn clean_chunk(self: &Arc, chunk: &Vector2) { self.clean_chunks(&[*chunk]).await; } diff --git a/pumpkin/src/block/blocks/piston/mod.rs b/pumpkin/src/block/blocks/piston/mod.rs index 4db46605a..2b509aa3d 100644 --- a/pumpkin/src/block/blocks/piston/mod.rs +++ b/pumpkin/src/block/blocks/piston/mod.rs @@ -21,7 +21,7 @@ pub struct PistonHandler<'a> { motion_direction: BlockDirection, pub moved_blocks: Vec, pub broken_blocks: Vec, - _piston_direction: BlockDirection, + piston_direction: BlockDirection, } impl<'a> PistonHandler<'a> { @@ -29,7 +29,7 @@ impl<'a> PistonHandler<'a> { let motion_direction; let pos_to = if retracted { motion_direction = dir; - pos.offset_dir(dir.to_offset(), 1) + pos.offset(dir.to_offset()) } else { motion_direction = dir.opposite(); pos.offset_dir(dir.to_offset(), 2) @@ -37,7 +37,7 @@ impl<'a> PistonHandler<'a> { PistonHandler { world, pos_from: pos, - _piston_direction: dir, + piston_direction: dir, retracted, motion_direction, pos_to, @@ -54,14 +54,19 @@ impl<'a> PistonHandler<'a> { .get_block_and_block_state(&self.pos_to) .await .unwrap(); + dbg!(PistonBlock::is_movable( + &block, + &block_state, + self.motion_direction, + false, + self.piston_direction, + )); if !PistonBlock::is_movable( &block, &block_state, - // self.world, - // self.pos_to, - // self.motion_direction, - // false, - // self.piston_direction, + self.motion_direction, + false, + self.piston_direction, ) { if self.retracted && block_state.piston_behavior == PistonBehavior::Destroy { self.broken_blocks.push(self.pos_to); @@ -97,21 +102,13 @@ impl<'a> PistonHandler<'a> { Self::is_block_sticky(state) || Self::is_block_sticky(adjacent_state) } - async fn try_move(&mut self, pos: BlockPos, _dir: BlockDirection) -> bool { + async fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool { let (mut block, mut block_state) = self.world.get_block_and_block_state(&pos).await.unwrap(); if block_state.is_air() { return true; } - if !PistonBlock::is_movable( - &block, - &block_state, - // self.world, - // pos, - // self.motion_direction, - // false, - // dir, - ) { + if !PistonBlock::is_movable(&block, &block_state, self.motion_direction, false, dir) { return true; } if pos == self.pos_from { @@ -137,11 +134,9 @@ impl<'a> PistonHandler<'a> { || !PistonBlock::is_movable( &block, &block_state, - // self.world, - // block_pos, - // self.motion_direction, - // false, - // self.motion_direction.opposite(), + self.motion_direction, + false, + self.motion_direction.opposite(), ) || block_pos == self.pos_from { @@ -185,11 +180,9 @@ impl<'a> PistonHandler<'a> { if !PistonBlock::is_movable( &block, &block_state, - // self.world, - // block_pos2, - // self.motion_direction, - // true, - // self.motion_direction, + self.motion_direction, + true, + self.motion_direction, ) || block_pos2 == self.pos_from { return false; diff --git a/pumpkin/src/block/blocks/piston/piston.rs b/pumpkin/src/block/blocks/piston/piston.rs index c43d64fb4..9f81a945b 100644 --- a/pumpkin/src/block/blocks/piston/piston.rs +++ b/pumpkin/src/block/blocks/piston/piston.rs @@ -7,6 +7,7 @@ use pumpkin_data::{ BlockProperties, MovingPistonLikeProperties, PistonHeadLikeProperties, PistonType, get_block_by_state_id, get_state_by_state_id, }, + block_state::PistonBehavior, }; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; @@ -14,7 +15,7 @@ use pumpkin_world::{ BlockStateId, block::{ BlockDirection, FacingExt, - entities::{BlockEntity, piston::PistonBlockEntity}, + entities::{BlockEntity, has_block_block_entity, piston::PistonBlockEntity}, }, world::BlockFlags, }; @@ -30,7 +31,7 @@ use crate::{ world::World, }; -use super::{PistonHandler, piston_extension::MovingPistonProps}; +use super::PistonHandler; pub(crate) type PistonProps = pumpkin_data::block_properties::StickyPistonLikeProperties; @@ -47,7 +48,13 @@ impl BlockMetadata for PistonBlock { } impl PistonBlock { - pub fn is_movable(block: &Block, state: &BlockState) -> bool { + pub fn is_movable( + block: &Block, + state: &BlockState, + dir: BlockDirection, + can_break: bool, + piston_dir: BlockDirection, + ) -> bool { // TODO: more checks if state.is_air() { return true; @@ -60,7 +67,24 @@ impl PistonBlock { { return false; } - true + if block == &Block::PISTON || block == &Block::STICKY_PISTON { + let props = PistonProps::from_state_id(state.id, block); + if props.extended { + return false; + } + } else { + #[expect(clippy::float_cmp)] + if state.hardness == -1.0 { + return false; + } + match state.piston_behavior { + pumpkin_data::block_state::PistonBehavior::Destroy => return can_break, + pumpkin_data::block_state::PistonBehavior::Block => return false, + pumpkin_data::block_state::PistonBehavior::PushOnly => return dir == piston_dir, + _ => {} + } + } + !has_block_block_entity(block) } } @@ -109,13 +133,14 @@ impl PumpkinBlock for PistonBlock { try_move(world, block, block_pos).await; } + #[expect(clippy::too_many_lines)] async fn on_synced_block_event( &self, block: &Block, world: &Arc, pos: &BlockPos, r#type: u8, - _data: u8, + data: u8, ) -> bool { let state = world.get_block_state(pos).await.unwrap(); let mut props = PistonProps::from_state_id(state.id, block); @@ -124,7 +149,7 @@ impl PumpkinBlock for PistonBlock { // I don't think this is optimal ? let sticky = block == &Block::STICKY_PISTON; - let should_extend = should_extend(world, block, &state, pos, dir).await; + let should_extend = should_extend(world, pos, dir).await; if should_extend && (r#type == 1 || r#type == 2) { props.extended = true; world @@ -138,6 +163,7 @@ impl PumpkinBlock for PistonBlock { return false; } + // Extend Piston if r#type == 0 { if !move_piston(world, dir, pos, true, sticky).await { return false; @@ -152,6 +178,8 @@ impl PumpkinBlock for PistonBlock { .await; return true; } + // Reduce Piston + let extended_pos = pos.offset(dir.to_offset()); if let Some((block_entity_nbt, _block_entity)) = world.get_block_entity(&extended_pos).await @@ -169,26 +197,67 @@ impl PumpkinBlock for PistonBlock { PistonType::Normal }; - let state = props.to_state_id(&Block::MOVING_PISTON); - world - .set_block_state(pos, state, BlockFlags::FORCE_STATE) + .set_block_state( + pos, + props.to_state_id(&Block::MOVING_PISTON), + BlockFlags::FORCE_STATE, + ) .await; + let mut props = PistonProps::default(block); + props.facing = BlockDirection::by_index((data & 7) as usize) + .unwrap() + .to_facing(); + world .add_block_entity(Arc::new(PistonBlockEntity { position: *pos, - facing: dir.to_facing().to_block_direction(), - pushed_block_state: get_state_by_state_id(state).unwrap(), + facing: dir, + pushed_block_state: get_state_by_state_id(props.to_state_id(block)).unwrap(), current_progress: 0.0.into(), last_progress: 0.0.into(), extending: false, source: true, })) .await; + world.update_neighbors(pos, None).await; if sticky { + let pos = pos.offset_dir(dir.to_offset(), 2); + let (block, state) = world.get_block_and_block_state(&pos).await.unwrap(); + let mut bl2 = false; + if block == Block::MOVING_PISTON { + if let Some(entity) = world.get_block_entity(&pos).await { + let piston = PistonBlockEntity::from_nbt(&entity.0, pos); + if piston.facing == dir && piston.extending { + piston.finish(world.clone()).await; + bl2 = true; + } + } + } + if !bl2 { + if r#type == 1 + && !state.is_air() + && Self::is_movable(&block, &state, dir, false, dir) + && (state.piston_behavior == PistonBehavior::Normal + || block == Block::PISTON + || block == Block::STICKY_PISTON) + { + move_piston(world, dir, &pos, false, sticky).await; + } else { + // remove + world + .set_block_state( + &extended_pos, + Block::AIR.default_state_id, + BlockFlags::NOTIFY_ALL, + ) + .await; + } + } } else { + // remove world .set_block_state( &extended_pos, @@ -201,42 +270,37 @@ impl PumpkinBlock for PistonBlock { } } -async fn should_extend( - world: &Arc, - block: &Block, - state: &BlockState, - block_pos: &BlockPos, - piston_dir: BlockDirection, -) -> bool { - // Pistons can't be powered from the same direction as they are facing +async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDirection) -> bool { for dir in BlockDirection::all() { - if dir == piston_dir - || !is_emitting_redstone_power( - block, - state, - world, - &block_pos.offset(dir.to_offset()), - dir, - ) + let neighbor_pos = block_pos.offset(dir.to_offset()); + let (block, state) = world + .get_block_and_block_state(&neighbor_pos) .await + .unwrap(); + // Pistons can't be powered from the same direction as they are facing + if dir == piston_dir + || !is_emitting_redstone_power(&block, &state, world, &neighbor_pos, dir).await { continue; } return true; } - if is_emitting_redstone_power(block, state, world, block_pos, BlockDirection::Down).await { + let neighbor_pos = block_pos.offset(BlockDirection::Down.to_offset()); + let (block, state) = world + .get_block_and_block_state(&neighbor_pos) + .await + .unwrap(); + if is_emitting_redstone_power(&block, &state, world, block_pos, BlockDirection::Down).await { return true; } for dir in BlockDirection::all() { - if dir == BlockDirection::Down - || !is_emitting_redstone_power( - block, - state, - world, - &block_pos.up().offset(dir.to_offset()), - dir, - ) + let neighbor_pos = block_pos.up().offset(dir.to_offset()); + let (block, state) = world + .get_block_and_block_state(&neighbor_pos) .await + .unwrap(); + if dir == BlockDirection::Down + || !is_emitting_redstone_power(&block, &state, world, &neighbor_pos, dir).await { continue; } @@ -249,7 +313,7 @@ async fn try_move(world: &Arc, block: &Block, block_pos: &BlockPos) { let state = world.get_block_state(block_pos).await.unwrap(); let props = PistonProps::from_state_id(state.id, block); let dir = props.facing.to_block_direction(); - let should_extent = should_extend(world, block, &state, block_pos, dir).await; + let should_extent = should_extend(world, block_pos, dir).await; if should_extent && !props.extended { if PistonHandler::new(world, *block_pos, dir, true) @@ -264,11 +328,19 @@ async fn try_move(world: &Arc, block: &Block, block_pos: &BlockPos) { let new_pos = block_pos.offset_dir(dir.to_offset(), 2); let (new_block, new_state) = world.get_block_and_block_state(&new_pos).await.unwrap(); let mut r#type = 1; + if new_block == Block::MOVING_PISTON { - let new_props = MovingPistonProps::from_state_id(new_state.id, &new_block); - // TODO: check more things + let new_props = MovingPistonLikeProperties::from_state_id(new_state.id, &new_block); if new_props.facing == props.facing { - r#type = 2; + if let Some(entity) = world.get_block_entity(&new_pos).await { + let piston = PistonBlockEntity::from_nbt(&entity.0, new_pos); + if piston.extending && piston.current_progress.load() < 0.5 + // TODO: more stuff... + { + // Piston reduced too quickly, if its a stick piston no blocks will be dragged + r#type = 2; + } + } } } world @@ -308,13 +380,11 @@ async fn move_piston( for &block_pos in &moved_blocks { let block_state = world.get_block_state(&block_pos).await.unwrap(); - dbg!(block_state.id); moved_block_states.push(block_state.clone()); moved_blocks_map.insert(block_pos, block_state); } let broken_blocks: Vec = handler.broken_blocks; - dbg!(&broken_blocks); let mut affected_block_states: Vec = Vec::with_capacity(moved_blocks.len() + broken_blocks.len()); let move_direction = if extend { dir } else { dir.opposite() }; @@ -322,7 +392,11 @@ async fn move_piston( for &broken_block_pos in broken_blocks.iter().rev() { let block_state = world.get_block_state(&broken_block_pos).await.unwrap(); world - .break_block(&broken_block_pos, None, BlockFlags::empty()) + .break_block( + &broken_block_pos, + None, + BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE, + ) .await; affected_block_states.push(block_state); } @@ -402,7 +476,6 @@ async fn move_piston( } for (pos, state) in &moved_blocks_map { - // state.prepare(world, pos, BlockFlags::NOTIFY_LISTENERS); world .block_registry .prepare( @@ -451,12 +524,13 @@ async fn move_piston( world.update_neighbors(&broken_block_pos, None).await; } } - for &moved_block_pos in moved_blocks.iter().rev() { world.update_neighbors(&moved_block_pos, None).await; } - // world.update_neighbors(&extended_pos, None).await; + if extend { + world.update_neighbors(&extended_pos, None).await; + } true } diff --git a/pumpkin/src/block/blocks/piston/piston_head.rs b/pumpkin/src/block/blocks/piston/piston_head.rs index c04515b94..1b31077ff 100644 --- a/pumpkin/src/block/blocks/piston/piston_head.rs +++ b/pumpkin/src/block/blocks/piston/piston_head.rs @@ -1,10 +1,46 @@ -use async_trait::async_trait; -use pumpkin_macros::pumpkin_block; +use std::sync::Arc; -use crate::block::pumpkin_block::PumpkinBlock; +use async_trait::async_trait; +use pumpkin_data::block_properties::BlockProperties; +use pumpkin_data::{Block, BlockState}; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::{block::FacingExt, world::BlockFlags}; + +use crate::entity::player::Player; +use crate::{ + block::pumpkin_block::{BlockMetadata, PumpkinBlock}, + world::World, +}; + +use super::piston::{PistonBlock, PistonProps}; +use crate::server::Server; + +pub(crate) type PistonHeadProperties = pumpkin_data::block_properties::PistonHeadLikeProperties; #[pumpkin_block("minecraft:piston_head")] pub struct PistonHeadBlock; #[async_trait] -impl PumpkinBlock for PistonHeadBlock {} +impl PumpkinBlock for PistonHeadBlock { + async fn broken( + &self, + _block: &Block, + _player: &Arc, + location: BlockPos, + _server: &Server, + world: Arc, + state: BlockState, + ) { + let props = PistonHeadProperties::from_state_id(state.id, &Block::PISTON_HEAD); + let pos = location.offset(props.facing.opposite().to_block_direction().to_offset()); + let (new_block, new_state) = world.get_block_and_block_state(&pos).await.unwrap(); + if PistonBlock::ids(&PistonBlock).contains(&new_block.name) { + let props = PistonProps::from_state_id(new_state.id, &new_block); + if props.extended { + // TODO: use player + world.break_block(&pos, None, BlockFlags::SKIP_DROPS).await; + } + } + } +} diff --git a/pumpkin/src/block/blocks/redstone/mod.rs b/pumpkin/src/block/blocks/redstone/mod.rs index b60954889..79ae330e2 100644 --- a/pumpkin/src/block/blocks/redstone/mod.rs +++ b/pumpkin/src/block/blocks/redstone/mod.rs @@ -13,7 +13,6 @@ use crate::world::World; pub mod buttons; pub mod lever; pub mod observer; -pub mod piston; pub mod rails; pub mod redstone_block; pub mod redstone_lamp; diff --git a/pumpkin/src/block/blocks/redstone/piston.rs b/pumpkin/src/block/blocks/redstone/piston.rs deleted file mode 100644 index ef7600aa8..000000000 --- a/pumpkin/src/block/blocks/redstone/piston.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::sync::Arc; - -use crate::{block::BlockIsReplacing, entity::player::Player}; -use async_trait::async_trait; -use pumpkin_data::{Block, block_properties::BlockProperties}; -use pumpkin_protocol::server::play::SUseItemOn; -use pumpkin_util::math::position::BlockPos; -use pumpkin_world::{BlockStateId, block::BlockDirection, world::BlockFlags}; - -use crate::{ - block::pumpkin_block::{BlockMetadata, PumpkinBlock}, - server::Server, - world::World, -}; - -use super::block_receives_redstone_power; - -type PistonProps = pumpkin_data::block_properties::StickyPistonLikeProperties; - -pub struct PistonBlock; - -impl BlockMetadata for PistonBlock { - fn namespace(&self) -> &'static str { - "minecraft" - } - - fn ids(&self) -> &'static [&'static str] { - &[Block::PISTON.name, Block::STICKY_PISTON.name] - } -} - -#[async_trait] -impl PumpkinBlock for PistonBlock { - async fn on_place( - &self, - _server: &Server, - _world: &World, - player: &Player, - block: &Block, - _block_pos: &BlockPos, - _face: BlockDirection, - _replacing: BlockIsReplacing, - _use_item_on: &SUseItemOn, - ) -> BlockStateId { - let mut props = PistonProps::default(block); - props.extended = false; - props.facing = player.living_entity.entity.get_facing().opposite(); - props.to_state_id(block) - } - - async fn placed( - &self, - world: &Arc, - block: &Block, - state_id: BlockStateId, - pos: &BlockPos, - old_state_id: BlockStateId, - _notify: bool, - ) { - if old_state_id == state_id { - return; - } - try_move(world, block, pos).await; - } - - async fn on_neighbor_update( - &self, - world: &Arc, - block: &Block, - block_pos: &BlockPos, - _source_block: &Block, - _notify: bool, - ) { - try_move(world, block, block_pos).await; - } -} - -async fn try_move(world: &Arc, block: &Block, block_pos: &BlockPos) { - let state = world.get_block_state(block_pos).await.unwrap(); - let mut props = PistonProps::from_state_id(state.id, block); - let is_receiving_power = block_receives_redstone_power(world, block_pos).await; - - if is_receiving_power { - props.extended = !props.extended; - world - .set_block_state( - block_pos, - props.to_state_id(block), - BlockFlags::NOTIFY_LISTENERS, - ) - .await; - } -} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 73c2d2d9f..f9dc73ad1 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -13,11 +13,12 @@ use blocks::iron_bars::IronBarsBlock; use blocks::logs::LogBlock; use blocks::nether_portal::NetherPortalBlock; use blocks::note::NoteBlock; +use blocks::piston::piston::PistonBlock; use blocks::piston::piston_extension::PistonExtensionBlock; +use blocks::piston::piston_head::PistonHeadBlock; use blocks::pumpkin::PumpkinBlock; use blocks::redstone::buttons::ButtonBlock; use blocks::redstone::observer::ObserverBlock; -use blocks::redstone::piston::PistonBlock; use blocks::redstone::rails::activator_rail::ActivatorRailBlock; use blocks::redstone::rails::detector_rail::DetectorRailBlock; use blocks::redstone::rails::powered_rail::PoweredRailBlock; @@ -106,6 +107,7 @@ pub fn default_registry() -> Arc { // Piston manager.register(PistonBlock); manager.register(PistonExtensionBlock); + manager.register(PistonHeadBlock); manager.register(RedstoneBlock); manager.register(RedstoneLamp); @@ -127,6 +129,7 @@ pub fn default_registry() -> Arc { Arc::new(manager) } +#[derive(Clone)] pub struct BlockEvent { pub pos: BlockPos, pub r#type: u8, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 67fb3b95b..6a80e375a 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -199,7 +199,12 @@ impl World { async fn flush_synced_block_events(self: &Arc) { let mut queue = self.synced_block_event_queue.lock().await; - for event in queue.drain(..) { + let events: Vec = queue.clone(); + queue.clear(); + // THIS IS IMPORTANT + // it prevents deadlocks and also removes the need to wait for a lock when adding a new synced block + drop(queue); + for event in events { let block = self.get_block(&event.pos).await.unwrap(); // TODO if !self .block_registry @@ -366,6 +371,8 @@ impl World { pub async fn tick(self: &Arc, server: &Server) { self.flush_block_updates().await; + // tick block entities + self.level.tick_block_entities(self.clone()).await; self.flush_synced_block_events().await; // world ticks