From 9e8b76d6ded2d50dbb31448dfc11b410c6a3991d Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 23 Mar 2025 11:55:06 +0100 Subject: [PATCH] implement sugar cane it currently does not grow because random ticks are not implemented, but now it checks if it can be placed and is destroyed correctly when broken --- pumpkin-world/src/level.rs | 18 ++--- pumpkin/src/block/blocks/mod.rs | 1 + pumpkin/src/block/blocks/sugar_cane.rs | 94 ++++++++++++++++++++++++++ pumpkin/src/block/mod.rs | 2 + pumpkin/src/block/pumpkin_block.rs | 18 ++--- pumpkin/src/world/mod.rs | 27 ++++---- 6 files changed, 131 insertions(+), 29 deletions(-) create mode 100644 pumpkin/src/block/blocks/sugar_cane.rs diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 28916f1b8..db75cd83e 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -47,7 +47,7 @@ pub struct Level { // Chunks that are paired with chunk watchers. When a chunk is no longer watched, it is removed // from the loaded chunks map and sent to the underlying ChunkIO - loaded_chunks: Arc, SyncChunk>>, + pub loaded_chunks: Arc, SyncChunk>>, chunk_watchers: Arc, usize>>, chunk_saver: Arc>, @@ -530,15 +530,17 @@ impl Level { pub async fn get_and_tick_block_ticks(&self) -> Vec { let mut block_ticks = self.block_ticks.lock().await; let mut ticks = Vec::new(); - block_ticks.retain_mut(|tick| { + let mut remaining_ticks = Vec::new(); + for mut tick in block_ticks.drain(..) { tick.delay = tick.delay.saturating_sub(1); if tick.delay == 0 { - ticks.push(tick.clone()); - false + ticks.push(tick); } else { - true + remaining_ticks.push(tick); } - }); + } + + *block_ticks = remaining_ticks; ticks.sort_by_key(|tick| tick.priority); ticks } @@ -553,13 +555,13 @@ impl Level { pub async fn schedule_block_tick( &self, block_id: u16, - block_pos: &BlockPos, + block_pos: BlockPos, delay: u16, priority: TickPriority, ) { let mut block_ticks = self.block_ticks.lock().await; block_ticks.push(ScheduledTick { - block_pos: *block_pos, + block_pos, delay, priority, target_block_id: block_id, diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 3a9a119b2..2ca41508e 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod furnace; pub(crate) mod jukebox; pub(crate) mod logs; pub(crate) mod redstone; +pub(crate) mod sugar_cane; pub(crate) mod tnt; pub(crate) mod torches; /// The standard destroy with container removes the player forcibly from the container, diff --git a/pumpkin/src/block/blocks/sugar_cane.rs b/pumpkin/src/block/blocks/sugar_cane.rs new file mode 100644 index 000000000..8b51b1068 --- /dev/null +++ b/pumpkin/src/block/blocks/sugar_cane.rs @@ -0,0 +1,94 @@ +use async_trait::async_trait; +use pumpkin_data::block::{ + Block, BlockProperties, CactusLikeProperties, EnumVariants, Integer0To15, +}; +use pumpkin_macros::pumpkin_block; +use pumpkin_world::block::BlockDirection; +use pumpkin_world::chunk::TickPriority; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::world::BlockFlags; +use crate::world::World; +use pumpkin_util::math::position::BlockPos; + +#[pumpkin_block("minecraft:sugar_cane")] +pub struct SugarCaneBlock; + +#[async_trait] +impl PumpkinBlock for SugarCaneBlock { + async fn on_scheduled_tick(&self, world: &World, _block: &Block, pos: &BlockPos) { + if !self.can_place_at(world, pos).await { + // TODO: break the block so it drops loot, but for this an &Arc is required + world.set_block_state(pos, 0, BlockFlags::empty()).await; + } + } + + async fn random_tick(&self, block: &Block, world: &World, pos: &BlockPos) { + if world.get_block_state(&pos.up()).await.unwrap().air { + let state_id = world + .get_block_state(pos) + .await + .expect("`location` should be a sugar cane") + .id; + let age = CactusLikeProperties::from_state_id(state_id, block).age; + if age == Integer0To15::L15 { + world + .set_block_state(&pos.up(), state_id, BlockFlags::empty()) + .await; + let props = CactusLikeProperties { + age: Integer0To15::L0, + }; + world + .set_block_state(pos, props.to_state_id(block), BlockFlags::empty()) + .await; + } else { + let props = CactusLikeProperties { + age: Integer0To15::from_index(age.to_index() + 1), + }; + world + .set_block_state(pos, props.to_state_id(block), BlockFlags::empty()) + .await; + } + } + } + + async fn get_state_for_neighbor_update( + &self, + world: &World, + block: &Block, + state: u16, + pos: &BlockPos, + _direction: &BlockDirection, + _neighbor_pos: &BlockPos, + _neighbor_state: u16, + ) -> u16 { + if !self.can_place_at(world, pos).await { + world + .schedule_block_tick(block, *pos, 1, TickPriority::Normal) + .await; + } + state + } + + async fn can_place_at(&self, world: &World, pos: &BlockPos) -> bool { + let block = world.get_block(&pos.down()).await.unwrap(); + + if block == Block::SUGAR_CANE { + return true; + } + + // TODO: use tags + if block == Block::DIRT || block == Block::SAND { + for direction in BlockDirection::horizontal() { + let block = world + .get_block(&pos.down().offset(direction.to_offset())) + .await + .unwrap(); + if block == Block::WATER { + return true; + } + } + } + false + } +} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index b8948d279..61350f3cc 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -11,6 +11,7 @@ use blocks::redstone::redstone_torch::register_redstone_torch_blocks; use blocks::redstone::redstone_wire::RedstoneWireBlock; use blocks::redstone::repeater::RepeaterBlock; use blocks::redstone::target_block::TargetBlock; +use blocks::sugar_cane::SugarCaneBlock; use blocks::torches::register_torch_blocks; use blocks::{ chest::ChestBlock, furnace::FurnaceBlock, redstone::lever::LeverBlock, tnt::TNTBlock, @@ -42,6 +43,7 @@ pub fn default_registry() -> Arc { let mut manager = BlockRegistry::default(); manager.register(JukeboxBlock); + manager.register(SugarCaneBlock); manager.register(CraftingTableBlock); manager.register(FurnaceBlock); manager.register(ChestBlock); diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index cbc666f61..2ecae183f 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -62,7 +62,9 @@ pub trait PumpkinBlock: Send + Sync { block.default_state_id } - async fn can_place_at(&self, _world: &World, _block_pos: &BlockPos) -> bool { + async fn random_tick(&self, _block: &Block, _world: &World, _pos: &BlockPos) {} + + async fn can_place_at(&self, _world: &World, _pos: &BlockPos) -> bool { true } @@ -72,7 +74,7 @@ pub trait PumpkinBlock: Send + Sync { _world: &World, _block: &Block, _state_id: u16, - _block_pos: &BlockPos, + _pos: &BlockPos, _old_state_id: u16, _notify: bool, ) { @@ -103,7 +105,7 @@ pub trait PumpkinBlock: Send + Sync { &self, _world: &World, _block: &Block, - _block_pos: &BlockPos, + _pos: &BlockPos, _source_block: &Block, _notify: bool, ) { @@ -113,7 +115,7 @@ pub trait PumpkinBlock: Send + Sync { async fn prepare( &self, _world: &World, - _block_pos: &BlockPos, + _pos: &BlockPos, _block: &Block, _state_id: u16, _flags: BlockFlags, @@ -126,7 +128,7 @@ pub trait PumpkinBlock: Send + Sync { _world: &World, _block: &Block, state: u16, - _block_pos: &BlockPos, + _pos: &BlockPos, _direction: &BlockDirection, _neighbor_pos: &BlockPos, _neighbor_state: u16, @@ -134,7 +136,7 @@ pub trait PumpkinBlock: Send + Sync { state } - async fn on_scheduled_tick(&self, _world: &World, _block: &Block, _block_pos: &BlockPos) {} + async fn on_scheduled_tick(&self, _world: &World, _block: &Block, _pos: &BlockPos) {} async fn on_state_replaced( &self, @@ -161,7 +163,7 @@ pub trait PumpkinBlock: Send + Sync { &self, _block: &Block, _world: &World, - _block_pos: &BlockPos, + _pos: &BlockPos, _state: &BlockState, _direction: &BlockDirection, ) -> u8 { @@ -173,7 +175,7 @@ pub trait PumpkinBlock: Send + Sync { &self, _block: &Block, _world: &World, - _block_pos: &BlockPos, + _pos: &BlockPos, _state: &BlockState, _direction: &BlockDirection, ) -> u8 { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index cbc177567..54029780d 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1158,15 +1158,9 @@ impl World { block_state_id: u16, flags: BlockFlags, ) -> u16 { - let (chunk_coordinate, relative_coordinates) = position.chunk_and_chunk_relative_position(); - - // Since we divide by 16, remnant can never exceed `u8::MAX` - let relative = ChunkRelativeBlockCoordinates::from(relative_coordinates); - - let chunk = match self.level.try_get_chunk(chunk_coordinate) { - Some(chunk) => chunk.clone(), - None => self.receive_chunk(chunk_coordinate).await.0, - }; + let chunk = self.get_chunk(position).await; + let (_, relative) = position.chunk_and_chunk_relative_position(); + let relative = ChunkRelativeBlockCoordinates::from(relative); let mut chunk = chunk.write().await; let replaced_block_state_id = chunk .blocks @@ -1274,7 +1268,7 @@ impl World { priority: TickPriority, ) { self.level - .schedule_block_tick(block.id, &block_pos, delay, priority) + .schedule_block_tick(block.id, block_pos, delay, priority) .await; } @@ -1356,13 +1350,20 @@ impl World { } } - pub async fn get_block_state_id(&self, position: &BlockPos) -> Result { - let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); - let relative = ChunkRelativeBlockCoordinates::from(relative); + pub async fn get_chunk(&self, position: &BlockPos) -> Arc> { + let (chunk_coordinate, _) = position.chunk_and_chunk_relative_position(); let chunk = match self.level.try_get_chunk(chunk_coordinate) { Some(chunk) => chunk.clone(), None => self.receive_chunk(chunk_coordinate).await.0, }; + chunk + } + + pub async fn get_block_state_id(&self, position: &BlockPos) -> Result { + let chunk = self.get_chunk(position).await; + let (_, relative) = position.chunk_and_chunk_relative_position(); + let relative = ChunkRelativeBlockCoordinates::from(relative); + let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; let Some(id) = chunk.blocks.get_block(relative) else {