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
This commit is contained in:
Alexander Medvedev 2025-03-23 11:55:06 +01:00
parent 2c63a5bd00
commit 9e8b76d6de
6 changed files with 131 additions and 29 deletions

View File

@ -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<DashMap<Vector2<i32>, SyncChunk>>,
pub loaded_chunks: Arc<DashMap<Vector2<i32>, SyncChunk>>,
chunk_watchers: Arc<DashMap<Vector2<i32>, usize>>,
chunk_saver: Arc<dyn ChunkIO<Data = SyncChunk>>,
@ -530,15 +530,17 @@ impl Level {
pub async fn get_and_tick_block_ticks(&self) -> Vec<ScheduledTick> {
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,

View File

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

View File

@ -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<World> 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
}
}

View File

@ -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<BlockRegistry> {
let mut manager = BlockRegistry::default();
manager.register(JukeboxBlock);
manager.register(SugarCaneBlock);
manager.register(CraftingTableBlock);
manager.register(FurnaceBlock);
manager.register(ChestBlock);

View File

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

View File

@ -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<u16, GetBlockError> {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let relative = ChunkRelativeBlockCoordinates::from(relative);
pub async fn get_chunk(&self, position: &BlockPos) -> Arc<RwLock<ChunkData>> {
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<u16, GetBlockError> {
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<ChunkData> = chunk.read().await;
let Some(id) = chunk.blocks.get_block(relative) else {