use pumpkin_data::fluid::Fluid; use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::{Block, BlockId, BlockState}; use pumpkin_data::BlockStateId; use pumpkin_util::math::position::BlockPos; use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro}; use crate::entity::experience_orb::ExperienceOrbEntity; use crate::entity::player::Player; use crate::world::World; use crate::world::loot::LootContextParameters; use std::sync::Arc; pub mod blocks; pub mod entities; pub mod fluid; pub mod registry; pub mod viewer; use crate::block::registry::BlockActionResult; use crate::entity::EntityBase; use crate::server::Server; use pumpkin_data::BlockDirection; use pumpkin_data::block_rotation::{Mirror, Rotation}; use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::item_stack::ItemStack; use pumpkin_inventory::screen_handler::ScreenHandlerFactory; use pumpkin_protocol::java::server::play::SUseItemOn; use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::world::{BlockAccessor, BlockFlags}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PathComputationType { Land, Water, Air, } pub trait BlockMetadata { fn ids() -> Box<[BlockId]>; } pub trait FluidMetadata { fn ids() -> Box<[u16]>; } pub(crate) fn stop_vertical_movement_after_fall(entity: &dyn EntityBase) { let entity = entity.get_entity(); let mut velocity = entity.velocity.load(); velocity.y = 0.0; entity.velocity.store(velocity); } pub(crate) fn bounce_entity_after_fall(entity: &dyn EntityBase, bounce_multiplier: f64) { let base_entity = entity.get_entity(); let mut velocity = base_entity.velocity.load(); if base_entity.is_sneaking() { velocity.y = 0.0; } else if velocity.y < 0.0 { let entity_factor = if entity.get_living_entity().is_some() { 1.0 } else { 0.8 }; velocity.y = -velocity.y * bounce_multiplier * entity_factor; } base_entity.velocity.store(velocity); } pub trait BlockBehaviour: Send + Sync { fn is_valid_bonemeal_target(&self, _args: BonemealArgs<'_>) -> bool { false } fn is_bonemeal_success(&self, _args: BonemealArgs<'_>) -> bool { true } fn perform_bonemeal(&self, _args: BonemealArgs<'_>) {} fn normal_use(&self, _args: NormalUseArgs<'_>) -> BlockActionResult { BlockActionResult::Pass } fn get_screen_handler_factory( &self, _args: GetScreenHandlerFactoryArgs<'_>, ) -> Option> { None } fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult { BlockActionResult::PassToDefaultBlockAction } fn on_entity_collision(&self, _args: OnEntityCollisionArgs<'_>) {} fn on_projectile_hit(&self, _args: OnProjectileHitArgs<'_>) {} /// Called when an entity is standing on / walking over the top face of this block. fn on_entity_step(&self, _args: OnEntityStepArgs<'_>) {} fn should_drop_items_on_explosion(&self) -> bool { true } fn explode(&self, _args: ExplodeArgs<'_>) {} /// Handles the block event, which is an event specific to a block with an integer ID and data. /// /// returns whether the event was handled successfully fn on_synced_block_event(&self, _args: OnSyncedBlockEventArgs<'_>) -> bool { false } /// getPlacementState in source code fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId { args.block.default_state.id } fn random_tick(&self, _args: RandomTickArgs<'_>) {} fn can_place_at(&self, _args: CanPlaceAtArgs<'_>) -> bool { true } fn can_update_at(&self, _args: CanUpdateAtArgs<'_>) -> bool { false } /// onBlockAdded in source code fn placed(&self, _args: PlacedArgs<'_>) {} fn player_placed(&self, _args: PlayerPlacedArgs<'_>) {} fn on_landed_upon(&self, args: OnLandedUponArgs<'_>) { if let Some(living) = args.entity.get_living_entity() { living.handle_fall_damage(args.entity, args.fall_distance, 1.0); } } fn update_entity_movement_after_fall_on(&self, args: UpdateEntityMovementAfterFallOnArgs<'_>) { stop_vertical_movement_after_fall(args.entity); } fn broken(&self, _args: BrokenArgs<'_>) {} fn on_neighbor_update(&self, _args: OnNeighborUpdateArgs<'_>) {} /// Called if a block state is replaced or it replaces another state fn prepare(&self, _args: PrepareArgs<'_>) {} fn get_state_for_neighbor_update( &self, args: GetStateForNeighborUpdateArgs<'_>, ) -> BlockStateId { args.state_id } fn on_scheduled_tick(&self, _args: OnScheduledTickArgs<'_>) {} fn on_state_replaced(&self, _args: OnStateReplacedArgs<'_>) {} // --- Redstone/Comparator Methods --- /// Sides where redstone connects to fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool { false } /// Weak redstone power, aka. block that should be powered needs to be directly next to the source block fn get_weak_redstone_power(&self, _args: GetRedstonePowerArgs<'_>) -> u8 { 0 } /// Strong redstone power. this can power a block that then gives power fn get_strong_redstone_power(&self, _args: GetRedstonePowerArgs<'_>) -> u8 { 0 } fn get_comparator_output(&self, _args: GetComparatorOutputArgs<'_>) -> Option { None } fn get_inside_collision_shape(&self, _args: GetInsideCollisionShapeArgs<'_>) -> BoundingBox { BoundingBox::full_block() } fn mirror(&self, block: &Block, state_id: BlockStateId, mirror: Mirror) -> &'static BlockState { block.mirror(state_id, mirror) } fn rotate( &self, block: &Block, state_id: BlockStateId, rotation: Rotation, ) -> &'static BlockState { block.rotate(state_id, rotation) } fn is_pathfindable(&self, state: &BlockState, computation_type: PathComputationType) -> bool { match computation_type { PathComputationType::Water => { state.is_waterlogged() || Fluid::from_state_id(state.id) .is_some_and(|f| f.has_tag(&tag::Fluid::MINECRAFT_WATER)) } PathComputationType::Land | PathComputationType::Air => !state.is_full_cube(), } } } #[derive(Clone, Copy)] pub struct BonemealArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub state_id: BlockStateId, } pub struct NormalUseArgs<'a> { pub server: &'a Server, pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub player: &'a Arc, pub hit: &'a BlockHitResult<'a>, } #[derive(Clone, Copy)] pub struct GetScreenHandlerFactoryArgs<'a> { pub server: &'a Server, pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub player: &'a Arc, } pub struct UseWithItemArgs<'a> { pub server: &'a Server, pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub player: &'a Arc, pub hit: &'a BlockHitResult<'a>, pub item_stack: &'a mut ItemStack, pub equipment_slot: &'a EquipmentSlot, } pub struct BlockHitResult<'a> { pub face: &'a BlockDirection, pub cursor_pos: &'a Vector3, } pub struct OnEntityCollisionArgs<'a> { pub server: &'a Server, pub world: &'a Arc, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, pub entity: &'a dyn EntityBase, } pub struct OnProjectileHitArgs<'a> { pub server: &'a Server, pub world: &'a Arc, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, pub projectile: &'a dyn EntityBase, pub hit_pos: &'a Vector3, } pub struct OnEntityStepArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, pub entity: &'a dyn EntityBase, pub below_supporting_block: bool, } pub struct ExplodeArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, } pub struct OnSyncedBlockEventArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub r#type: u8, pub data: u8, } pub struct OnPlaceArgs<'a> { pub server: &'a Server, pub world: &'a World, pub block: &'a Block, pub position: &'a BlockPos, pub direction: BlockDirection, pub player: &'a Player, pub replacing: BlockIsReplacing, pub use_item_on: &'a SUseItemOn, } pub struct RandomTickArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, } pub struct CanPlaceAtArgs<'a> { pub server: Option<&'a Server>, pub world: Option<&'a World>, pub block_accessor: &'a dyn BlockAccessor, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, pub direction: Option, pub player: Option<&'a Player>, pub use_item_on: Option<&'a SUseItemOn>, } pub struct CanUpdateAtArgs<'a> { pub world: &'a World, pub block: &'a Block, pub state_id: BlockStateId, pub position: &'a BlockPos, pub direction: BlockDirection, pub player: &'a Player, pub use_item_on: &'a SUseItemOn, } pub struct PlacedArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub state_id: BlockStateId, pub old_state_id: BlockStateId, pub position: &'a BlockPos, pub notify: bool, } pub struct PlayerPlacedArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub state_id: BlockStateId, pub position: &'a BlockPos, pub direction: BlockDirection, pub player: &'a Player, } pub struct OnLandedUponArgs<'a> { pub world: &'a Arc, pub fall_distance: f32, pub entity: &'a dyn EntityBase, } pub struct UpdateEntityMovementAfterFallOnArgs<'a> { pub entity: &'a dyn EntityBase, } pub struct BrokenArgs<'a> { pub block: &'a Block, pub player: &'a Arc, pub position: &'a BlockPos, pub server: &'a Server, pub world: &'a Arc, pub state: &'a BlockState, } pub struct OnNeighborUpdateArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, pub source_block: &'a Block, pub notify: bool, } pub struct PrepareArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub state_id: BlockStateId, pub position: &'a BlockPos, pub flags: BlockFlags, } pub struct GetStateForNeighborUpdateArgs<'a> { pub world: &'a World, pub block: &'a Block, pub state_id: BlockStateId, pub position: &'a BlockPos, pub direction: BlockDirection, pub neighbor_position: &'a BlockPos, pub neighbor_state_id: BlockStateId, } pub struct OnScheduledTickArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, } pub struct OnStateReplacedArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub old_state_id: BlockStateId, pub position: &'a BlockPos, pub moved: bool, } pub struct EmitsRedstonePowerArgs<'a> { pub block: &'a Block, pub state: &'a BlockState, pub direction: BlockDirection, } pub struct GetRedstonePowerArgs<'a> { pub world: &'a World, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, pub direction: BlockDirection, } pub struct GetComparatorOutputArgs<'a> { pub world: &'a World, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, } pub struct GetInsideCollisionShapeArgs<'a> { pub world: &'a World, pub block: &'a Block, pub state: &'a BlockState, pub position: &'a BlockPos, } #[derive(Clone)] pub struct BlockEvent { pub pos: BlockPos, pub r#type: u8, pub data: u8, } pub fn drop_loot( world: &Arc, block: &Block, pos: &BlockPos, experience: bool, params: &LootContextParameters, ) { let key = format!("minecraft:blocks/{}", block.name); if let Some(loot_table) = pumpkin_data::loot_table::get_loot_table(&key) { let seed: i64 = rand::random(); let items = crate::world::loot::generate_loot_with_context(loot_table, seed, params); if !items.is_empty() { let mut event = crate::plugin::block::block_drop_item::BlockDropItemEvent { block_pos: *pos, world: world.clone(), player: None, items, cancelled: false, }; if let Some(server) = world.server.upgrade() { server.plugin_manager.fire_blocking(&server, &mut event); } if !event.cancelled { for stack in event.items { world.drop_stack(pos, stack); } } } } let has_silk_touch = params.tool.as_ref().is_some_and(|tool| { pumpkin_data::Enchantment::from_name("silk_touch") .is_some_and(|e| tool.get_enchantment_level(e) > 0) }); if experience && !has_silk_touch && let Some(experience) = &block.experience { let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(get_seed())); let amount = experience.experience.get(&mut random); if amount > 0 { let mut event = crate::plugin::block::block_exp::BlockExpEvent { block_pos: *pos, world: world.clone(), exp: amount, }; if let Some(server) = world.server.upgrade() { server.plugin_manager.fire_blocking(&server, &mut event); } if event.exp > 0 { ExperienceOrbEntity::spawn(world, pos.to_f64(), event.exp as u32); } } } } pub fn calc_block_breaking(player: &Player, state: &BlockState, block: &'static Block) -> f32 { let hardness = state.hardness; #[expect(clippy::float_cmp)] if hardness == -1.0 { // unbreakable return 0.0; } let i = if player.can_harvest(state, block) { 30.0 } else { 100.0 }; player.get_mining_speed(block) / hardness / i } #[derive(PartialEq, Eq, Debug)] pub enum BlockIsReplacing { Itself(BlockStateId), Water(u8), Other, None, } impl BlockIsReplacing { #[must_use] /// Returns true if the block was a water source block. pub const fn water_source(&self) -> bool { match self { // Level 0 means the water is a source block Self::Water(level) => *level == 0, _ => false, } } } pub fn calculate_comparator_output(inventory: &dyn pumpkin_world::inventory::Inventory) -> u8 { let size = inventory.size(); if size == 0 { return 0; } let mut fill_sum = 0.0; let mut non_empty_count = 0; for i in 0..size { let stack = inventory.get_stack(i); if !stack.is_empty() { let max_stack = stack.get_max_stack_size() as f32; let count = stack.item_count as f32; fill_sum += count / max_stack; non_empty_count += 1; } } if non_empty_count == 0 { return 0; } let percentage = fill_sum / (size as f32); let output = 1.0 + percentage * 14.0; output.floor() as u8 }