Implement Piston 1.5/2

This commit is contained in:
Alexander Medvedev 2025-05-13 19:29:18 +02:00
parent bcf9524334
commit f79167cece
10 changed files with 214 additions and 174 deletions

View File

@ -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<Arc<dyn BlockEntity>>
_ => None,
}
}
pub fn has_block_block_entity(block: &Block) -> bool {
BLOCK_ENTITY_TYPES.contains(&block.name)
}

View File

@ -67,6 +67,10 @@ impl BlockDirection {
}
}
pub fn by_index(index: usize) -> Option<Self> {
Self::all().get(index % Self::all().len()).cloned()
}
pub fn to_offset(&self) -> Vector3<i32> {
match self {
BlockDirection::Down => (0, -1, 0),

View File

@ -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<dyn SimpleWorld>) {
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<Self>, chunk: &Vector2<i32>) {
self.clean_chunks(&[*chunk]).await;
}

View File

@ -21,7 +21,7 @@ pub struct PistonHandler<'a> {
motion_direction: BlockDirection,
pub moved_blocks: Vec<BlockPos>,
pub broken_blocks: Vec<BlockPos>,
_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;

View File

@ -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<World>,
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<World>,
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<World>, 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,13 +328,21 @@ async fn try_move(world: &Arc<World>, 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 {
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
.add_synced_block_event(*block_pos, r#type, dir.to_index())
.await;
@ -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<BlockPos> = handler.broken_blocks;
dbg!(&broken_blocks);
let mut affected_block_states: Vec<BlockState> =
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
}

View File

@ -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<Player>,
location: BlockPos,
_server: &Server,
world: Arc<World>,
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;
}
}
}
}

View File

@ -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;

View File

@ -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<World>,
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<World>,
block: &Block,
block_pos: &BlockPos,
_source_block: &Block,
_notify: bool,
) {
try_move(world, block, block_pos).await;
}
}
async fn try_move(world: &Arc<World>, 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;
}
}

View File

@ -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<BlockRegistry> {
// 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<BlockRegistry> {
Arc::new(manager)
}
#[derive(Clone)]
pub struct BlockEvent {
pub pos: BlockPos,
pub r#type: u8,

View File

@ -199,7 +199,12 @@ impl World {
async fn flush_synced_block_events(self: &Arc<Self>) {
let mut queue = self.synced_block_event_queue.lock().await;
for event in queue.drain(..) {
let events: Vec<BlockEvent> = 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<Self>, 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