feat: Falling Blocks

This commit is contained in:
Alexander Medvedev 2025-08-03 17:21:46 +02:00
parent 73c2c0115e
commit 306a007e3c
7 changed files with 222 additions and 1 deletions

View File

@ -1043,6 +1043,16 @@ pub(crate) fn build() -> TokenStream {
}
}
#[doc = r" Get a raw ID from an State ID."]
#[inline]
pub const fn get_raw_id_from_state_id(state_id: u16) -> u16 {
if state_id as usize >= Self::RAW_ID_FROM_STATE_ID.len() {
0
} else {
Self::RAW_ID_FROM_STATE_ID[state_id as usize]
}
}
#[doc = r" Get a block from a state id."]
#[inline]
pub const fn from_state_id(id: u16) -> &'static Self {

View File

@ -1,4 +1,7 @@
use crate::block::{BlockBehaviour, OnPlaceArgs};
use crate::block::blocks::falling::FallingBlock;
use crate::block::{
BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use async_trait::async_trait;
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_data::tag::{RegistryKey, get_tag_values};
@ -23,4 +26,18 @@ impl BlockBehaviour for AnvilBlock {
props.facing = dir;
props.to_state_id(args.block)
}
async fn placed(&self, args: PlacedArgs<'_>) {
FallingBlock::placed(&FallingBlock, args).await;
}
async fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
FallingBlock::get_state_for_neighbor_update(&FallingBlock, args).await
}
async fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
}
}

View File

@ -0,0 +1,62 @@
use crate::{
block::{
BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
PlacedArgs,
},
entity::falling::FallingEntity,
};
use async_trait::async_trait;
use pumpkin_data::{
Block, BlockState,
tag::{self, Taggable},
};
use pumpkin_world::{BlockStateId, tick::TickPriority};
pub struct FallingBlock;
impl FallingBlock {
#[must_use]
pub fn can_fall_through(state: &BlockState, block: &Block) -> bool {
state.is_air()
|| block.is_tagged_with_by_tag(&tag::Block::MINECRAFT_FIRE)
|| state.is_liquid()
|| state.replaceable()
}
}
impl BlockMetadata for FallingBlock {
fn namespace(&self) -> &'static str {
"minecraft"
}
fn ids(&self) -> &'static [&'static str] {
&[Block::GRAVEL.name, Block::SAND.name, Block::RED_SAND.name]
}
}
#[async_trait]
impl BlockBehaviour for FallingBlock {
async fn placed(&self, args: PlacedArgs<'_>) {
// TODO: make delay configurable
args.world
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal)
.await;
}
async fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
// TODO: make delay configurable
args.world
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal)
.await;
args.state_id
}
async fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let (block, state) = args.world.get_block_and_state(&args.position.down()).await;
if !Self::can_fall_through(state, block) || args.position.0.y < args.world.min_y {
return;
}
FallingEntity::replace_spawn(args.world, *args.position, args.block.default_state.id).await;
}
}

View File

@ -17,6 +17,7 @@ pub mod dirt_path;
pub mod doors;
pub mod end_portal;
pub mod end_portal_frame;
pub mod falling;
pub mod farmland;
pub mod fence_gates;
pub mod fences;

View File

@ -11,6 +11,7 @@ use crate::block::blocks::dirt_path::DirtPathBlock;
use crate::block::blocks::doors::DoorBlock;
use crate::block::blocks::end_portal::EndPortalBlock;
use crate::block::blocks::end_portal_frame::EndPortalFrameBlock;
use crate::block::blocks::falling::FallingBlock;
use crate::block::blocks::farmland::FarmlandBlock;
use crate::block::blocks::fence_gates::FenceGateBlock;
use crate::block::blocks::fences::FenceBlock;
@ -205,6 +206,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(LecternBlock);
manager.register(StemBlock);
manager.register(AttachedStemBlock);
manager.register(FallingBlock);
// Fire
manager.register(SoulFireBlock);

View File

@ -0,0 +1,128 @@
use async_trait::async_trait;
use pumpkin_data::Block;
use pumpkin_data::{damage::DamageType, entity::EntityType};
use pumpkin_protocol::java::client::play::{MetaDataType, Metadata};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::{BlockStateId, world::BlockFlags};
use std::sync::{Arc, atomic::Ordering};
use uuid::Uuid;
use crate::{
entity::{Entity, EntityBase, NBTStorage, living::LivingEntity},
server::Server,
world::World,
};
pub struct FallingEntity {
entity: Entity,
block_state_id: BlockStateId,
}
impl FallingEntity {
pub fn new(entity: Entity, block_state_id: BlockStateId) -> Self {
Self {
entity,
block_state_id,
}
}
/// Replaced the current Block and Spawns a new Falling one
pub async fn replace_spawn(world: &Arc<World>, position: BlockPos, block_state: BlockStateId) {
// Replace the original block, TODO: use fluid state
world
.set_block_state(
&position,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
let position = position.0.to_f64().add_raw(0.5, 0.0, 0.5);
let entity = Entity::new(
Uuid::new_v4(),
world.clone(),
position,
&EntityType::FALLING_BLOCK,
false,
);
entity.data.store(i32::from(block_state), Ordering::Relaxed);
let entity = Arc::new(Self::new(entity, block_state));
world.spawn_entity(entity).await;
}
}
impl NBTStorage for FallingEntity {}
#[async_trait]
impl EntityBase for FallingEntity {
async fn tick(&self, caller: Arc<dyn EntityBase>, server: &Server) {
let entity = &self.entity;
entity.tick(caller.clone(), server).await;
let original_velo = entity.velocity.load();
let mut velo = original_velo;
velo.y -= self.get_gravity();
entity.velocity.store(velo);
entity.move_entity(caller.clone(), velo).await;
entity.tick_block_collisions(&caller, server).await;
if entity.on_ground.load(Ordering::Relaxed) {
entity.velocity.store(velo.multiply(0.7, -0.5, 0.7));
entity
.world
.read()
.await
.set_block_state(
&self.entity.block_pos.load(),
self.block_state_id,
BlockFlags::NOTIFY_ALL,
)
.await;
entity.remove().await;
}
entity.velocity.store(velo.multiply(0.98, 0.98, 0.98));
entity.send_pos_rot().await;
entity.send_velocity().await;
}
async fn init_data_tracker(&self) {
self.entity
.send_meta_data(&[Metadata::new(
8,
MetaDataType::BlockPos,
self.entity.block_pos.load(),
)])
.await;
}
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}
fn as_nbt_storage(&self) -> &dyn NBTStorage {
self
}
fn get_gravity(&self) -> f64 {
0.04
}
}

View File

@ -53,6 +53,7 @@ pub mod ai;
pub mod decoration;
pub mod effect;
pub mod experience_orb;
pub mod falling;
pub mod hunger;
pub mod item;
pub mod living;