Pumpkin/crates/pumpkin/src/block/mod.rs

529 lines
14 KiB
Rust

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, LootTableExt};
use std::pin::Pin;
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_protocol::java::server::play::SUseItemOn;
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::world::{BlockAccessor, BlockFlags};
pub trait BlockMetadata {
fn ids() -> Box<[BlockId]>;
}
pub trait FluidMetadata {
fn ids() -> Box<[u16]>;
}
pub type BlockFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
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 normal_use<'a>(&'a self, _args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { BlockActionResult::Pass })
}
fn use_with_item<'a>(
&'a self,
_args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { BlockActionResult::PassToDefaultBlockAction })
}
fn on_entity_collision<'a>(&'a self, _args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
/// Called when an entity is standing on / walking over the top face of this block.
fn on_entity_step<'a>(&'a self, _args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn should_drop_items_on_explosion(&self) -> bool {
true
}
fn explode<'a>(&'a self, _args: ExplodeArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
/// 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<'a>(
&'a self,
_args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { false })
}
/// getPlacementState in source code
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { args.block.default_state.id })
}
fn random_tick<'a>(&'a self, _args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn can_place_at(&self, _args: CanPlaceAtArgs<'_>) -> bool {
true
}
fn can_update_at(&self, _args: CanUpdateAtArgs<'_>) -> bool {
false
}
/// onBlockAdded in source code
fn placed<'a>(&'a self, _args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn player_placed<'a>(&'a self, _args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn on_landed_upon<'a>(&'a self, args: OnLandedUponArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(living) = args.entity.get_living_entity() {
living
.handle_fall_damage(args.entity, args.fall_distance, 1.0)
.await;
}
})
}
fn update_entity_movement_after_fall_on<'a>(
&'a self,
args: UpdateEntityMovementAfterFallOnArgs<'a>,
) -> BlockFuture<'a, ()> {
Box::pin(async move {
stop_vertical_movement_after_fall(args.entity);
})
}
fn broken<'a>(&'a self, _args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn on_neighbor_update<'a>(&'a self, _args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
/// Called if a block state is replaced or it replaces another state
fn prepare<'a>(&'a self, _args: PrepareArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { args.state_id })
}
fn on_scheduled_tick<'a>(&'a self, _args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
fn on_state_replaced<'a>(&'a self, _args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {})
}
// --- Redstone/Comparator Methods ---
/// Sides where redstone connects to
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { false })
}
/// Weak redstone power, aka. block that should be powered needs to be directly next to the source block
fn get_weak_redstone_power<'a>(
&'a self,
_args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move { 0 })
}
/// Strong redstone power. this can power a block that then gives power
fn get_strong_redstone_power<'a>(
&'a self,
_args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move { 0 })
}
fn get_comparator_output<'a>(
&'a self,
_args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { None })
}
fn get_inside_collision_shape<'a>(
&'a self,
_args: GetInsideCollisionShapeArgs<'a>,
) -> BlockFuture<'a, BoundingBox> {
Box::pin(async move { 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)
}
}
pub struct NormalUseArgs<'a> {
pub server: &'a Server,
pub world: &'a Arc<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
pub player: &'a Arc<Player>,
pub hit: &'a BlockHitResult<'a>,
}
pub struct UseWithItemArgs<'a> {
pub server: &'a Server,
pub world: &'a Arc<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
pub player: &'a Arc<Player>,
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<f32>,
}
pub struct OnEntityCollisionArgs<'a> {
pub server: &'a Server,
pub world: &'a Arc<World>,
pub block: &'a Block,
pub state: &'a BlockState,
pub position: &'a BlockPos,
pub entity: &'a dyn EntityBase,
}
pub struct OnEntityStepArgs<'a> {
pub world: &'a Arc<World>,
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<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
}
pub struct OnSyncedBlockEventArgs<'a> {
pub world: &'a Arc<World>,
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<World>,
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<BlockDirection>,
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<World>,
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<World>,
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<World>,
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<Player>,
pub position: &'a BlockPos,
pub server: &'a Server,
pub world: &'a Arc<World>,
pub state: &'a BlockState,
}
pub struct OnNeighborUpdateArgs<'a> {
pub world: &'a Arc<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
pub source_block: &'a Block,
pub notify: bool,
}
pub struct PrepareArgs<'a> {
pub world: &'a Arc<World>,
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<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
}
pub struct OnStateReplacedArgs<'a> {
pub world: &'a Arc<World>,
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 async fn drop_loot(
world: &Arc<World>,
block: &Block,
pos: &BlockPos,
experience: bool,
params: LootContextParameters,
) {
if let Some(loot_table) = &block.loot_table {
for stack in loot_table.get_loot(params) {
world.drop_stack(pos, stack).await;
}
}
if experience && let Some(experience) = &block.experience {
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(get_seed()));
let amount = experience.experience.get(&mut random);
// TODO: Silk touch gives no exp
if amount > 0 {
ExperienceOrbEntity::spawn(world, pos.to_f64(), amount as u32).await;
}
}
}
pub async 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).await {
30.0
} else {
100.0
};
player.get_mining_speed(block).await / 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 async 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).await;
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
}