refactor: from async to sync Part 1

This commit is contained in:
Alexander Medvedev
2026-08-26 14:18:00 +02:00
parent 26f91f0b0f
commit 8ee11b06ce
485 changed files with 24441 additions and 28048 deletions

View File

@@ -556,7 +556,7 @@ mod tests {
},
};
use pumpkin_world::inventory::SimpleInventory;
use tokio::sync::Mutex;
use std::sync::Mutex;
use crate::{
entity_equipment::EntityEquipment,
@@ -911,7 +911,7 @@ mod tests {
player_inventory
.main_inventory
.write()
.await
.unwrap_or_else(std::sync::PoisonError::into_inner)
.fill_with(|| ItemStack::new(64, &Item::COBBLESTONE));
merchant_inventory
.set_stack(0, ItemStack::new(9, &Item::EMERALD))

View File

@@ -19,9 +19,9 @@ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
use std::any::Any;
use std::collections::HashMap;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use tokio::sync::{Mutex, RwLock};
use std::sync::{Arc, Mutex, RwLock};
use tracing::warn;
/// The player's inventory.
@@ -99,27 +99,36 @@ impl PlayerInventory {
/// Gets the item in the currently selected hotbar slot.
///
/// This is the item the player is currently holding in their main hand.
pub async fn held_item(&self) -> ItemStack {
let inv = self.main_inventory.read().await;
pub fn held_item(&self) -> ItemStack {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[self.get_selected_slot() as usize].clone()
}
/// Sets the item in the currently selected hotbar slot.
pub async fn set_held_item(&self, stack: ItemStack) {
pub fn set_held_item(&self, stack: ItemStack) {
let selected = self.get_selected_slot() as usize;
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[selected] = stack;
}
/// Sets the item in the specified hand.
pub async fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) {
pub fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) {
match hand {
Hand::Right => self.set_held_item(stack).await,
Hand::Right => self.set_held_item(stack),
Hand::Left => {
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
return;
};
self.entity_equipment.lock().await.put(slot, stack);
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, stack);
}
}
}
@@ -128,34 +137,43 @@ impl PlayerInventory {
///
/// # Arguments
/// - `hand` - Which hand to get the item from
pub async fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
pub fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
match hand {
Hand::Left => self.off_hand_item().await,
Hand::Right => self.held_item().await,
Hand::Left => self.off_hand_item(),
Hand::Right => self.held_item(),
}
}
/// Gets the item in the off-hand.
///
/// Mojang name: `getOffHandStack`
pub async fn off_hand_item(&self) -> ItemStack {
pub fn off_hand_item(&self) -> ItemStack {
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
return ItemStack::EMPTY.clone();
};
self.entity_equipment.lock().await.get(slot)
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot)
}
/// Swaps the items between main hand and off-hand.
///
/// # Returns
/// The new main hand item and new off-hand item.
pub async fn swap_item(&self) -> (ItemStack, ItemStack) {
pub fn swap_item(&self) -> (ItemStack, ItemStack) {
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
return (ItemStack::EMPTY.clone(), ItemStack::EMPTY.clone());
};
let mut equipment = self.entity_equipment.lock().await;
let mut equipment = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let selected = self.get_selected_slot() as usize;
let mut main_inv = self.main_inventory.write().await;
let mut main_inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let main_hand_item = main_inv[selected].clone();
let new_main = equipment.put(slot, main_hand_item.clone());
main_inv[selected] = new_main.clone();
@@ -169,27 +187,30 @@ impl PlayerInventory {
}
/// Adds a stack to any available slot, prioritizing stacking with existing items.
async fn add_stack(&self, stack: ItemStack) -> usize {
let mut slot_index = self.get_occupied_slot_with_room_for_stack(&stack).await;
fn add_stack(&self, stack: ItemStack) -> usize {
let mut slot_index = self.get_occupied_slot_with_room_for_stack(&stack);
if slot_index == -1 {
slot_index = self.get_empty_slot().await;
slot_index = self.get_empty_slot();
}
if slot_index == -1 {
stack.item_count as usize
} else {
self.add_stack_to_slot(slot_index as usize, stack).await
self.add_stack_to_slot(slot_index as usize, stack)
}
}
/// Adds a stack to a specific slot.
///
/// Returns the number of items that couldn't fit.
async fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize {
fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize {
if slot >= Self::MAIN_SIZE {
if let Some(slot_type) = self.equipment_slots.get(&slot) {
let mut equipment = self.entity_equipment.lock().await;
let mut equipment = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let current = equipment.get(slot_type);
if current.is_empty() {
equipment.put(slot_type, stack);
@@ -199,7 +220,10 @@ impl PlayerInventory {
return stack.item_count as usize;
}
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut stack_count = stack.item_count;
let self_stack = &mut inv[slot];
@@ -221,8 +245,11 @@ impl PlayerInventory {
///
/// # Returns
/// The slot index or -1 if inventory is full.
async fn get_empty_slot(&self) -> i16 {
let inv = self.main_inventory.read().await;
fn get_empty_slot(&self) -> i16 {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for (i, stack) in inv.iter().enumerate() {
if stack.is_empty() {
return i as i16;
@@ -242,14 +269,17 @@ impl PlayerInventory {
/// Finds a slot with the same item type that has room for more items.
///
/// Checks selected slot, off-hand, then other slots.
async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
let selected = self.get_selected_slot() as usize;
let inv = self.main_inventory.read().await;
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if Self::can_stack_add_more(&inv[selected], stack) {
return selected as i16;
}
let off_hand = self.off_hand_item().await;
let off_hand = self.off_hand_item();
if Self::can_stack_add_more(&off_hand, stack) {
return Self::OFF_HAND_SLOT as i16;
}
@@ -270,8 +300,8 @@ impl PlayerInventory {
///
/// # Returns
/// `true` if any items were inserted, `false` otherwise.
pub async fn insert_stack_anywhere(&self, stack: &mut ItemStack) -> bool {
self.insert_stack(-1, stack).await
pub fn insert_stack_anywhere(&self, stack: &mut ItemStack) -> bool {
self.insert_stack(-1, stack)
}
/// Inserts a stack into a specific slot or any slot.
@@ -282,7 +312,7 @@ impl PlayerInventory {
///
/// # Returns
/// `true` if any items were inserted, `false` otherwise.
pub async fn insert_stack(&self, slot: i16, stack: &mut ItemStack) -> bool {
pub fn insert_stack(&self, slot: i16, stack: &mut ItemStack) -> bool {
if stack.is_empty() {
return false;
}
@@ -292,9 +322,9 @@ impl PlayerInventory {
loop {
i = stack.item_count;
if slot == -1 {
stack.set_count(self.add_stack(stack.clone()).await as u8);
stack.set_count(self.add_stack(stack.clone()) as u8);
} else {
stack.set_count(self.add_stack_to_slot(slot as usize, stack.clone()).await as u8);
stack.set_count(self.add_stack_to_slot(slot as usize, stack.clone()) as u8);
}
if stack.is_empty() || stack.item_count >= i {
@@ -309,8 +339,11 @@ impl PlayerInventory {
///
/// # Returns
/// The slot index or -1 if not found.
pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
let inv = self.main_inventory.read().await;
pub fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for (i, item) in inv.iter().enumerate() {
if !item.is_empty() && item.are_items_and_components_equal(stack) {
return i as i16;
@@ -322,8 +355,11 @@ impl PlayerInventory {
/// Finds an empty hotbar slot to swap an item to.
///
/// First looks for empty slots, then slots without enchantments.
async fn get_swappable_hotbar_slot(&self) -> usize {
let inv = self.main_inventory.read().await;
fn get_swappable_hotbar_slot(&self) -> usize {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let selected_slot = self.get_selected_slot() as usize;
for i in 0..Self::HOTBAR_SIZE {
let check_index = (i + selected_slot) % 9;
@@ -338,11 +374,14 @@ impl PlayerInventory {
/// Swaps an item stack with an item on the hotbar.
///
/// Finds an empty hotbar slot and places the stack there.
pub async fn swap_stack_with_hotbar(&self, stack: ItemStack) {
let swappable = self.get_swappable_hotbar_slot().await;
pub fn swap_stack_with_hotbar(&self, stack: ItemStack) {
let swappable = self.get_swappable_hotbar_slot();
self.set_selected_slot(swappable as u8);
let selected = self.get_selected_slot() as usize;
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(empty_slot) = inv.iter().position(ItemStack::is_empty)
&& !inv[selected].is_empty()
@@ -354,11 +393,14 @@ impl PlayerInventory {
}
/// Swaps the items at two slot indices.
pub async fn swap_slot_with_hotbar(&self, slot: usize) {
let swappable = self.get_swappable_hotbar_slot().await;
pub fn swap_slot_with_hotbar(&self, slot: usize) {
let swappable = self.get_swappable_hotbar_slot();
self.set_selected_slot(swappable as u8);
let selected = self.get_selected_slot() as usize;
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv.swap(selected, slot);
}
@@ -376,9 +418,9 @@ impl PlayerInventory {
pub async fn offer(&self, stack: ItemStack, notify_client: bool, player: &dyn InventoryPlayer) {
let mut stack = stack;
while !stack.is_empty() {
let mut room_for_stack = self.get_occupied_slot_with_room_for_stack(&stack).await;
let mut room_for_stack = self.get_occupied_slot_with_room_for_stack(&stack);
if room_for_stack == -1 {
room_for_stack = self.get_empty_slot().await;
room_for_stack = self.get_empty_slot();
}
if room_for_stack == -1 {
@@ -388,11 +430,7 @@ impl PlayerInventory {
let items_fit = stack.get_max_stack_size()
- self.get_stack(room_for_stack as usize).await.item_count;
if self
.insert_stack(room_for_stack, &mut stack.split(items_fit))
.await
&& notify_client
{
if self.insert_stack(room_for_stack, &mut stack.split(items_fit)) && notify_client {
player
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
i32::from(room_for_stack).into(),
@@ -407,9 +445,15 @@ impl PlayerInventory {
impl Clearable for PlayerInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv.fill_with(|| ItemStack::EMPTY.clone());
self.entity_equipment.lock().await.clear();
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
})
}
}
@@ -421,13 +465,20 @@ impl Inventory for PlayerInventory {
fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move {
let inv = self.main_inventory.read().await;
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.iter().any(|s| !s.is_empty()) {
return false;
}
for slot in self.equipment_slots.values() {
let eq_item = self.entity_equipment.lock().await.get(slot);
let eq_item = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot);
if !eq_item.is_empty() {
return false;
}
@@ -440,10 +491,16 @@ impl Inventory for PlayerInventory {
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let inv = self.main_inventory.read().await;
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot].clone()
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment.lock().await.get(slot)
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot)
} else {
ItemStack::EMPTY.clone()
}
@@ -453,12 +510,15 @@ impl Inventory for PlayerInventory {
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.await
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, ItemStack::EMPTY.clone())
} else {
ItemStack::EMPTY.clone()
@@ -469,14 +529,20 @@ impl Inventory for PlayerInventory {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !inv[slot].is_empty() && amount > 0 {
inv[slot].split(amount)
} else {
ItemStack::EMPTY.clone()
}
} else if let Some(slot) = self.equipment_slots.get(&slot) {
let mut equipment = self.entity_equipment.lock().await;
let mut equipment = self
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut stack = equipment.get(slot);
if !stack.is_empty() && amount > 0 {
@@ -495,10 +561,16 @@ impl Inventory for PlayerInventory {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move {
if slot < Self::MAIN_SIZE {
let mut inv = self.main_inventory.write().await;
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot] = stack;
} else if let Some(slot) = self.equipment_slots.get(&slot) {
self.entity_equipment.lock().await.put(slot, stack);
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot, stack);
} else {
warn!("Failed to get Equipment Slot at {slot}");
}

View File

@@ -6,10 +6,7 @@ use pumpkin_data::{
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockAccessor;
use crate::{
block::{BlockFuture, GetStateForNeighborUpdateArgs},
entity::player::Player,
};
use crate::{block::GetStateForNeighborUpdateArgs, entity::player::Player};
pub trait WallMountedBlock: Send + Sync {
fn get_direction(&self, state_id: BlockStateId, block: &Block) -> BlockDirection;
@@ -49,10 +46,10 @@ pub trait WallMountedBlock: Send + Sync {
}
}
fn can_place_at<'a>(
&'a self,
world: &'a dyn BlockAccessor,
pos: &'a BlockPos,
fn can_place_at(
&self,
world: &dyn BlockAccessor,
pos: &BlockPos,
direction: BlockDirection,
) -> bool {
let block_pos = pos.offset(direction.to_offset());
@@ -60,18 +57,16 @@ pub trait WallMountedBlock: Send + Sync {
block_state.is_side_solid(direction.opposite())
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if self.get_direction(args.state_id, args.block).opposite() == args.direction
&& !self.can_place_at(args.world, args.position, args.direction)
{
Block::AIR.default_state.id
} else {
args.state_id
}
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if self.get_direction(args.state_id, args.block).opposite() == args.direction
&& !self.can_place_at(args.world, args.position, args.direction)
{
Block::AIR.default_state.id
} else {
args.state_id
}
}
}

View File

@@ -7,8 +7,8 @@ use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, RandomTickArgs, blocks::abstract_wall_mounting::WallMountedBlock,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
RandomTickArgs, blocks::abstract_wall_mounting::WallMountedBlock,
};
const ALL_DIRECTIONS: [BlockDirection; 6] = [
@@ -35,16 +35,12 @@ impl BlockMetadata for AmethystBlock {
}
impl BlockBehaviour for AmethystBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = AmethystClusterLikeProperties::from_state_id(
args.block.default_state.id,
args.block,
);
props.facing = args.direction.to_facing();
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
AmethystClusterLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.facing = args.direction.to_facing();
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -56,11 +52,11 @@ impl BlockBehaviour for AmethystBlock {
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, direction)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { WallMountedBlock::get_state_for_neighbor_update(self, args).await })
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
WallMountedBlock::get_state_for_neighbor_update(self, args)
}
}
@@ -75,56 +71,53 @@ impl WallMountedBlock for AmethystBlock {
pub struct BuddingAmethystBlock;
impl BlockBehaviour for BuddingAmethystBlock {
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::rng().random_range(0..5) == 0 {
let grow_direction = {
let mut rng = rand::rng();
ALL_DIRECTIONS[rng.random_range(0..ALL_DIRECTIONS.len())]
fn random_tick(&self, args: RandomTickArgs<'_>) {
if rand::rng().random_range(0..5) == 0 {
let grow_direction = {
let mut rng = rand::rng();
ALL_DIRECTIONS[rng.random_range(0..ALL_DIRECTIONS.len())]
};
let grow_pos = args.position.offset(grow_direction.to_offset());
let (relative_block, relative_state) = args.world.get_block_and_state(&grow_pos);
let relative_state_id = relative_state.id;
let next_stage_and_water =
if can_cluster_grow_at_state(relative_block, relative_state_id) {
Some((&Block::SMALL_AMETHYST_BUD, relative_block == &Block::WATER))
} else if relative_block == &Block::SMALL_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::SMALL_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::MEDIUM_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::MEDIUM_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::MEDIUM_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::LARGE_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::LARGE_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::LARGE_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::AMETHYST_CLUSTER, props.waterlogged))
} else {
None
};
let grow_pos = args.position.offset(grow_direction.to_offset());
let (relative_block, relative_state) = args.world.get_block_and_state(&grow_pos);
let relative_state_id = relative_state.id;
let next_stage_and_water =
if can_cluster_grow_at_state(relative_block, relative_state_id) {
Some((&Block::SMALL_AMETHYST_BUD, relative_block == &Block::WATER))
} else if relative_block == &Block::SMALL_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::SMALL_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::MEDIUM_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::MEDIUM_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::MEDIUM_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::LARGE_AMETHYST_BUD, props.waterlogged))
} else if relative_block == &Block::LARGE_AMETHYST_BUD {
let props = AmethystClusterLikeProperties::from_state_id(
relative_state_id,
&Block::LARGE_AMETHYST_BUD,
);
(props.facing == grow_direction.to_facing())
.then_some((&Block::AMETHYST_CLUSTER, props.waterlogged))
} else {
None
};
if let Some((next_stage, waterlogged)) = next_stage_and_water {
let mut target_props = AmethystClusterLikeProperties::default(next_stage);
target_props.facing = grow_direction.to_facing();
target_props.waterlogged = waterlogged;
let target_state_id = target_props.to_state_id(next_stage);
args.world
.set_block_state(&grow_pos, target_state_id, BlockFlags::NOTIFY_ALL)
.await;
}
if let Some((next_stage, waterlogged)) = next_stage_and_water {
let mut target_props = AmethystClusterLikeProperties::default(next_stage);
target_props.facing = grow_direction.to_facing();
target_props.waterlogged = waterlogged;
let target_state_id = target_props.to_state_id(next_stage);
args.world
.set_block_state(&grow_pos, target_state_id, BlockFlags::NOTIFY_ALL);
}
})
}
}
}

View File

@@ -1,8 +1,8 @@
use crate::block::blocks::falling::FallingBlock;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
PlacedArgs,
};
use pumpkin_data::BlockStateId;
@@ -23,58 +23,50 @@ use tokio::sync::Mutex;
pub struct AnvilBlock;
impl BlockBehaviour for AnvilBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
1,
)
.await;
args.player
.open_handled_screen(&AnvilScreenFactory, Some(*args.position))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&AnvilScreenFactory, Some(pos))
.await;
});
BlockActionResult::Success
})
BlockActionResult::Success
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
FallingBlock::placed(&FallingBlock, args).await;
})
fn placed(&self, args: PlacedArgs<'_>) {
FallingBlock::placed(&FallingBlock, args);
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let dir = args
.player
.living_entity
.entity
.get_horizontal_facing()
.rotate_clockwise();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let dir = args
.player
.living_entity
.entity
.get_horizontal_facing()
.rotate_clockwise();
let mut props = WallTorchLikeProperties::default(args.block);
let mut props = WallTorchLikeProperties::default(args.block);
props.facing = dir;
props.to_state_id(args.block)
})
props.facing = dir;
props.to_state_id(args.block)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
FallingBlock::on_scheduled_tick(&FallingBlock, args);
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(
async move { FallingBlock::get_state_for_neighbor_update(&FallingBlock, args).await },
)
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
FallingBlock::get_state_for_neighbor_update(&FallingBlock, args)
}
}

View File

@@ -1,6 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
PlacedArgs,
BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::EntityBase;
use pumpkin_data::BlockStateId;
@@ -17,46 +16,39 @@ use std::sync::Arc;
pub struct BannerBlock;
impl BlockBehaviour for BannerBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let entity = BannerBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
}
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = WhiteBannerLikeProperties::default(args.block);
props.rotation = args.player.get_entity().get_flipped_rotation_16();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = WhiteBannerLikeProperties::default(args.block);
props.rotation = args.player.get_entity().get_flipped_rotation_16();
props.to_state_id(args.block)
}
fn can_place_at(&self, args: crate::block::CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
}

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::{BlockFuture, GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@@ -50,54 +50,47 @@ impl ScreenHandlerFactory for BarrelScreenFactory {
pub struct BarrelBlock;
impl BlockBehaviour for BarrelBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = BarrelLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = BarrelLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
1,
)
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BarrelScreenFactory(inventory), Some(pos))
.await;
args.player
.open_handled_screen(&BarrelScreenFactory(inventory), Some(*args.position))
.await;
}
});
}
BlockActionResult::Success
})
BlockActionResult::Success
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(barrel_block_entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(barrel_block_entity));
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(
inventory.as_ref(),
))
} else {
None
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs};
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs};
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{
BlockProperties, MangroveRootsLikeProperties as BarrierLikeProperties,
@@ -11,29 +11,25 @@ use pumpkin_world::tick::TickPriority;
pub struct BarrierBlock;
impl BlockBehaviour for BarrierBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = BarrierLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = BarrierLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = BarrierLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
props.to_state_id(args.block)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let props = BarrierLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
props.to_state_id(args.block)
}
}

View File

@@ -12,7 +12,7 @@ use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
use crate::block::{BlockBehaviour, NormalUseArgs};
// Create the factory just like ChestScreenFactory
struct BeaconScreenFactory(Arc<dyn Inventory>);
@@ -48,29 +48,29 @@ impl ScreenHandlerFactory for BeaconScreenFactory {
pub struct BeaconBlock;
impl BlockBehaviour for BeaconBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let block_entity = args.world.get_block_entity(args.position);
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let block_entity = args.world.get_block_entity(args.position);
// Extract the inventory from the entity
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
return BlockActionResult::Fail;
};
// Extract the inventory from the entity
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
return BlockActionResult::Fail;
};
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBeacon as i32,
1,
)
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBeacon as i32,
1,
);
// Open the screen using the factory
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(pos))
.await;
});
// Open the screen using the factory
args.player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(*args.position))
.await;
BlockActionResult::Success
})
BlockActionResult::Success
}
}

View File

@@ -13,7 +13,6 @@ use pumpkin_util::GameMode;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags;
use crate::block::BlockFuture;
use crate::block::OnLandedUponArgs;
use crate::block::UpdateEntityMovementAfterFallOnArgs;
use crate::block::bounce_entity_after_fall;
@@ -22,7 +21,7 @@ use crate::block::{
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, OnStateReplacedArgs,
PlacedArgs, PlayerPlacedArgs,
};
use crate::entity::{Entity, EntityBase};
use crate::entity::{Entity, EntityBase, player::Player};
use crate::world::World;
type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties;
@@ -84,36 +83,27 @@ impl BlockBehaviour for BedBlock {
false
}
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 * 0.5, 1.0)
.await;
}
})
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 * 0.5, 1.0);
}
}
fn update_entity_movement_after_fall_on<'a>(
&'a self,
args: UpdateEntityMovementAfterFallOnArgs<'a>,
) -> BlockFuture<'a, ()> {
Box::pin(async move { bounce_entity_after_fall(args.entity, 0.66) })
fn update_entity_movement_after_fall_on(&self, args: UpdateEntityMovementAfterFallOnArgs<'_>) {
bounce_entity_after_fall(args.entity, 0.66);
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut bed_props = BedProperties::default(args.block);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut bed_props = BedProperties::default(args.block);
bed_props.facing = args.player.get_entity().get_horizontal_facing();
bed_props.part = BedPart::Foot;
bed_props.facing = args.player.get_entity().get_horizontal_facing();
bed_props.part = BedPart::Foot;
bed_props.to_state_id(args.block)
})
bed_props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let bed_entity = BedBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(bed_entity));
@@ -122,283 +112,275 @@ impl BlockBehaviour for BedBlock {
bed_head_props.part = BedPart::Head;
let bed_head_pos = args.position.offset(bed_head_props.facing.to_offset());
args.world
.set_block_state(
&bed_head_pos,
bed_head_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
)
.await;
args.world.set_block_state(
&bed_head_pos,
bed_head_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
);
let bed_head_entity = BedBlockEntity::new(bed_head_pos);
args.world.add_block_entity(Arc::new(bed_head_entity));
})
}
}
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
{
args.world.play_bedrock_level_sound(
"place",
&args.position.to_centered_f64(),
i32::from(pumpkin_data::BlockState::to_be_network_id(args.state_id)),
);
})
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let bed_props = BedProperties::from_state_id(args.state.id, args.block);
let other_half_pos = if bed_props.part == BedPart::Head {
args.position
.offset(bed_props.facing.opposite().to_offset())
} else {
args.position.offset(bed_props.facing.to_offset())
};
fn broken(&self, args: BrokenArgs<'_>) {
let bed_props = BedProperties::from_state_id(args.state.id, args.block);
let other_half_pos = if bed_props.part == BedPart::Head {
args.position
.offset(bed_props.facing.opposite().to_offset())
} else {
args.position.offset(bed_props.facing.to_offset())
};
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
if neighbor_state_id.to_block_id() != args.block.id {
args.world.update_neighbors(&other_half_pos, None);
return;
}
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
if neighbor_state_id.to_block_id() != args.block.id {
args.world.update_neighbors(&other_half_pos, None).await;
return;
}
let is_creative = args.player.gamemode.load() == GameMode::Creative;
let flags = if bed_props.part == BedPart::Foot && !is_creative {
// Breaking foot in survival -> allow head to drop
BlockFlags::NOTIFY_NEIGHBORS
} else {
// Breaking head OR creative mode -> skip drops
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
};
let is_creative = args.player.gamemode.load() == GameMode::Creative;
let flags = if bed_props.part == BedPart::Foot && !is_creative {
// Breaking foot in survival -> allow head to drop
BlockFlags::NOTIFY_NEIGHBORS
} else {
// Breaking head OR creative mode -> skip drops
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
};
args.world
.break_block(&other_half_pos, Some(args.player.clone()), flags)
.await;
})
args.world
.break_block(&other_half_pos, Some(args.player.clone()), flags);
}
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.moved {
return;
}
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
if args.moved {
return;
}
// If the block is being replaced with air (i.e., broken), the `broken` callback
// will handle breaking the other half with the correct drop flags. Only handle it here
// if the block is being replaced with something else (e.g., piston movement).
let new_state_id = args.world.get_block_state_id(args.position);
let new_block = Block::from_state_id(new_state_id);
if new_block == &Block::AIR {
return;
}
// If the block is being replaced with air (i.e., broken), the `broken` callback
// will handle breaking the other half with the correct drop flags. Only handle it here
// if the block is being replaced with something else (e.g., piston movement).
let new_state_id = args.world.get_block_state_id(args.position);
let new_block = Block::from_state_id(new_state_id);
if new_block == &Block::AIR {
return;
}
let bed_props = BedProperties::from_state_id(args.old_state_id, args.block);
let other_half_pos = if bed_props.part == BedPart::Head {
args.position
.offset(bed_props.facing.opposite().to_offset())
} else {
args.position.offset(bed_props.facing.to_offset())
};
let bed_props = BedProperties::from_state_id(args.old_state_id, args.block);
let other_half_pos = if bed_props.part == BedPart::Head {
args.position
.offset(bed_props.facing.opposite().to_offset())
} else {
args.position.offset(bed_props.facing.to_offset())
};
let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos);
if other_block == args.block {
let other_props = BedProperties::from_state_id(other_state.id, other_block);
if other_props.part != bed_props.part {
args.world
.break_block(
&other_half_pos,
None,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
}
let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos);
if other_block == args.block {
let other_props = BedProperties::from_state_id(other_state.id, other_block);
if other_props.part != bed_props.part {
args.world.break_block(
&other_half_pos,
None,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS,
);
}
})
}
}
#[expect(clippy::too_many_lines)]
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let bed_props = BedProperties::from_state_id(state_id, args.block);
let (bed_head_pos, bed_foot_pos) = if bed_props.part == BedPart::Head {
(
*args.position,
args.position
.offset(bed_props.facing.opposite().to_offset()),
)
} else {
(
args.position.offset(bed_props.facing.to_offset()),
*args.position,
)
};
// Explode if not in the overworld
if args.world.dimension != Dimension::OVERWORLD {
args.world
.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS)
.await;
args.world
.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS)
.await;
args.world
.explode(
bed_head_pos.to_centered_f64(),
5.0,
crate::world::ExplosionInteraction::Block,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure the bed is not obstructed
if args.world.get_block_state(&bed_head_pos.up()).is_solid()
|| args.world.get_block_state(&bed_foot_pos.up()).is_solid()
{
args.player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_OBSTRUCTED,
translation::bedrock::TILE_BED_OBSTRUCTED
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure the bed is not occupied
if bed_props.occupied {
// TODO: Wake up villager
args.player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_OCCUPIED,
translation::bedrock::TILE_BED_OCCUPIED
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure player is close enough
if !args
.player
.position()
.is_within_bounds(bed_head_pos.to_f64(), 3.0, 3.0, 3.0)
&& !args
.player
.position()
.is_within_bounds(bed_foot_pos.to_f64(), 3.0, 3.0, 3.0)
{
args.player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_TOO_FAR_AWAY,
translation::bedrock::TILE_BED_TOOFAR
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Set respawn point
if args
.player
.set_respawn_point(
args.world.dimension.clone(),
bed_head_pos,
args.player.get_entity().yaw.load(),
args.player.get_entity().pitch.load(),
false,
)
.await
{
args.player
.send_system_message(&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
translation::bedrock::TILE_BED_RESPAWNSET
))
.await;
}
// Make sure the time and weather allows sleep
if !can_sleep(args.world).await {
args.player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
translation::bedrock::TILE_BED_NOSLEEP
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure there are no monsters nearby
for entity in args.world.entities.load().iter() {
if !entity_prevents_sleep(entity.get_entity()) {
continue;
}
let pos = entity.get_entity().pos.load();
if pos.is_within_bounds(bed_head_pos.to_f64(), 8.0, 5.0, 8.0)
|| pos.is_within_bounds(bed_foot_pos.to_f64(), 8.0, 5.0, 8.0)
{
args.player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NOT_SAFE,
translation::bedrock::TILE_BED_NOTSAFE
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
}
if let Some(server) = args.world.server.upgrade() {
let mut event =
crate::plugin::api::events::player::player_bed::PlayerBedEnterEvent::new(
args.player.clone(),
bed_head_pos,
);
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return BlockActionResult::SuccessServer;
}
}
args.player.sleep(bed_head_pos);
args.player
.trigger_advancement(
crate::entity::player::advancement::trigger::AdvancementTrigger::SleptInBed,
)
.await;
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::SleepInBed as i32,
1,
)
.await;
Self::set_occupied(true, args.world, args.block, args.position, state_id).await;
BlockActionResult::SuccessServer
})
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let player = Arc::clone(args.player);
let world = Arc::clone(args.world);
let block_id = args.block.id;
let position = *args.position;
tokio::spawn(async move {
let block = Block::from_id(block_id);
Self::use_bed(&world, &player, block, &position).await;
});
BlockActionResult::SuccessServer
}
}
impl BedBlock {
pub async fn set_occupied(
#[expect(clippy::too_many_lines)]
async fn use_bed(
world: &Arc<World>,
player: &Arc<Player>,
block: &Block,
position: &BlockPos,
) -> BlockActionResult {
let state_id = world.get_block_state_id(position);
let bed_props = BedProperties::from_state_id(state_id, block);
let (bed_head_pos, bed_foot_pos) = if bed_props.part == BedPart::Head {
(
*position,
position.offset(bed_props.facing.opposite().to_offset()),
)
} else {
(position.offset(bed_props.facing.to_offset()), *position)
};
// Explode if not in the overworld
if world.dimension != Dimension::OVERWORLD {
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
world
.explode(
bed_head_pos.to_centered_f64(),
5.0,
crate::world::ExplosionInteraction::Block,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure the bed is not obstructed
if world.get_block_state(&bed_head_pos.up()).is_solid()
|| world.get_block_state(&bed_foot_pos.up()).is_solid()
{
player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_OBSTRUCTED,
translation::bedrock::TILE_BED_OBSTRUCTED
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure the bed is not occupied
if bed_props.occupied {
// TODO: Wake up villager
player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_OCCUPIED,
translation::bedrock::TILE_BED_OCCUPIED
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure player is close enough
if !player
.position()
.is_within_bounds(bed_head_pos.to_f64(), 3.0, 3.0, 3.0)
&& !player
.position()
.is_within_bounds(bed_foot_pos.to_f64(), 3.0, 3.0, 3.0)
{
player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_TOO_FAR_AWAY,
translation::bedrock::TILE_BED_TOOFAR
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Set respawn point
if player
.set_respawn_point(
world.dimension.clone(),
bed_head_pos,
player.get_entity().yaw.load(),
player.get_entity().pitch.load(),
false,
)
.await
{
player
.send_system_message(&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
translation::bedrock::TILE_BED_RESPAWNSET
))
.await;
}
// Make sure the time and weather allows sleep
if !can_sleep(world) {
player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
translation::bedrock::TILE_BED_NOSLEEP
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
// Make sure there are no monsters nearby
for entity in world.entities.load().iter() {
if !entity_prevents_sleep(entity.get_entity()) {
continue;
}
let pos = entity.get_entity().pos.load();
if pos.is_within_bounds(bed_head_pos.to_f64(), 8.0, 5.0, 8.0)
|| pos.is_within_bounds(bed_foot_pos.to_f64(), 8.0, 5.0, 8.0)
{
player
.send_system_message_raw(
&pumpkin_macros::translate_cross!(
translation::java::BLOCK_MINECRAFT_BED_NOT_SAFE,
translation::bedrock::TILE_BED_NOTSAFE
),
true,
)
.await;
return BlockActionResult::SuccessServer;
}
}
if let Some(server) = world.server.upgrade() {
let mut event =
crate::plugin::api::events::player::player_bed::PlayerBedEnterEvent::new(
player.clone(),
bed_head_pos,
);
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return BlockActionResult::SuccessServer;
}
}
player.sleep(bed_head_pos);
player.trigger_advancement(
crate::entity::player::advancement::trigger::AdvancementTrigger::SleptInBed,
);
player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::SleepInBed as i32,
1,
);
Self::set_occupied(true, world, block, position, state_id);
BlockActionResult::SuccessServer
}
}
impl BedBlock {
pub fn set_occupied(
occupied: bool,
world: &Arc<World>,
block: &Block,
@@ -407,13 +389,11 @@ impl BedBlock {
) {
let mut bed_props = BedProperties::from_state_id(state_id, block);
bed_props.occupied = occupied;
world
.set_block_state(
block_pos,
bed_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
block_pos,
bed_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
let other_half_pos = if bed_props.part == BedPart::Head {
block_pos.offset(bed_props.facing.opposite().to_offset())
@@ -425,19 +405,23 @@ impl BedBlock {
} else {
BedPart::Head
};
world
.set_block_state(
&other_half_pos,
bed_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
&other_half_pos,
bed_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
}
}
async fn can_sleep(world: &Arc<World>) -> bool {
let time = world.level_time.lock().await;
let weather = world.weather.lock().await;
fn can_sleep(world: &Arc<World>) -> bool {
let time = world
.level_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let weather = world
.weather
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if weather.thundering {
true

View File

@@ -1,4 +1,4 @@
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs};
use crate::block::{BlockBehaviour, BlockMetadata, GetComparatorOutputArgs};
use pumpkin_data::BlockId;
use pumpkin_data::block_properties::{BeeNestLikeProperties, BlockProperties};
@@ -11,14 +11,11 @@ impl BlockMetadata for BeehiveBlock {
}
impl BlockBehaviour for BeehiveBlock {
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
{
let state_id = args.world.get_block_state_id(args.position);
let props = BeeNestLikeProperties::from_state_id(state_id, args.block);
Some(props.honey_level)
})
}
}
}

View File

@@ -21,8 +21,8 @@ use tokio::sync::Mutex;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@@ -83,83 +83,74 @@ impl ScreenHandlerFactory for BlastingFurnaceScreenFactory {
pub struct BlastFurnaceBlock;
impl BlockBehaviour for BlastFurnaceBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
1,
)
.await;
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
inventory,
property_delegate,
experience_container,
);
args.player
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
1,
);
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
inventory,
property_delegate,
experience_container,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&blasting_furnace_screen_factory, Some(pos))
.await;
});
}
crate::block::registry::BlockActionResult::Consume
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
fn placed(&self, args: PlacedArgs<'_>) {
let blasting_furnace_block_entity = BlastingFurnaceBlockEntity::new(*args.position);
args.world
.add_block_entity(Arc::new(blasting_furnace_block_entity));
}
fn broken(&self, args: BrokenArgs<'_>) {
// Extract and drop accumulated XP as orbs before removing the block entity
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(experience_container) = block_entity.to_experience_container()
{
let xp = experience_container.extract_experience();
if xp > 0 {
let pos = args.position.to_f64();
ExperienceOrbEntity::spawn(args.world, pos, xp as u32);
}
crate::block::registry::BlockActionResult::Consume
})
}
args.world.remove_block_entity(args.position);
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let blasting_furnace_block_entity = BlastingFurnaceBlockEntity::new(*args.position);
args.world
.add_block_entity(Arc::new(blasting_furnace_block_entity));
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Extract and drop accumulated XP as orbs before removing the block entity
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(experience_container) = block_entity.to_experience_container()
{
let xp = experience_container.extract_experience();
if xp > 0 {
let pos = args.position.to_f64();
ExperienceOrbEntity::spawn(args.world, pos, xp as u32).await;
}
}
args.world.remove_block_entity(args.position);
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(
inventory.as_ref(),
))
} else {
None
}
}
}

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::block::{BlockFuture, GetComparatorOutputArgs, PlacedArgs};
use crate::block::{GetComparatorOutputArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@@ -49,55 +49,48 @@ impl ScreenHandlerFactory for BrewingScreenFactory {
pub struct BrewingStandBlock;
impl BlockBehaviour for BrewingStandBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(pd) = block_entity.clone().to_property_delegate()
{
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
1,
)
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(pd) = block_entity.clone().to_property_delegate()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(pos))
.await;
args.player
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(*args.position))
.await;
}
});
}
BlockActionResult::Success
})
BlockActionResult::Success
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let be = BrewingStandBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(be));
})
fn placed(&self, args: PlacedArgs<'_>) {
let be = BrewingStandBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(be));
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let mut bottles = 0u8;
// Bottle slots are 0, 1, 2 in brewing stands
for slot in 0..3 {
let stack = inventory.get_stack(slot).await;
if !stack.is_empty() {
bottles += 1;
}
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
let mut bottles = 0u8;
// Bottle slots are 0, 1, 2 in brewing stands
for slot in 0..3 {
let stack = futures::executor::block_on(inventory.get_stack(slot));
if !stack.is_empty() {
bottles += 1;
}
Some(bottles)
} else {
None
}
})
Some(bottles)
} else {
None
}
}
}

View File

@@ -6,9 +6,7 @@ use pumpkin_data::{Block, BlockId, BlockStateId};
use pumpkin_world::world::BlockFlags;
use crate::block::entities::brushable_block::BrushableBlockBlockEntity;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs,
};
use crate::block::{BlockBehaviour, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs};
pub struct BrushableBlock;
@@ -38,7 +36,7 @@ impl BrushableBlock {
if *hits >= 4 {
let item = brush_be.item.lock().await.take();
if let Some(item_stack) = item {
world.drop_stack(pos, item_stack).await;
world.drop_stack(pos, item_stack);
}
let target_block = if is_gravel {
@@ -47,9 +45,7 @@ impl BrushableBlock {
&Block::SAND
};
world
.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL);
let sound = if is_gravel {
Sound::ItemBrushBrushingGravelComplete
@@ -60,9 +56,7 @@ impl BrushableBlock {
world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64());
} else {
props.dusted = (*hits as u8).min(3);
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
let sound = if is_gravel {
Sound::ItemBrushBrushingGravel
@@ -77,28 +71,22 @@ impl BrushableBlock {
}
impl BlockBehaviour for BrushableBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = SuspiciousSandLikeProperties::default(args.block);
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let props = SuspiciousSandLikeProperties::default(args.block);
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = BrushableBlockBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let entity = BrushableBlockBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(be) = args.world.get_block_entity(args.position)
&& let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>()
&& let Some(contained) = brush_be.item.lock().await.take()
{
args.world.drop_stack(args.position, contained).await;
}
})
fn broken(&self, args: BrokenArgs<'_>) {
if let Some(be) = args.world.get_block_entity(args.position)
&& let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>()
&& let Some(contained) = brush_be.item.blocking_lock().take()
{
args.world.drop_stack(args.position, contained);
}
}
}

View File

@@ -10,7 +10,7 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs,
BlockBehaviour, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs,
OnScheduledTickArgs, PlacedArgs,
};
use crate::world::World;
@@ -161,8 +161,8 @@ fn bubble_column_velocity(
}
impl BlockBehaviour for BubbleColumnBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
{
if args.block != &Block::BUBBLE_COLUMN {
return;
}
@@ -179,19 +179,19 @@ impl BlockBehaviour for BubbleColumnBlock {
if let Some(player) = args.entity.get_player() {
player.breath_manager.reset(player);
}
})
}
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
if args.block == &Block::WATER && is_source_water(args.world, *args.position) {
schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
}
})
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
let state = args.world.get_block_state_id(args.position);
if args.block == &Block::BUBBLE_COLUMN {
schedule_reconcile(args.world, *args.position, REMOVE_DELAY_TICKS);
@@ -202,42 +202,37 @@ impl BlockBehaviour for BubbleColumnBlock {
{
schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
}
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state_id(args.position);
let block = Block::from_state_id(state);
if block != &Block::BUBBLE_COLUMN && block != &Block::WATER {
return;
}
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let state = args.world.get_block_state_id(args.position);
let block = Block::from_state_id(state);
if block != &Block::BUBBLE_COLUMN && block != &Block::WATER {
return;
}
let below_pos = args.position.down();
let below_state = args.world.get_block_state_id(&below_pos);
let below_block = Block::from_state_id(below_state);
let below_pos = args.position.down();
let below_state = args.world.get_block_state_id(&below_pos);
let below_block = Block::from_state_id(below_state);
match reconcile_action(block, state, below_block, below_state) {
ReconcileAction::SetBubble(kind) => {
let new_state = bubble_column_state(kind);
args.world
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL)
.await;
schedule_reconcile(args.world, args.position.up(), CREATE_DELAY_TICKS);
}
ReconcileAction::RestoreWater => {
args.world
.set_block_state(
args.position,
source_water_state(),
BlockFlags::NOTIFY_ALL,
)
.await;
schedule_reconcile(args.world, args.position.up(), REMOVE_DELAY_TICKS);
}
ReconcileAction::Stop => {}
match reconcile_action(block, state, below_block, below_state) {
ReconcileAction::SetBubble(kind) => {
let new_state = bubble_column_state(kind);
args.world
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL);
schedule_reconcile(args.world, args.position.up(), CREATE_DELAY_TICKS);
}
})
ReconcileAction::RestoreWater => {
args.world.set_block_state(
args.position,
source_water_state(),
BlockFlags::NOTIFY_ALL,
);
schedule_reconcile(args.world, args.position.up(), REMOVE_DELAY_TICKS);
}
ReconcileAction::Stop => {}
}
}
}

View File

@@ -2,9 +2,9 @@ use std::sync::Arc;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetComparatorOutputArgs,
GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
UseWithItemArgs, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
BlockBehaviour, CanPlaceAtArgs, GetComparatorOutputArgs, GetStateForNeighborUpdateArgs,
NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, UseWithItemArgs,
blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
},
entity::player::Player,
world::World,
@@ -21,13 +21,11 @@ use pumpkin_world::{
tick::TickPriority,
world::{BlockAccessor, BlockFlags},
};
use rand::{RngExt, rng};
#[pumpkin_block("minecraft:cake")]
pub struct CakeBlock;
impl CakeBlock {
pub async fn consume_if_hungry(
pub fn consume_if_hungry(
world: &Arc<World>,
player: &Player,
block: &Block,
@@ -45,7 +43,7 @@ impl CakeBlock {
.hunger_manager
.saturation
.store(player.hunger_manager.saturation.load() + 0.4);
player.send_health().await;
player.send_health();
}
GameMode::Creative | GameMode::Spectator => {}
}
@@ -53,38 +51,30 @@ impl CakeBlock {
let mut properties = CakeLikeProperties::from_state_id(state_id, block);
match properties.bites {
0..=5 => {
player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
1,
)
.await;
player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
1,
);
properties.bites += 1;
world
.set_block_state(
location,
properties.to_state_id(block),
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
location,
properties.to_state_id(block),
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Consume
}
6 => {
player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
1,
)
.await;
world
.set_block_state(
location,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
1,
);
world.set_block_state(
location,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Consume
}
_ => BlockActionResult::Pass,
@@ -93,113 +83,83 @@ impl CakeBlock {
}
impl BlockBehaviour for CakeBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
return Block::AIR.default_state.id;
}
Block::CAKE.default_state.id
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
if !can_place_at(args.world, args.position) {
return Block::AIR.default_state.id;
}
Block::CAKE.default_state.id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position)
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
let item = args.item_stack.item;
match item.id {
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
if properties.bites != 0 {
return Self::consume_if_hungry(
args.world,
args.player,
args.block,
args.position,
state_id,
)
.await;
}
if args.player.gamemode.load() != GameMode::Creative {
args.item_stack.decrement(1);
}
args.world
.set_block_state(
args.position,
cake_from_candle(item).default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
let seed: f64 = rng().random();
args.player
.play_sound(
Sound::BlockCakeAddCandle as u16,
SoundCategory::Blocks,
&args.position.to_f64(),
1.0,
1.0,
seed,
)
.await;
BlockActionResult::Consume
}
_ => {
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
let item = args.item_stack.item;
match item.id {
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
if properties.bites != 0 {
return Self::consume_if_hungry(
args.world,
args.player,
args.block,
args.position,
state_id,
)
.await;
);
}
if args.player.gamemode.load() != GameMode::Creative {
args.item_stack.decrement(1);
}
args.world.set_block_state(
args.position,
cake_from_candle(item).default_state.id,
BlockFlags::NOTIFY_ALL,
);
args.world.play_sound(
Sound::BlockCakeAddCandle,
SoundCategory::Blocks,
&args.position.to_f64(),
);
BlockActionResult::Consume
}
})
_ => Self::consume_if_hungry(
args.world,
args.player,
args.block,
args.position,
state_id,
),
}
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
.await
})
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let state_id = args.world.get_block_state_id(args.position);
Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
{
let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
if properties.bites <= 6 {
@@ -207,7 +167,7 @@ impl BlockBehaviour for CakeBlock {
} else {
Some(0)
}
})
}
}
}

View File

@@ -12,8 +12,8 @@ use pumpkin_world::tick::TickPriority;
use crate::block::entities::campfire::CampfireBlockEntity;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnPlaceArgs, PlacedArgs,
BlockBehaviour, BlockIsReplacing, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnPlaceArgs, PlacedArgs,
},
entity::EntityBase,
};
@@ -23,21 +23,24 @@ use std::sync::Arc;
pub struct CampfireBlock;
impl BlockBehaviour for CampfireBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let entity = CampfireBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
}
}
// TODO: cooking food on campfire (CampfireBlockEntity)
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
{
if CampfireLikeProperties::from_state_id(args.state.id, args.block).lit
&& let Some(living_entity) = args.entity.get_living_entity()
{
let has_frost_walker_enchantment = {
let equipment = living_entity.entity_equipment.lock().await;
let equipment = living_entity
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment
.equipment
.get(&EquipmentSlot::FEET)
@@ -47,7 +50,6 @@ impl BlockBehaviour for CampfireBlock {
};
let has_fire_res = living_entity
.get_effect(&StatusEffect::FIRE_RESISTANCE)
.await
.is_some();
if has_frost_walker_enchantment || has_fire_res {
//campfire burning doesn't work if entity's boots has frost walker enchantment or entity has fire resistance. source: https://minecraft.wiki/w/Campfire#Damage
@@ -59,49 +61,43 @@ impl BlockBehaviour for CampfireBlock {
1.0
};
args.entity
.damage(args.entity, damage_amount, DamageType::CAMPFIRE)
.await;
.damage(args.entity, damage_amount, DamageType::CAMPFIRE);
}
})
}
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_));
let mut props =
CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.waterlogged = is_replacing_water;
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_));
let mut props =
CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.waterlogged = is_replacing_water;
props.signal_fire = is_signal_fire_base_block(args.world.get_block(&args.position.down()));
props.lit = !is_replacing_water;
props.facing = args.player.get_entity().get_horizontal_facing();
props.to_state_id(args.block)
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let mut props = CampfireLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
props.lit = false;
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
if args.direction == BlockDirection::Down {
props.signal_fire =
is_signal_fire_base_block(args.world.get_block(&args.position.down()));
props.lit = !is_replacing_water;
props.facing = args.player.get_entity().get_horizontal_facing();
props.to_state_id(args.block)
})
}
is_signal_fire_base_block(args.world.get_block(args.neighbor_position));
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CampfireLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
props.lit = false;
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
if args.direction == BlockDirection::Down {
props.signal_fire =
is_signal_fire_base_block(args.world.get_block(args.neighbor_position));
}
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
// TODO: onProjectileHit

View File

@@ -10,8 +10,8 @@ use pumpkin_world::{
use crate::{
block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs,
OnScheduledTickArgs, UseWithItemArgs, blocks::cake::CakeBlock, registry::BlockActionResult,
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnScheduledTickArgs,
UseWithItemArgs, blocks::cake::CakeBlock, registry::BlockActionResult,
},
entity::player::Player,
world::World,
@@ -55,7 +55,7 @@ pub fn candle_from_cake(block: &Block) -> &'static Item {
pub struct CandleCakeBlock;
impl CandleCakeBlock {
async fn consume_and_drop_candle(
fn consume_and_drop_candle(
block: &Block,
player: &Player,
location: &BlockPos,
@@ -75,65 +75,51 @@ impl CandleCakeBlock {
let item_stack = ItemStack::new(1, candle_item);
world.drop_stack(location, item_stack).await;
world.drop_stack(location, item_stack);
world
.set_block_state(
location,
Block::CAKE.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
location,
Block::CAKE.default_state.id,
BlockFlags::NOTIFY_ALL,
);
let (block, state) = world.get_block_and_state_id(location);
CakeBlock::consume_if_hungry(world, player, block, location, state).await
CakeBlock::consume_if_hungry(world, player, block, location, state)
}
}
impl BlockBehaviour for CandleCakeBlock {
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let item_id = args.item_stack.item.id;
match item_id {
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
BlockActionResult::Pass
} // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL
_ => BlockActionResult::PassToDefaultBlockAction,
}
})
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let item_id = args.item_stack.item.id;
match item_id {
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
BlockActionResult::Pass
} // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL
_ => BlockActionResult::PassToDefaultBlockAction,
}
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
Self::consume_and_drop_candle(args.block, args.player, args.position, args.world).await
})
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Self::consume_and_drop_candle(args.block, args.player, args.position, args.world)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
}

View File

@@ -10,7 +10,7 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockAccessor;
use pumpkin_world::world::BlockFlags;
use crate::block::{BlockFuture, GetStateForNeighborUpdateArgs, OnScheduledTickArgs};
use crate::block::{GetStateForNeighborUpdateArgs, OnScheduledTickArgs};
use crate::{
block::{
BlockIsReplacing,
@@ -27,29 +27,24 @@ use crate::{
pub struct CandleBlock;
impl BlockBehaviour for CandleBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.player.get_entity().pose.load() != EntityPose::Crouching
&& let BlockIsReplacing::Itself(state_id) = args.replacing
{
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
if properties.candles < 4 {
properties.candles += 1;
}
return properties.to_state_id(args.block);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
if args.player.get_entity().pose.load() != EntityPose::Crouching
&& let BlockIsReplacing::Itself(state_id) = args.replacing
{
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
if properties.candles < 4 {
properties.candles += 1;
}
return properties.to_state_id(args.block);
}
let mut properties = CandleLikeProperties::default(args.block);
properties.waterlogged = args.replacing.water_source();
properties.to_state_id(args.block)
})
let mut properties = CandleLikeProperties::default(args.block);
properties.waterlogged = args.replacing.water_source();
properties.to_state_id(args.block)
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
{
let state = args.world.get_block_state(args.position);
let mut properties = CandleLikeProperties::from_state_id(state.id, args.block);
@@ -67,13 +62,11 @@ impl BlockBehaviour for CandleBlock {
properties.lit = was_lit;
args.world
.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Consume
}
@@ -84,22 +77,20 @@ impl BlockBehaviour for CandleBlock {
return BlockActionResult::Pass;
}
args.world
.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Consume
}
}
})
}
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
let state_id = args.world.get_block_state_id(args.position);
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
@@ -107,16 +98,14 @@ impl BlockBehaviour for CandleBlock {
properties.lit = false;
}
args.world
.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
properties.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Consume
})
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -130,27 +119,22 @@ impl BlockBehaviour for CandleBlock {
&& args.block.id == b.id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
};
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::is_air;
@@ -16,27 +16,22 @@ impl BlockBehaviour for CarpetBlock {
can_place_at(args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}
@@ -48,27 +43,22 @@ impl BlockBehaviour for MossCarpetBlock {
can_place_at(args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}
@@ -80,27 +70,22 @@ impl BlockBehaviour for PaleMossCarpetBlock {
can_place_at(args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
use crate::block::{BlockBehaviour, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::cartography_table_screen_handler::CartographyTableScreenHandler;
@@ -16,21 +16,21 @@ use tokio::sync::Mutex;
pub struct CartographyTableBlock;
impl BlockBehaviour for CartographyTableBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
1,
)
.await;
args.player
.open_handled_screen(&CartographyTableScreenFactory, Some(*args.position))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&CartographyTableScreenFactory, Some(pos))
.await;
});
BlockActionResult::Success
})
BlockActionResult::Success
}
}

View File

@@ -7,7 +7,7 @@ use pumpkin_data::{
use pumpkin_world::world::BlockFlags;
use crate::{
block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs, PlacedArgs},
block::{BlockBehaviour, BlockMetadata, OnPlaceArgs, PlacedArgs},
entity::{
Entity,
passive::{iron_golem::IronGolemEntity, snow_golem::SnowGolemEntity},
@@ -23,35 +23,31 @@ impl BlockMetadata for CarvedPumpkinBlock {
}
impl BlockBehaviour for CarvedPumpkinBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = WallTorchLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = WallTorchLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
fn placed(&self, args: PlacedArgs<'_>) {
// Mojang uses some BlockPattern magic, way too complex tbh
Box::pin(async {
{
let down_pos = args.position.down();
let upper = args.world.get_block(&down_pos);
let lower = args.world.get_block(&down_pos.down());
if upper == &Block::SNOW_BLOCK && lower == &Block::SNOW_BLOCK {
for i in 0..3 {
let pos = args.position.down_height(i);
args.world
.set_block_state(
&pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world.set_block_state(
&pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_LISTENERS,
);
args.world.sync_world_event(
WorldEvent::ParticlesDestroyBlock,
pos,
@@ -64,7 +60,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
&EntityType::SNOW_GOLEM,
);
let golem = SnowGolemEntity::new(entity);
args.world.spawn_entity(golem).await;
args.world.spawn_entity(golem);
return;
}
@@ -80,13 +76,11 @@ impl BlockBehaviour for CarvedPumpkinBlock {
let pattern = [*args.position, down_pos, down_pos.down(), arm1, arm2];
for p in pattern {
args.world
.set_block_state(
&p,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world.set_block_state(
&p,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_LISTENERS,
);
args.world.sync_world_event(
WorldEvent::ParticlesDestroyBlock,
p,
@@ -100,11 +94,11 @@ impl BlockBehaviour for CarvedPumpkinBlock {
&EntityType::IRON_GOLEM,
);
let golem = IronGolemEntity::new(entity);
args.world.spawn_entity(golem).await;
args.world.spawn_entity(golem);
return;
}
}
}
})
}
}
}

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs, UseWithItemArgs,
};
use crate::block::{BlockBehaviour, BlockMetadata, GetComparatorOutputArgs, UseWithItemArgs};
use pumpkin_data::Block;
use pumpkin_data::BlockId;
use pumpkin_data::block_properties::{BlockProperties, WaterCauldronLikeProperties};
@@ -24,7 +24,7 @@ impl BlockMetadata for CauldronBlock {
}
}
async fn fire_cauldron_change(
fn fire_cauldron_change(
world: &std::sync::Arc<crate::world::World>,
pos: pumpkin_util::math::position::BlockPos,
old_level: i32,
@@ -42,210 +42,208 @@ async fn fire_cauldron_change(
cancelled: false,
};
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
server.plugin_manager.fire_blocking(&server, &mut event);
}
!event.cancelled
}
impl BlockBehaviour for CauldronBlock {
#[allow(clippy::too_many_lines)]
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let item_id = args.item_stack.item.id;
let block_id = args.block.id;
let gamemode = args.player.gamemode.load();
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let item_id = args.item_stack.item.id;
let block_id = args.block.id;
let gamemode = args.player.gamemode.load();
// Filling empty cauldron with buckets
if block_id == BlockId::CAULDRON {
if item_id == Item::WATER_BUCKET.id {
if !fire_cauldron_change(
args.world,
*args.position,
0,
3,
crate::plugin::block::cauldron_level_change::CauldronChangeReason::BucketEmpty,
Some(args.player.clone()),
)
.await
{
return BlockActionResult::Pass;
}
let state_id = Block::WATER_CAULDRON
.from_properties(&[("level", "3")])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
.await;
args.world.play_sound(
Sound::ItemBucketEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
// Filling empty cauldron with buckets
if block_id == BlockId::CAULDRON {
if item_id == Item::WATER_BUCKET.id {
if !fire_cauldron_change(
args.world,
*args.position,
0,
3,
crate::plugin::block::cauldron_level_change::CauldronChangeReason::BucketEmpty,
Some(Arc::clone(args.player) as Arc<dyn crate::entity::EntityBase>),
) {
return BlockActionResult::Pass;
}
let state_id = Block::WATER_CAULDRON
.from_properties(&[("level", "3")])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
args.world.play_sound(
Sound::ItemBucketEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
return BlockActionResult::Success;
} else if item_id == Item::LAVA_BUCKET.id {
args.world
.set_block_state(
args.position,
Block::LAVA_CAULDRON.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.play_sound(
Sound::ItemBucketEmptyLava,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
});
return BlockActionResult::Success;
} else if item_id == Item::LAVA_BUCKET.id {
args.world.set_block_state(
args.position,
Block::LAVA_CAULDRON.default_state.id,
BlockFlags::NOTIFY_ALL,
);
args.world.play_sound(
Sound::ItemBucketEmptyLava,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
return BlockActionResult::Success;
} else if item_id == Item::POWDER_SNOW_BUCKET.id {
let state_id = Block::POWDER_SNOW_CAULDRON
.from_properties(&[("level", "3")])
.to_state_id(&Block::POWDER_SNOW_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
.await;
args.world.play_sound(
Sound::ItemBucketEmptyPowderSnow,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
});
return BlockActionResult::Success;
} else if item_id == Item::POWDER_SNOW_BUCKET.id {
let state_id = Block::POWDER_SNOW_CAULDRON
.from_properties(&[("level", "3")])
.to_state_id(&Block::POWDER_SNOW_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
args.world.play_sound(
Sound::ItemBucketEmptyPowderSnow,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
return BlockActionResult::Success;
} else if item_id == Item::POTION.id {
let state_id = Block::WATER_CAULDRON
.from_properties(&[("level", "1")])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
.await;
args.world.play_sound(
Sound::ItemBottleEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
});
return BlockActionResult::Success;
} else if item_id == Item::POTION.id {
let state_id = Block::WATER_CAULDRON
.from_properties(&[("level", "1")])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
args.world.play_sound(
Sound::ItemBottleEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(
ItemStack::new(1, &Item::GLASS_BOTTLE),
args.player.as_ref(),
player.as_ref(),
)
.await;
return BlockActionResult::Success;
}
});
return BlockActionResult::Success;
}
}
// Collecting fluid from full cauldrons into empty bucket
if item_id == Item::BUCKET.id {
let state_id = args.world.get_block_state_id(args.position);
let (filled_item, sound) = if block_id == BlockId::WATER_CAULDRON {
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
if props.level == 3 {
(Some(&Item::WATER_BUCKET), Sound::ItemBucketFill)
} else {
(None, Sound::ItemBucketFill)
}
} else if block_id == BlockId::LAVA_CAULDRON {
(Some(&Item::LAVA_BUCKET), Sound::ItemBucketFillLava)
} else if block_id == BlockId::POWDER_SNOW_CAULDRON {
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
if props.level == 3 {
(
Some(&Item::POWDER_SNOW_BUCKET),
Sound::ItemBucketFillPowderSnow,
)
} else {
(None, Sound::ItemBucketFillPowderSnow)
}
// Collecting fluid from full cauldrons into empty bucket
if item_id == Item::BUCKET.id {
let state_id = args.world.get_block_state_id(args.position);
let (filled_item, sound) = if block_id == BlockId::WATER_CAULDRON {
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
if props.level == 3 {
(Some(&Item::WATER_BUCKET), Sound::ItemBucketFill)
} else {
(None, Sound::ItemBucketFill)
};
if let Some(result_item) = filled_item {
args.world
.set_block_state(
args.position,
Block::CAULDRON.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
args.world
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
.inventory
.offer_or_drop_stack(ItemStack::new(1, result_item), args.player.as_ref())
.await;
return BlockActionResult::Success;
}
}
// Adding water bottle to non-full water cauldron
if block_id == BlockId::WATER_CAULDRON && item_id == Item::POTION.id {
let state_id = args.world.get_block_state_id(args.position);
} else if block_id == BlockId::LAVA_CAULDRON {
(Some(&Item::LAVA_BUCKET), Sound::ItemBucketFillLava)
} else if block_id == BlockId::POWDER_SNOW_CAULDRON {
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
if props.level < 3 {
let next_level_str = match props.level {
1 => "2",
_ => "3",
};
let new_state_id = Block::WATER_CAULDRON
.from_properties(&[("level", next_level_str)])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
if props.level == 3 {
(
Some(&Item::POWDER_SNOW_BUCKET),
Sound::ItemBucketFillPowderSnow,
)
} else {
(None, Sound::ItemBucketFillPowderSnow)
}
} else {
(None, Sound::ItemBucketFill)
};
if let Some(result_item) = filled_item {
args.world.set_block_state(
args.position,
Block::CAULDRON.default_state.id,
BlockFlags::NOTIFY_ALL,
);
args.world
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, result_item), player.as_ref())
.await;
args.world.play_sound(
Sound::ItemBottleEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
args.player
});
return BlockActionResult::Success;
}
}
// Adding water bottle to non-full water cauldron
if block_id == BlockId::WATER_CAULDRON && item_id == Item::POTION.id {
let state_id = args.world.get_block_state_id(args.position);
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
if props.level < 3 {
let next_level_str = match props.level {
1 => "2",
_ => "3",
};
let new_state_id = Block::WATER_CAULDRON
.from_properties(&[("level", next_level_str)])
.to_state_id(&Block::WATER_CAULDRON);
args.world
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL);
args.world.play_sound(
Sound::ItemBottleEmpty,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(
ItemStack::new(1, &Item::GLASS_BOTTLE),
args.player.as_ref(),
player.as_ref(),
)
.await;
return BlockActionResult::Success;
}
});
return BlockActionResult::Success;
}
}
BlockActionResult::PassToDefaultBlockAction
})
BlockActionResult::PassToDefaultBlockAction
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
match args.block.id {
BlockId::WATER_CAULDRON | BlockId::POWDER_SNOW_CAULDRON => {
let state_id = args.world.get_block_state_id(args.position);
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
Some(props.level)
}
BlockId::LAVA_CAULDRON => Some(3),
_ => Some(0),
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
match args.block.id {
BlockId::WATER_CAULDRON | BlockId::POWDER_SNOW_CAULDRON => {
let state_id = args.world.get_block_state_id(args.position);
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
Some(props.level)
}
})
BlockId::LAVA_CAULDRON => Some(3),
_ => Some(0),
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::{BlockBehaviour, OnPlaceArgs};
use pumpkin_data::BlockDirection;
use pumpkin_data::BlockStateId;
@@ -10,18 +9,16 @@ use pumpkin_macros::pumpkin_block;
pub struct ChainBlock;
impl BlockBehaviour for ChainBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
pumpkin_data::block_properties::IronChainLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
props.r#axis = match args.direction {
BlockDirection::East | BlockDirection::West => Axis::X,
BlockDirection::Up | BlockDirection::Down => Axis::Y,
BlockDirection::North | BlockDirection::South => Axis::Z,
};
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
pumpkin_data::block_properties::IronChainLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
props.r#axis = match args.direction {
BlockDirection::East | BlockDirection::West => Axis::X,
BlockDirection::Up | BlockDirection::Down => Axis::Y,
BlockDirection::North | BlockDirection::South => Axis::Z,
};
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
}

View File

@@ -24,7 +24,7 @@ use pumpkin_world::world::BlockFlags;
use tokio::sync::Mutex;
use crate::block::{
BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, PlayerPlacedArgs,
RandomTickArgs,
};
@@ -93,8 +93,8 @@ fn on_place_chest_impl(args: &OnPlaceArgs<'_>) -> BlockStateId {
chest_props.to_state_id(args.block)
}
async fn placed_chest_impl<E: BlockEntity + 'static>(
args: PlacedArgs<'_>,
fn placed_chest_impl<E: BlockEntity + 'static>(
args: &PlacedArgs<'_>,
create_entity: impl FnOnce(BlockPos) -> E,
) {
let chest = create_entity(*args.position);
@@ -117,13 +117,11 @@ async fn placed_chest_impl<E: BlockEntity + 'static>(
) {
neighbor_props.r#type = chest_props.r#type.opposite();
args.world
.set_block_state(
&args.position.offset(connected_towards.to_offset()),
neighbor_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world.set_block_state(
&args.position.offset(connected_towards.to_offset()),
neighbor_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
);
}
}
@@ -140,7 +138,7 @@ fn player_placed_chest_impl(args: &PlayerPlacedArgs<'_>) {
);
}
async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Option<u8> {
fn get_chest_comparator_output(args: &GetComparatorOutputArgs<'_>) -> Option<u8> {
let state = args.world.get_block_state_id(args.position);
let first_chest = args.world.get_block_entity(args.position);
let first_inventory = first_chest.and_then(BlockEntity::get_inventory)?;
@@ -163,20 +161,22 @@ async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Optio
} else {
DoubleInventory::new(second_inventory, first_inventory)
};
Some(crate::block::calculate_comparator_output(double_inventory.as_ref()).await)
Some(crate::block::calculate_comparator_output(
double_inventory.as_ref(),
))
} else {
Some(crate::block::calculate_comparator_output(first_inventory.as_ref()).await)
Some(crate::block::calculate_comparator_output(
first_inventory.as_ref(),
))
}
}
async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenChest as i32,
1,
)
.await;
fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenChest as i32,
1,
);
let state = args.world.get_block_state_id(args.position);
let first_chest = args.world.get_block_entity(args.position);
@@ -196,9 +196,10 @@ async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
&& let Some(table) = get_chest_loot_table(&loot_key)
&& let Some(inv) = entity.clone().get_inventory()
{
fill_chest_inventory(&inv, table, seed).await;
// Mark the block entity dirty so the generated items persist.
inv.mark_dirty();
tokio::spawn(async move {
fill_chest_inventory(&inv, table, seed).await;
inv.mark_dirty();
});
}
let Some(first_inventory) = first_chest.and_then(BlockEntity::get_inventory) else {
@@ -239,14 +240,18 @@ async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
first_inventory
};
args.player
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position))
.await;
let player = args.player.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&ChestScreenFactory(inventory), Some(pos))
.await;
});
BlockActionResult::Success
}
async fn broken_chest_impl(args: BrokenArgs<'_>) {
fn broken_chest_impl(args: &BrokenArgs<'_>) {
let chest_props = ChestLikeProperties::from_state_id(args.state.id, args.block);
let connected_towards = match chest_props.r#type {
ChestType::Single => return,
@@ -264,13 +269,11 @@ async fn broken_chest_impl(args: BrokenArgs<'_>) {
) {
neighbor_props.r#type = ChestType::Single;
args.world
.set_block_state(
&args.position.offset(connected_towards.to_offset()),
neighbor_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world.set_block_state(
&args.position.offset(connected_towards.to_offset()),
neighbor_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
);
}
}
@@ -278,38 +281,32 @@ async fn broken_chest_impl(args: BrokenArgs<'_>) {
pub struct ChestBlock;
impl BlockBehaviour for ChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { on_place_chest_impl(&args) })
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
on_place_chest_impl(&args)
}
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
args.r#type == LID_ANIMATION_EVENT_TYPE
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(placed_chest_impl(args, ChestBlockEntity::new))
fn placed(&self, args: PlacedArgs<'_>) {
placed_chest_impl(&args, ChestBlockEntity::new);
}
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { player_placed_chest_impl(&args) })
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
player_placed_chest_impl(&args);
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(normal_use_chest_impl(args))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
normal_use_chest_impl(&args)
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(broken_chest_impl(args))
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
get_chest_comparator_output(&args)
}
}
@@ -352,65 +349,56 @@ impl
impl crate::block::blocks::weathering_copper::WeatheringCopper for CopperChestBlock {}
impl BlockBehaviour for CopperChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { on_place_chest_impl(&args) })
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
on_place_chest_impl(&args)
}
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
args.r#type == LID_ANIMATION_EVENT_TYPE
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(placed_chest_impl(args, ChestBlockEntity::new))
fn placed(&self, args: PlacedArgs<'_>) {
placed_chest_impl(&args, ChestBlockEntity::new);
}
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { player_placed_chest_impl(&args) })
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
player_placed_chest_impl(&args);
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(normal_use_chest_impl(args))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
normal_use_chest_impl(&args)
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(broken_chest_impl(args))
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let current_state_id = args.world.get_block_state_id(args.position);
let chest_props = ChestLikeProperties::from_state_id(current_state_id, args.block);
fn random_tick(&self, args: RandomTickArgs<'_>) {
let current_state_id = args.world.get_block_state_id(args.position);
let chest_props = ChestLikeProperties::from_state_id(current_state_id, args.block);
// Only oxidize LEFT or SINGLE chests (not RIGHT) to prevent double oxidation
if chest_props.r#type == ChestType::Right {
return;
}
// Only oxidize LEFT or SINGLE chests (not RIGHT) to prevent double oxidation
if chest_props.r#type == ChestType::Right {
return;
}
// Only oxidize if no players are viewing the chest
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(chest_entity) = block_entity.as_any().downcast_ref::<ChestBlockEntity>()
&& chest_entity.get_viewer_count() > 0
{
return;
}
// Only oxidize if no players are viewing the chest
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(chest_entity) = block_entity.as_any().downcast_ref::<ChestBlockEntity>()
&& chest_entity.get_viewer_count() > 0
{
return;
}
crate::block::blocks::weathering_copper::change_over_time(
args.world,
args.position,
args.block,
)
.await;
})
crate::block::blocks::weathering_copper::change_over_time(
args.world,
args.position,
args.block,
);
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
get_chest_comparator_output(&args)
}
}
@@ -419,84 +407,64 @@ impl BlockBehaviour for CopperChestBlock {
pub struct TrappedChestBlock;
impl BlockBehaviour for TrappedChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { on_place_chest_impl(&args) })
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
on_place_chest_impl(&args)
}
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
args.r#type == LID_ANIMATION_EVENT_TYPE
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
fn placed(&self, args: PlacedArgs<'_>) {
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
Box::pin(placed_chest_impl(args, TrappedChestBlockEntity::new))
placed_chest_impl(&args, TrappedChestBlockEntity::new);
}
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { player_placed_chest_impl(&args) })
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
player_placed_chest_impl(&args);
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(normal_use_chest_impl(args))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
normal_use_chest_impl(&args)
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(broken_chest_impl(args))
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { true })
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
true
}
fn get_weak_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
// Get viewer count from this chest
let viewer_count = if let Some(block_entity) =
args.world.get_block_entity(args.position)
&& let Some(trapped_chest) = block_entity
.as_any()
.downcast_ref::<TrappedChestBlockEntity>()
{
trapped_chest.get_viewer_count()
} else {
0
};
// Get viewer count from this chest
let viewer_count = if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(trapped_chest) = block_entity
.as_any()
.downcast_ref::<TrappedChestBlockEntity>()
{
trapped_chest.get_viewer_count()
} else {
0
};
viewer_count.min(15) as u8
})
viewer_count.min(15) as u8
}
fn get_strong_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
// Strong power emitted to the block beneath the trapped chest
// The block below queries with direction Up (from below looking up at the chest)
if args.direction == BlockDirection::Up {
self.get_weak_redstone_power(args).await
} else {
0
}
})
fn get_strong_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
// Strong power emitted to the block beneath the trapped chest
// The block below queries with direction Up (from below looking up at the chest)
if args.direction == BlockDirection::Up {
self.get_weak_redstone_power(args)
} else {
0
}
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move { get_chest_comparator_output(args).await })
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
get_chest_comparator_output(&args)
}
}

View File

@@ -5,8 +5,8 @@ use pumpkin_macros::pumpkin_block;
use crate::block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockHitResult, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, UseWithItemArgs, registry::BlockActionResult,
BlockBehaviour, BlockHitResult, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, UseWithItemArgs, registry::BlockActionResult,
},
entity::{EntityBase, player::Player},
world::World,
@@ -22,121 +22,102 @@ use pumpkin_data::{
};
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use pumpkin_world::inventory::Inventory;
#[pumpkin_block("minecraft:chiseled_bookshelf")]
pub struct ChiseledBookshelfBlock;
impl BlockBehaviour for ChiseledBookshelfBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut properties = ChiseledBookshelfLikeProperties::default(args.block);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut properties = ChiseledBookshelfLikeProperties::default(args.block);
// Face in the opposite direction the player is facing
properties.facing = args.player.get_entity().get_horizontal_facing().opposite();
// Face in the opposite direction the player is facing
properties.facing = args.player.get_entity().get_horizontal_facing().opposite();
properties.to_state_id(args.block)
})
properties.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let state = args.world.get_block_state(args.position);
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
if Self::is_slot_used(properties, slot) {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<ChiseledBookshelfBlockEntity>()
{
Self::try_remove_book(
args.world,
args.player,
args.position,
block_entity,
properties,
slot,
)
.await;
return BlockActionResult::Success;
}
} else {
return BlockActionResult::Consume;
}
}
BlockActionResult::Pass
})
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
if !args
.item_stack
.get_item()
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
{
return BlockActionResult::PassToDefaultBlockAction;
}
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
if Self::is_slot_used(properties, slot) {
return BlockActionResult::PassToDefaultBlockAction;
} else if let Some(block_entity) = args.world.get_block_entity(args.position)
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
if Self::is_slot_used(properties, slot) {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<ChiseledBookshelfBlockEntity>()
{
Self::try_add_book(
Self::try_remove_book(
args.world,
args.player,
args.position,
block_entity,
properties,
slot,
args.item_stack,
)
.await;
);
return BlockActionResult::Success;
}
} else {
return BlockActionResult::Consume;
}
BlockActionResult::Pass
})
}
BlockActionResult::Pass
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_entity = ChiseledBookshelfBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
})
}
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let state = args.world.get_block_state(args.position);
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
if !args
.item_stack
.get_item()
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
{
return BlockActionResult::PassToDefaultBlockAction;
}
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
if Self::is_slot_used(properties, slot) {
return BlockActionResult::PassToDefaultBlockAction;
} else if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<ChiseledBookshelfBlockEntity>()
{
return Some((block_entity.last_interacted_slot.load(Ordering::Relaxed) + 1) as u8);
Self::try_add_book(
args.world,
args.player,
args.position,
block_entity,
properties,
slot,
args.item_stack,
);
return BlockActionResult::Success;
}
None
})
}
BlockActionResult::Pass
}
fn placed(&self, args: PlacedArgs<'_>) {
let block_entity = ChiseledBookshelfBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
}
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<ChiseledBookshelfBlockEntity>()
{
return Some((block_entity.last_interacted_slot.load(Ordering::Relaxed) + 1) as u8);
}
None
}
}
impl ChiseledBookshelfBlock {
async fn try_add_book(
fn try_add_book(
world: &Arc<World>,
player: &Player,
position: &BlockPos,
@@ -153,28 +134,24 @@ impl ChiseledBookshelfBlock {
Sound::BlockChiseledBookshelfPickup
};
entity
.set_stack(
slot as usize,
item.split_unless_creative(player.gamemode.load(), 1),
)
.await;
entity
.update_state(properties, world.clone(), slot as usize)
.await;
entity.set_book(
slot as usize,
item.split_unless_creative(player.gamemode.load(), 1),
);
entity.update_state(properties, world, slot as usize);
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
}
async fn try_remove_book(
fn try_remove_book(
world: &Arc<World>,
player: &Player,
player: &Arc<Player>,
position: &BlockPos,
entity: &ChiseledBookshelfBlockEntity,
properties: ChiseledBookshelfLikeProperties,
slot: i8,
) {
let mut stack = entity.remove_stack_specific(slot as usize, 1).await;
let mut stack = entity.remove_book(slot as usize, 1);
let sound = if stack.get_item() == &Item::ENCHANTED_BOOK {
Sound::BlockChiseledBookshelfPickupEnchanted
@@ -182,17 +159,11 @@ impl ChiseledBookshelfBlock {
Sound::BlockChiseledBookshelfPickup
};
if !player
.get_inventory()
.insert_stack_anywhere(&mut stack)
.await
{
if !player.get_inventory().insert_stack_anywhere(&mut stack) {
// Drop the item on the ground if the player cannot hold it because of a full inventory
player.drop_item(stack).await;
player.drop_item(stack);
}
entity
.update_state(properties, world.clone(), slot as usize)
.await;
entity.update_state(properties, world, slot as usize);
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
}

View File

@@ -1,4 +1,4 @@
use crate::block::{BlockBehaviour, BlockFuture, OnEntityCollisionArgs};
use crate::block::{BlockBehaviour, OnEntityCollisionArgs};
use crate::entity::EntityBase;
use pumpkin_data::effect::StatusEffect;
use pumpkin_macros::pumpkin_block;
@@ -8,17 +8,15 @@ use pumpkin_util::math::vector3::Vector3;
pub struct CobwebBlock;
impl BlockBehaviour for CobwebBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = args.entity.get_entity();
let vec = if let Some(living) = entity.get_living_entity()
&& living.has_effect(&StatusEffect::WEAVING).await
{
Vector3::new(0.5, 0.25, 0.5)
} else {
Vector3::new(0.25, 0.05, 0.25)
};
entity.slow_movement(args.state, vec).await;
})
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
let entity = args.entity.get_entity();
let vec = if let Some(living) = entity.get_living_entity()
&& living.has_effect(&StatusEffect::WEAVING)
{
Vector3::new(0.5, 0.25, 0.5)
} else {
Vector3::new(0.25, 0.05, 0.25)
};
entity.slow_movement(args.state, vec);
}
}

View File

@@ -6,9 +6,8 @@ use crate::command::CommandSender;
use crate::entity::EntityBase;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
registry::BlockActionResult,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, NormalUseArgs, OnNeighborUpdateArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, registry::BlockActionResult,
},
server::Server,
world::World,
@@ -30,7 +29,7 @@ impl CommandBlock {
dir: Facing,
) -> Option<(BlockPos, CommandBlockLikeProperties)> {
let target_pos = pos.offset(dir.to_block_direction().to_offset());
let block = world.get_block(&target_pos);
let (block, state_id) = world.get_block_and_state_id(&target_pos);
let allowed_blocks = [
Block::COMMAND_BLOCK.name,
@@ -41,7 +40,6 @@ impl CommandBlock {
return None;
}
let state_id = world.get_block_state_id(&target_pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
Some((target_pos, props))
@@ -161,7 +159,7 @@ impl CommandBlock {
if !command_blocks_work {
return;
}
let block = world.get_block(&pos);
let (block, state_id) = world.get_block_and_state_id(&pos);
if block.id != Block::CHAIN_COMMAND_BLOCK.id {
break;
@@ -178,13 +176,16 @@ impl CommandBlock {
};
let powered = command_entity.powered.load(Ordering::Relaxed);
let auto = command_entity.auto.load(Ordering::Relaxed);
let state_id = world.get_block_state_id(&pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
if powered || auto {
let conditions_met = Self::conditions_met(&world, &pos, direction);
if conditions_met {
let command = command_entity.command.lock().await;
let command = command_entity
.command
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let Some(entity) = world.get_block_entity(&pos) else {
warn!("Command block entity disappeared during execution");
break;
@@ -220,16 +221,14 @@ impl BlockMetadata for CommandBlock {
}
impl BlockBehaviour for CommandBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CommandBlockLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = CommandBlockLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
if args.player.permission_lvl.load() < PermissionLvl::Two {
return BlockActionResult::Pass;
}
@@ -238,11 +237,11 @@ impl BlockBehaviour for CommandBlock {
};
args.world.update_block_entity(&block_entity);
BlockActionResult::SuccessServer
})
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
let command_blocks_work =
{ args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
@@ -264,64 +263,68 @@ impl BlockBehaviour for CommandBlock {
args.block,
command_entity,
args.position,
block_receives_redstone_power(args.world, args.position).await,
block_receives_redstone_power(args.world, args.position),
);
}
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let command_blocks_work =
{ args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return;
};
if block_entity.resource_location() != CommandBlockEntity::ID {
return;
}
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let command_blocks_work = { args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work {
return;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return;
};
if block_entity.resource_location() != CommandBlockEntity::ID {
return;
}
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", args.position);
return;
};
let Some(server) = args.world.server.upgrade() else {
return;
};
let props = CommandBlockLikeProperties::from_state_id(
args.world.get_block_state_id(args.position),
args.block,
);
let world = args.world.clone();
let entity_clone = block_entity.clone();
let position = *args.position;
let facing = props.facing;
tokio::spawn(async move {
let Some(command_entity) = entity_clone.as_any().downcast_ref::<CommandBlockEntity>()
else {
warn!("Block entity at {} is not a command block", args.position);
return;
};
let Some(server) = args.world.server.upgrade() else {
return;
};
let props = CommandBlockLikeProperties::from_state_id(
args.world.get_block_state_id(args.position),
args.block,
);
Self::execute(
&server,
args.world.clone(),
block_entity.clone(),
&command_entity.command.lock().await,
)
.await;
let command = command_entity
.command
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
Self::execute(&server, world.clone(), entity_clone, &command).await;
Self::chain_execute(
&server,
args.world.clone(),
args.position
.offset(props.facing.to_block_direction().to_offset()),
props.facing,
world,
position.offset(facing.to_block_direction().to_offset()),
facing,
)
.await;
});
let block = args.world.get_block(args.position);
let is_auto = command_entity.auto.load(Ordering::Relaxed);
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
args.world
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
}
})
let block = args.world.get_block(args.position);
let is_auto = command_entity.auto.load(Ordering::Relaxed);
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
args.world
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -334,8 +337,8 @@ impl BlockBehaviour for CommandBlock {
false
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let send_command_feedback = {
let game_rules = &args.world.level_info.load().game_rules;
game_rules.send_command_feedback
@@ -347,14 +350,11 @@ impl BlockBehaviour for CommandBlock {
args.block.id == Block::CHAIN_COMMAND_BLOCK.id,
);
args.world.add_block_entity(Arc::new(entity));
})
}
}
fn get_comparator_output<'a>(
&'a self,
args: crate::block::GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async {
fn get_comparator_output(&self, args: crate::block::GetComparatorOutputArgs<'_>) -> Option<u8> {
{
let entity = args.world.get_block_entity(args.position);
entity.map_or_else(
@@ -368,6 +368,6 @@ impl BlockBehaviour for CommandBlock {
command_block_entity.map(|e| e.success_count.load(Ordering::Acquire) as u8)
},
)
})
}
}
}

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::{
block::{
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs,
BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs,
UseWithItemArgs, registry::BlockActionResult,
},
entity::{Entity, item::ItemEntity},
@@ -27,32 +27,27 @@ use rand::RngExt;
pub struct ComposterBlock;
impl BlockBehaviour for ComposterBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
if props.level == 8 {
self.clear_composter(args.world, args.position, state_id, args.block)
.await;
self.clear_composter(args.world, args.position, state_id, args.block);
}
BlockActionResult::Pass
})
}
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
{
let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.level;
// Check if the composter is full
if level == 8 {
self.clear_composter(args.world, args.position, state_id, args.block)
.await;
self.clear_composter(args.world, args.position, state_id, args.block);
return BlockActionResult::Consume;
}
@@ -77,48 +72,35 @@ impl BlockBehaviour for ComposterBlock {
state_id,
args.block,
level + 1,
)
.await;
);
args.world
.sync_world_event(WorldEvent::ComposterFill, *args.position, 1);
}
// Consume the item
BlockActionResult::Consume
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.level;
if level == 7 {
self.update_level_composter(
args.world,
args.position,
state_id,
args.block,
level + 1,
)
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.level;
if level == 7 {
self.update_level_composter(args.world, args.position, state_id, args.block, level + 1);
}
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
{
let props = ComposterLikeProperties::from_state_id(args.state.id, args.block);
Some(props.level)
})
}
}
}
impl ComposterBlock {
pub async fn update_level_composter(
pub fn update_level_composter(
&self,
world: &Arc<World>,
location: &BlockPos,
@@ -128,23 +110,20 @@ impl ComposterBlock {
) {
let mut props = ComposterLikeProperties::from_state_id(state_id, block);
props.level = level;
world
.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
if level == 7 {
world.schedule_block_tick(block, *location, 20, TickPriority::Normal);
}
}
pub async fn clear_composter(
pub fn clear_composter(
&self,
world: &Arc<World>,
location: &BlockPos,
state_id: BlockStateId,
block: &Block,
) {
self.update_level_composter(world, location, state_id, block, 0)
.await;
self.update_level_composter(world, location, state_id, block, 0);
let item_position = {
let mut rng = rand::rng();
@@ -160,6 +139,6 @@ impl ComposterBlock {
ItemStack::new(1, &Item::BONE_MEAL),
);
world.spawn_entity(Arc::new(item_entity)).await;
world.spawn_entity(Arc::new(item_entity));
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::entities::conduit::ConduitBlockEntity;
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs};
use crate::block::{BlockBehaviour, OnPlaceArgs, PlacedArgs};
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block;
@@ -9,20 +9,18 @@ use std::sync::Arc;
pub struct ConduitBlock;
impl BlockBehaviour for ConduitBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let entity = ConduitBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, BlockMetadata, OnScheduledTickArgs, PlacedArgs,
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
};
use pumpkin_data::{Block, BlockId, tag};
@@ -18,25 +18,22 @@ impl BlockMetadata for CoralBlock {
}
}
impl BlockBehaviour for CoralBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
try_schedule_die_tick(args.block, args.world, args.position).await;
fn placed(&self, args: PlacedArgs<'_>) {
{
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
try_schedule_die_tick(args.block, args.world, args.position);
}
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
let Some(dead_block) = get_dead_coral_block_type(args.block.id) else {
return;
};
let dead_block_state_id = dead_block.default_state.id;
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
let Some(dead_block) = get_dead_coral_block_type(args.block.id) else {
return;
};
let dead_block_state_id = dead_block.default_state.id;
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
}
}
}
const fn get_dead_coral_block_type(id: BlockId) -> Option<&'static Block> {

View File

@@ -1,6 +1,6 @@
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs,
BlockBehaviour, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs,
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
},
@@ -47,102 +47,90 @@ pub type CoralWallFanLikeProperties = LadderLikeProperties;
pub type CoralFanLikeProperties = MangroveRootsLikeProperties;
impl BlockBehaviour for CoralFanBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(
args.block,
args.replacing.water_source(),
);
}
}
let mut directions = args.player.get_entity().get_entity_facing_order();
if args.replacing == BlockIsReplacing::None {
let face = args.direction.to_facing();
let mut i = 0;
while i < directions.len() && directions[i] != face {
i += 1;
}
if i > 0 {
directions.copy_within(0..i, 1);
directions[0] = face;
}
} else if directions[0] == Facing::Down {
let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(
args.block,
args.replacing.water_source(),
);
}
}
for dir in directions {
if let (Some(h_facing), Some(opp_facing)) = (
dir.to_horizontal_facing(),
dir.opposite().to_horizontal_facing(),
) && can_place_at(args.world, args.position, h_facing)
{
let Some(wall_block) = get_corresponding_wall_fan_type(args.block.id) else {
return BlockStateId::AIR;
};
let mut coral_wall_fan_props = CoralWallFanLikeProperties::default(wall_block);
coral_wall_fan_props.waterlogged = args.replacing.water_source();
coral_wall_fan_props.facing = opp_facing;
return coral_wall_fan_props.to_state_id(wall_block);
}
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
}
BlockStateId::AIR
})
}
}
let mut directions = args.player.get_entity().get_entity_facing_order();
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await {
try_schedule_die_tick(args.block, args.world, args.position).await;
if args.replacing == BlockIsReplacing::None {
let face = args.direction.to_facing();
let mut i = 0;
while i < directions.len() && directions[i] != face {
i += 1;
}
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
let current_state = args.world.get_block_state(args.position);
let Some(dead_block) = get_dead_type(args.block.id) else {
return;
};
// VANILLA FIX: Explicitly set waterlogged to false when dying
let dead_block_state_id = if is_wall_fan(args.block) {
let mut props =
CoralWallFanLikeProperties::from_state_id(current_state.id, args.block);
props.waterlogged = false;
props.to_state_id(dead_block)
} else {
let mut props =
CoralFanLikeProperties::from_state_id(current_state.id, args.block);
props.waterlogged = false;
props.to_state_id(dead_block)
};
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
.await;
if i > 0 {
directions.copy_within(0..i, 1);
directions[0] = face;
}
})
} else if directions[0] == Facing::Down {
let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
}
}
for dir in directions {
if let (Some(h_facing), Some(opp_facing)) = (
dir.to_horizontal_facing(),
dir.opposite().to_horizontal_facing(),
) && can_place_at(args.world, args.position, h_facing)
{
let Some(wall_block) = get_corresponding_wall_fan_type(args.block.id) else {
return BlockStateId::AIR;
};
let mut coral_wall_fan_props = CoralWallFanLikeProperties::default(wall_block);
coral_wall_fan_props.waterlogged = args.replacing.water_source();
coral_wall_fan_props.facing = opp_facing;
return coral_wall_fan_props.to_state_id(wall_block);
}
}
let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
}
BlockStateId::AIR
}
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> bool {
fn placed(&self, args: PlacedArgs<'_>) {
{
if !scan_for_water(args.world, args.position) {
try_schedule_die_tick(args.block, args.world, args.position);
}
}
}
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
let current_state = args.world.get_block_state(args.position);
let Some(dead_block) = get_dead_type(args.block.id) else {
return;
};
// VANILLA FIX: Explicitly set waterlogged to false when dying
let dead_block_state_id = if is_wall_fan(args.block) {
let mut props =
CoralWallFanLikeProperties::from_state_id(current_state.id, args.block);
props.waterlogged = false;
props.to_state_id(dead_block)
} else {
let mut props = CoralFanLikeProperties::from_state_id(current_state.id, args.block);
props.waterlogged = false;
props.to_state_id(dead_block)
};
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
let support_block = args.block_accessor.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) && !is_wall_fan(args.block) {
return true;
@@ -155,27 +143,25 @@ impl BlockBehaviour for CoralFanBlock {
false
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if is_wall_fan(args.block) {
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
if props.facing.to_block_direction().opposite() == args.direction
&& !can_place_at(args.world, args.position, props.facing.opposite())
{
return BlockStateId::AIR;
}
} else if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if !support_block.is_center_solid(BlockDirection::Up) {
return BlockStateId::AIR;
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if is_wall_fan(args.block) {
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
if props.facing.to_block_direction().opposite() == args.direction
&& !can_place_at(args.world, args.position, props.facing.opposite())
{
return BlockStateId::AIR;
}
} else if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if !support_block.is_center_solid(BlockDirection::Up) {
return BlockStateId::AIR;
}
}
args.state_id
})
args.state_id
}
}

View File

@@ -6,8 +6,8 @@ use pumpkin_data::{
use pumpkin_world::world::BlockFlags;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
};
pub struct CoralPlantBlock;
@@ -26,36 +26,30 @@ impl BlockMetadata for CoralPlantBlock {
pub type CoralPlantLikeProperties = MangroveRootsLikeProperties;
impl BlockBehaviour for CoralPlantBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CoralPlantLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = CoralPlantLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
try_schedule_die_tick(args.block, args.world, args.position).await;
fn placed(&self, args: PlacedArgs<'_>) {
{
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
try_schedule_die_tick(args.block, args.world, args.position);
}
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
let current_state = args.world.get_block_state(args.position);
let dead_block_state_id = {
let props =
CoralPlantLikeProperties::from_state_id(current_state.id, args.block);
props.to_state_id(get_dead_type(args.block.id).unwrap_or_default().to_block())
};
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
let current_state = args.world.get_block_state(args.position);
let dead_block_state_id = {
let props = CoralPlantLikeProperties::from_state_id(current_state.id, args.block);
props.to_state_id(get_dead_type(args.block.id).unwrap_or_default().to_block())
};
args.world
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
}
}
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> bool {
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
let support_block = args.block_accessor.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) {
return true;
@@ -63,19 +57,17 @@ impl BlockBehaviour for CoralPlantBlock {
false
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if !support_block.is_center_solid(BlockDirection::Up) {
return BlockStateId::AIR;
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction == BlockDirection::Down {
let support_block = args.world.get_block_state(&args.position.down());
if !support_block.is_center_solid(BlockDirection::Up) {
return BlockStateId::AIR;
}
args.state_id
})
}
args.state_id
}
}
const fn get_dead_type(id: BlockId) -> Option<BlockId> {

View File

@@ -12,7 +12,7 @@ pub mod coral_block;
pub mod coral_fan;
pub mod coral_plant;
pub async fn scan_for_water(world: &Arc<World>, pos: &BlockPos) -> bool {
pub fn scan_for_water(world: &Arc<World>, pos: &BlockPos) -> bool {
for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset());
let block = world.get_fluid(&neighbor_pos);
@@ -44,7 +44,7 @@ fn is_dead_coral(block: &Block) -> bool {
|| block == &Block::DEAD_TUBE_CORAL_FAN
|| block == &Block::DEAD_TUBE_CORAL_WALL_FAN
}
async fn try_schedule_die_tick(block: &Block, world: &Arc<World>, pos: &BlockPos) {
pub fn try_schedule_die_tick(block: &Block, world: &Arc<World>, pos: &BlockPos) {
let tick_delay = 60 + rand::rng().random_range(0..40);
world.schedule_block_tick(
block,

View File

@@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
use crate::block::{BlockBehaviour, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler;
@@ -16,24 +16,22 @@ use tokio::sync::Mutex;
pub struct CraftingTableBlock;
impl BlockBehaviour for CraftingTableBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
1,
)
.await;
args.player
.open_handled_screen(
&CraftingTableScreenFactory(args.server.recipe_manager.clone()),
Some(*args.position),
)
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
1,
);
let player = Arc::clone(args.player);
let recipe_manager = args.server.recipe_manager.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&CraftingTableScreenFactory(recipe_manager), Some(pos))
.await;
});
BlockActionResult::Success
})
BlockActionResult::Success
}
}

View File

@@ -11,9 +11,7 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_world::world::BlockFlags;
use crate::block::entities::creaking_heart::CreakingHeartBlockEntity;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
};
use crate::block::{BlockBehaviour, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
#[pumpkin_block("minecraft:creaking_heart")]
pub struct CreakingHeartBlock;
@@ -45,24 +43,20 @@ impl CreakingHeartBlock {
}
impl BlockBehaviour for CreakingHeartBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
CreakingHeartLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.axis = match args.direction {
pumpkin_data::BlockDirection::North | pumpkin_data::BlockDirection::South => {
Axis::Z
}
pumpkin_data::BlockDirection::East | pumpkin_data::BlockDirection::West => Axis::X,
pumpkin_data::BlockDirection::Up | pumpkin_data::BlockDirection::Down => Axis::Y,
};
props.creaking_heart_state = CreakingHeartState::Uprooted;
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
CreakingHeartLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.axis = match args.direction {
pumpkin_data::BlockDirection::North | pumpkin_data::BlockDirection::South => Axis::Z,
pumpkin_data::BlockDirection::East | pumpkin_data::BlockDirection::West => Axis::X,
pumpkin_data::BlockDirection::Up | pumpkin_data::BlockDirection::Down => Axis::Y,
};
props.creaking_heart_state = CreakingHeartState::Uprooted;
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let entity = CreakingHeartBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
@@ -71,13 +65,11 @@ impl BlockBehaviour for CreakingHeartBlock {
if Self::check_active_logs(args.world.as_ref(), args.position, props.axis) {
props.creaking_heart_state = CreakingHeartState::Dormant;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
args.world.play_sound(
Sound::BlockCreakingHeartSpawn,
@@ -85,11 +77,11 @@ impl BlockBehaviour for CreakingHeartBlock {
&args.position.to_f64(),
);
}
})
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
let state_id = args.world.get_block_state_id(args.position);
let mut props = CreakingHeartLikeProperties::from_state_id(state_id, args.block);
@@ -110,27 +102,24 @@ impl BlockBehaviour for CreakingHeartBlock {
if props.creaking_heart_state != new_state {
props.creaking_heart_state = new_state;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
}
})
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn broken(&self, args: BrokenArgs<'_>) {
{
args.world.play_sound(
Sound::BlockCreakingHeartBreak,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.world
.drop_stack(args.position, ItemStack::new(1, &Item::CREAKING_HEART))
.await;
})
.drop_stack(args.position, ItemStack::new(1, &Item::CREAKING_HEART));
}
}
}

View File

@@ -10,125 +10,104 @@ use pumpkin_macros::pumpkin_block;
use crate::block::entities::decorated_pot::DecoratedPotBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, UseWithItemArgs,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
UseWithItemArgs,
};
#[pumpkin_block("minecraft:decorated_pot")]
pub struct DecoratedPotBlock;
impl BlockBehaviour for DecoratedPotBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = DecoratedPotBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let entity = DecoratedPotBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if args.item_stack.item_count == 0 {
return self
.normal_use(NormalUseArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
hit: args.hit,
})
.await;
}
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
if args.item_stack.item_count == 0 {
return self.normal_use(NormalUseArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
hit: args.hit,
});
}
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
{
if pot_entity.try_insert_item(args.item_stack, 1).await {
args.world.play_sound(
Sound::BlockDecoratedPotInsert,
SoundCategory::Blocks,
&args.position.to_f64(),
);
} else {
args.world.play_sound(
Sound::BlockDecoratedPotInsertFail,
SoundCategory::Blocks,
&args.position.to_f64(),
);
}
return BlockActionResult::Success;
}
BlockActionResult::Pass
})
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.world.play_sound(
Sound::BlockDecoratedPotInsertFail,
SoundCategory::Blocks,
&args.position.to_f64(),
);
BlockActionResult::Success
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
&& let Some(contained) = pot_entity.take_item().await
{
args.world.drop_stack(args.position, contained).await;
}
args.world.play_sound(
Sound::BlockDecoratedPotShatter,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.world
.drop_stack(args.position, ItemStack::new(4, &Item::BRICK))
.await;
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
{
Some(pot_entity.get_comparator_output().await)
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
{
if pot_entity.try_insert_item(args.item_stack, 1) {
args.world.play_sound(
Sound::BlockDecoratedPotInsert,
SoundCategory::Blocks,
&args.position.to_f64(),
);
} else {
Some(0)
args.world.play_sound(
Sound::BlockDecoratedPotInsertFail,
SoundCategory::Blocks,
&args.position.to_f64(),
);
}
})
return BlockActionResult::Success;
}
BlockActionResult::Pass
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
args.world.play_sound(
Sound::BlockDecoratedPotInsertFail,
SoundCategory::Blocks,
&args.position.to_f64(),
);
BlockActionResult::Success
}
fn broken(&self, args: BrokenArgs<'_>) {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
&& let Some(contained) = pot_entity.take_item()
{
args.world.drop_stack(args.position, contained);
}
args.world.play_sound(
Sound::BlockDecoratedPotShatter,
SoundCategory::Blocks,
&args.position.to_f64(),
);
args.world
.drop_stack(args.position, ItemStack::new(4, &Item::BRICK));
}
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity
.as_any()
.downcast_ref::<DecoratedPotBlockEntity>()
{
Some(pot_entity.get_comparator_output())
} else {
Some(0)
}
}
}

View File

@@ -1,5 +1,4 @@
use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
@@ -17,40 +16,32 @@ use pumpkin_world::world::BlockFlags;
pub struct DirtPathBlock;
impl BlockBehaviour for DirtPathBlock {
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// TODO: push up entities
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
// TODO: push up entities
args.world.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
if !can_place_at(args.world, args.position) {
return Block::DIRT.default_state.id;
}
args.block.default_state.id
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
args.world
.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
})
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
return Block::DIRT.default_state.id;
}
args.block.default_state.id
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {

View File

@@ -1,4 +1,3 @@
use crate::entity::EntityBase;
use pumpkin_data::BlockDirection;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::Axis;
@@ -17,7 +16,6 @@ use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::BrokenArgs;
use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs;
@@ -36,7 +34,7 @@ use pumpkin_util::GameMode;
type DoorProperties = pumpkin_data::block_properties::OakDoorLikeProperties;
async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos) {
fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos) {
let (block, block_state) = world.get_block_and_state_id(block_pos);
let mut door_props = DoorProperties::from_state_id(block_state, block);
door_props.open = !door_props.open;
@@ -58,20 +56,16 @@ async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos)
*block_pos,
);
world
.set_block_state(
block_pos,
door_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world
.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
block_pos,
door_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
world.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
);
}
fn can_open_door(block: &Block) -> bool {
@@ -103,7 +97,7 @@ fn get_sound(block: &Block, open: bool) -> Sound {
#[expect(clippy::pedantic)]
#[inline]
async fn get_hinge(
fn get_hinge(
world: &World,
pos: &BlockPos,
use_item: &SUseItemOn,
@@ -177,7 +171,7 @@ impl DoorBlock {
door_props.open
}
pub async fn set_open(world: &Arc<World>, block_pos: &BlockPos, open: bool) {
pub fn set_open(world: &Arc<World>, block_pos: &BlockPos, open: bool) {
let (block, block_state) = world.get_block_and_state_id(block_pos);
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
return;
@@ -198,46 +192,39 @@ impl DoorBlock {
world.play_block_sound(get_sound(block, open), SoundCategory::Blocks, *block_pos);
world
.set_block_state(
block_pos,
door_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
block_pos,
door_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
if other_block.id == block.id {
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
other_door_props.open = open;
world
.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
);
}
}
}
impl BlockBehaviour for DoorBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &args.position.up()).await;
let direction = args.player.get_entity().get_horizontal_facing();
let hinge = get_hinge(args.world, args.position, args.use_item_on, direction).await;
let mut door_props = DoorProperties::default(args.block);
door_props.half = DoubleBlockHalf::Lower;
door_props.facing = direction;
door_props.hinge = hinge;
door_props.powered = powered;
door_props.open = powered;
door_props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut door_props = DoorProperties::default(args.block);
let facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
door_props.facing = facing;
door_props.half = DoubleBlockHalf::Lower;
door_props.hinge = get_hinge(args.world, args.position, args.use_item_on, facing);
door_props.open = false;
door_props.powered = false;
door_props.to_state_id(args.block)
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -248,65 +235,59 @@ impl BlockBehaviour for DoorBlock {
.replaceable()
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let mut door_props = DoorProperties::from_state_id(args.state_id, args.block);
door_props.half = DoubleBlockHalf::Upper;
args.world
.set_block_state(
&args.position.offset(BlockDirection::Up.to_offset()),
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
)
.await;
})
args.world.set_block_state(
&args.position.offset(BlockDirection::Up.to_offset()),
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
);
}
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
if !can_open_door(args.block) {
return BlockActionResult::Pass;
}
toggle_door(args.player, args.world, args.position).await;
toggle_door(args.player, args.world, args.position);
BlockActionResult::Success
})
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let door_props = DoorProperties::from_state_id(args.state.id, args.block);
let other_half_pos = match door_props.half {
DoubleBlockHalf::Upper => args.position.down(),
DoubleBlockHalf::Lower => args.position.up(),
};
fn broken(&self, args: BrokenArgs<'_>) {
let door_props = DoorProperties::from_state_id(args.state.id, args.block);
let other_half_pos = match door_props.half {
DoubleBlockHalf::Upper => args.position.down(),
DoubleBlockHalf::Lower => args.position.up(),
};
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
if neighbor_state_id.to_block_id() != args.block.id {
args.world.update_neighbors(&other_half_pos, None).await;
return; // Neighbor is already gone or is a different block
}
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
if neighbor_state_id.to_block_id() != args.block.id {
args.world.update_neighbors(&other_half_pos, None);
return; // Neighbor is already gone or is a different block
}
let is_creative = args.player.gamemode.load() == GameMode::Creative;
let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative {
BlockFlags::NOTIFY_ALL
} else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL
};
let is_creative = args.player.gamemode.load() == GameMode::Creative;
let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative {
BlockFlags::NOTIFY_ALL
} else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL
};
args.world
.break_block(&other_half_pos, Some(args.player.clone()), flags)
.await;
})
args.world
.break_block(&other_half_pos, Some(args.player.clone()), flags);
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
let block_state = args.world.get_block_state(args.position);
let mut door_props = DoorProperties::from_state_id(block_state.id, args.block);
let other_half = match door_props.half {
DoubleBlockHalf::Upper => BlockDirection::Down,
DoubleBlockHalf::Lower => BlockDirection::Up,
@@ -314,23 +295,28 @@ impl BlockBehaviour for DoorBlock {
let other_pos = args.position.offset(other_half.to_offset());
let (other_block, other_state_id) = args.world.get_block_and_state_id(&other_pos);
if other_block.id != args.block.id {
return;
}
let powered = block_receives_redstone_power(args.world, args.position)
|| block_receives_redstone_power(args.world, &other_pos);
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &other_pos).await;
if door_props.powered != powered {
let sound_half = if door_props.open {
DoubleBlockHalf::Lower
} else {
DoubleBlockHalf::Upper
};
if args.block.id == other_block.id && powered != door_props.powered {
let mut other_door_props =
DoorProperties::from_state_id(other_state_id, other_block);
door_props.powered = !door_props.powered;
other_door_props.powered = door_props.powered;
if powered != door_props.open {
door_props.open = door_props.powered;
other_door_props.open = other_door_props.powered;
door_props.powered = powered;
other_door_props.powered = powered;
if door_props.open != powered {
door_props.open = powered;
other_door_props.open = powered;
}
if door_props.half == sound_half {
args.world.play_block_sound(
get_sound(args.block, powered),
SoundCategory::Blocks,
@@ -338,55 +324,48 @@ impl BlockBehaviour for DoorBlock {
);
}
args.world
.set_block_state(
args.position,
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world
.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
args.world.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_ALL,
);
}
})
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
if args.direction.to_axis() != Axis::Y
|| (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
if args.direction.to_axis() != Axis::Y
|| (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
{
if lv == DoubleBlockHalf::Lower
&& args.direction == BlockDirection::Down
&& !has_support(args.world, args.position)
{
if lv == DoubleBlockHalf::Lower
&& args.direction == BlockDirection::Down
&& !has_support(args.world, args.position)
{
return BlockStateId::AIR;
}
} else if Block::from_state_id(args.neighbor_state_id).id == args.block.id
&& DoorProperties::from_state_id(args.neighbor_state_id, args.block).half != lv
{
let mut new_state =
DoorProperties::from_state_id(args.neighbor_state_id, args.block);
new_state.half = lv;
return new_state.to_state_id(args.block);
} else {
return BlockStateId::AIR;
}
args.state_id
})
} else if Block::from_state_id(args.neighbor_state_id).id == args.block.id
&& DoorProperties::from_state_id(args.neighbor_state_id, args.block).half != lv
{
let mut new_state = DoorProperties::from_state_id(args.neighbor_state_id, args.block);
new_state.half = lv;
return new_state.to_state_id(args.block);
} else {
return BlockStateId::AIR;
}
args.state_id
}
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
{
if args.moved {
return;
}
@@ -403,14 +382,12 @@ impl BlockBehaviour for DoorBlock {
DoubleBlockHalf::Lower => args.position.up(),
};
args.world
.break_block(
&other_half_pos,
None,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL,
)
.await;
})
args.world.break_block(
&other_half_pos,
None,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL,
);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::block::blocks::falling::FallingBlock;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, PlacedArgs};
use crate::block::{BlockBehaviour, BrokenArgs, NormalUseArgs, PlacedArgs};
use crate::world::World;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
@@ -12,7 +12,7 @@ use std::sync::Arc;
pub struct DragonEggBlock;
impl DragonEggBlock {
async fn teleport(&self, world: &Arc<World>, pos: &BlockPos) {
fn teleport(world: &Arc<World>, pos: &BlockPos) {
for _ in 0..1000 {
let x = pos.0.x + rng().random_range(-16..16);
let y = pos.0.y + rng().random_range(-8..8);
@@ -24,20 +24,16 @@ impl DragonEggBlock {
if state.is_air() && !below_state.is_air() {
let current_state = world.get_block_state(pos);
world
.set_block_state(
&test_pos,
current_state.id,
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
)
.await;
world
.set_block_state(
pos,
pumpkin_data::Block::AIR.default_state.id,
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
&test_pos,
current_state.id,
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
);
world.set_block_state(
pos,
pumpkin_data::Block::AIR.default_state.id,
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
);
return;
}
}
@@ -45,33 +41,22 @@ impl DragonEggBlock {
}
impl BlockBehaviour for DragonEggBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
args.world
.schedule_block_tick(args.block, *args.position, 5, TickPriority::Normal);
})
fn placed(&self, args: PlacedArgs<'_>) {
args.world
.schedule_block_tick(args.block, *args.position, 5, TickPriority::Normal);
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
self.teleport(args.world, args.position).await;
BlockActionResult::Success
})
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Self::teleport(args.world, args.position);
BlockActionResult::Success
}
// Dragon egg is typically teleported when attacked
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
self.teleport(args.world, args.position).await;
})
fn broken(&self, args: BrokenArgs<'_>) {
Self::teleport(args.world, args.position);
}
fn on_scheduled_tick<'a>(
&'a self,
args: crate::block::OnScheduledTickArgs<'a>,
) -> BlockFuture<'a, ()> {
Box::pin(async move {
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
})
fn on_scheduled_tick(&self, args: crate::block::OnScheduledTickArgs<'_>) {
FallingBlock::on_scheduled_tick(&FallingBlock, args);
}
}

View File

@@ -2,8 +2,8 @@ use std::sync::Arc;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, PlacedArgs,
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
PlacedArgs,
},
entity::player::Player,
world::World,
@@ -30,26 +30,24 @@ impl BlockBehaviour for DripstoneBlock {
args.player,
)
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut dripstone_props = PointedDripstoneLikeProperties::default(args.block);
dripstone_props.waterlogged = args.replacing.water_source();
let Some(support_block_ver_dir) = get_support_block_vertical_direction(
args.world,
args.position,
Some(args.direction),
Some(args.player),
) else {
//this shouldn't happen
return Block::AIR.default_state.id;
};
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut dripstone_props = PointedDripstoneLikeProperties::default(args.block);
dripstone_props.waterlogged = args.replacing.water_source();
let Some(support_block_ver_dir) = get_support_block_vertical_direction(
args.world,
args.position,
Some(args.direction),
Some(args.player),
) else {
//this shouldn't happen
return Block::AIR.default_state.id;
};
dripstone_props.vertical_direction = flip_dir(support_block_ver_dir);
dripstone_props.to_state_id(&Block::POINTED_DRIPSTONE)
})
dripstone_props.vertical_direction = flip_dir(support_block_ver_dir);
dripstone_props.to_state_id(&Block::POINTED_DRIPSTONE)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let (len, vertical_dir) = get_stalagmite_or_stalactice_len_and_dir_from_tip_pos(
args.world,
args.position,
@@ -57,16 +55,16 @@ impl BlockBehaviour for DripstoneBlock {
);
match vertical_dir {
VerticalDirection::Up => {
update_stalagmite(args.world, len, args.position).await;
update_stalagmite(args.world, len, args.position);
}
VerticalDirection::Down => {
update_stalactite(args.world, len, args.position).await;
update_stalactite(args.world, len, args.position);
}
}
})
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn broken(&self, args: BrokenArgs<'_>) {
{
let broken_dripstone_props =
PointedDripstoneLikeProperties::from_state_id(args.state.id, args.block);
let new_tip_pos = match broken_dripstone_props.vertical_direction {
@@ -81,54 +79,52 @@ impl BlockBehaviour for DripstoneBlock {
);
match vertical_dir {
VerticalDirection::Up => {
update_stalagmite(args.world, len, &new_tip_pos).await;
update_stalagmite(args.world, len, &new_tip_pos);
}
VerticalDirection::Down => {
update_stalactite(args.world, len, &new_tip_pos).await;
update_stalactite(args.world, len, &new_tip_pos);
}
}
})
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at_pos(args.world, args.position, None, None) {
return Block::AIR.default_state.id;
}
let mut dripstone_props =
PointedDripstoneLikeProperties::from_state_id(args.state_id, args.block);
if dripstone_props.thickness != SpeleothemThickness::TipMerge {
return args.state_id;
}
match dripstone_props.vertical_direction {
VerticalDirection::Up => {
let block_above = args.world.get_block(&args.position.up());
if block_above != &Block::POINTED_DRIPSTONE {
dripstone_props.thickness = SpeleothemThickness::Tip;
return dripstone_props.to_state_id(args.block);
}
}
VerticalDirection::Down => {
let block_below = args.world.get_block(&args.position.down());
if block_below != &Block::POINTED_DRIPSTONE {
dripstone_props.thickness = SpeleothemThickness::Tip;
return dripstone_props.to_state_id(args.block);
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at_pos(args.world, args.position, None, None) {
return Block::AIR.default_state.id;
}
let mut dripstone_props =
PointedDripstoneLikeProperties::from_state_id(args.state_id, args.block);
if dripstone_props.thickness != SpeleothemThickness::TipMerge {
return args.state_id;
}
match dripstone_props.vertical_direction {
VerticalDirection::Up => {
let block_above = args.world.get_block(&args.position.up());
if block_above != &Block::POINTED_DRIPSTONE {
dripstone_props.thickness = SpeleothemThickness::Tip;
return dripstone_props.to_state_id(args.block);
}
}
args.state_id
})
VerticalDirection::Down => {
let block_below = args.world.get_block(&args.position.down());
if block_below != &Block::POINTED_DRIPSTONE {
dripstone_props.thickness = SpeleothemThickness::Tip;
return dripstone_props.to_state_id(args.block);
}
}
}
args.state_id
}
}
async fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
let block_above = world.get_block(&tip_pos.up());
if block_above == &Block::POINTED_DRIPSTONE {
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge).await;
modify_dripstone_thickness_to(world, &tip_pos.up(), SpeleothemThickness::TipMerge).await;
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge);
modify_dripstone_thickness_to(world, &tip_pos.up(), SpeleothemThickness::TipMerge);
} else {
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip).await;
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip);
}
match stalagmite_len {
2 => {
@@ -136,74 +132,65 @@ async fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &Blo
world,
&tip_pos.down_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
}
3 => {
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(2),
SpeleothemThickness::Base,
)
.await;
);
}
4 => {
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(2),
SpeleothemThickness::Middle,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(3),
SpeleothemThickness::Base,
)
.await;
);
}
5 => {
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(2),
SpeleothemThickness::Middle,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.down_height(3),
SpeleothemThickness::Middle,
)
.await;
);
}
_ => {}
}
}
async fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
let block_below = world.get_block(&tip_pos.down());
if block_below == &Block::POINTED_DRIPSTONE {
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge).await;
modify_dripstone_thickness_to(world, &tip_pos.down(), SpeleothemThickness::TipMerge).await;
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge);
modify_dripstone_thickness_to(world, &tip_pos.down(), SpeleothemThickness::TipMerge);
} else {
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip).await;
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip);
}
match stalagmite_len {
2 => {
@@ -211,54 +198,45 @@ async fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &Blo
world,
&tip_pos.up_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
}
3 => {
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(1),
SpeleothemThickness::Frustum,
)
.await;
modify_dripstone_thickness_to(world, &tip_pos.up_height(2), SpeleothemThickness::Base)
.await;
);
modify_dripstone_thickness_to(world, &tip_pos.up_height(2), SpeleothemThickness::Base);
}
4 => {
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(2),
SpeleothemThickness::Middle,
)
.await;
modify_dripstone_thickness_to(world, &tip_pos.up_height(3), SpeleothemThickness::Base)
.await;
);
modify_dripstone_thickness_to(world, &tip_pos.up_height(3), SpeleothemThickness::Base);
}
5 => {
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(1),
SpeleothemThickness::Frustum,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(2),
SpeleothemThickness::Middle,
)
.await;
);
modify_dripstone_thickness_to(
world,
&tip_pos.up_height(3),
SpeleothemThickness::Middle,
)
.await;
);
}
_ => {}
}
@@ -382,7 +360,7 @@ fn can_support_dripstone(support_block: &Block) -> bool {
}
false
}
async fn modify_dripstone_thickness_to(
fn modify_dripstone_thickness_to(
world: &Arc<World>,
pos: &BlockPos,
new_thickness: SpeleothemThickness,
@@ -399,13 +377,11 @@ async fn modify_dripstone_thickness_to(
return;
}
support_props.thickness = new_thickness;
world
.set_block_state(
pos,
support_props.to_state_id(&Block::POINTED_DRIPSTONE),
BlockFlags::empty(),
)
.await;
world.set_block_state(
pos,
support_props.to_state_id(&Block::POINTED_DRIPSTONE),
BlockFlags::empty(),
);
}
fn offset_pos_by_vertical_dir(pos: &BlockPos, ver_dir: VerticalDirection) -> BlockPos {
match ver_dir {

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::block::entities::enchanting_table::EnchantingTableBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, PlacedArgs};
use crate::block::{BlockBehaviour, NormalUseArgs, PlacedArgs};
use pumpkin_data::{Block, BlockStateId, translation};
use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
@@ -19,70 +19,71 @@ use tokio::sync::Mutex;
pub struct EnchantingTableBlock;
impl BlockBehaviour for EnchantingTableBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = EnchantingTableBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let entity = EnchantingTableBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let mut bookshelf_count = 0;
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let mut bookshelf_count = 0;
for off_z in -1..=1 {
for off_x in -1..=1 {
if (off_z != 0 || off_x != 0)
&& args
.world
.get_block_state(&args.position.add(off_x, 0, off_z))
.id
== BlockStateId::AIR
&& args
.world
.get_block_state(&args.position.add(off_x, 1, off_z))
.id
== BlockStateId::AIR
// Air
{
for off_y in 0..=1 {
for off_z in -1..=1 {
for off_x in -1..=1 {
if (off_z != 0 || off_x != 0)
&& args
.world
.get_block_state(&args.position.add(off_x, 0, off_z))
.id
== BlockStateId::AIR
&& args
.world
.get_block_state(&args.position.add(off_x, 1, off_z))
.id
== BlockStateId::AIR
// Air
{
for off_y in 0..=1 {
if Self::is_bookshelf(
args.world,
&args.position.add(off_x * 2, off_y, off_z * 2),
) {
bookshelf_count += 1;
}
if off_x != 0 && off_z != 0 {
if Self::is_bookshelf(
args.world,
&args.position.add(off_x * 2, off_y, off_z * 2),
&args.position.add(off_x * 2, off_y, off_z),
) {
bookshelf_count += 1;
}
if off_x != 0 && off_z != 0 {
if Self::is_bookshelf(
args.world,
&args.position.add(off_x * 2, off_y, off_z),
) {
bookshelf_count += 1;
}
if Self::is_bookshelf(
args.world,
&args.position.add(off_x, off_y, off_z * 2),
) {
bookshelf_count += 1;
}
if Self::is_bookshelf(
args.world,
&args.position.add(off_x, off_y, off_z * 2),
) {
bookshelf_count += 1;
}
}
}
}
}
let bookshelf_count = bookshelf_count.min(15);
}
let bookshelf_count = bookshelf_count.min(15);
args.player
let player = Arc::clone(args.player);
let pos = *args.position;
let seed = args.player.enchantment_seed();
tokio::spawn(async move {
player
.open_handled_screen(
&EnchantingTableScreenFactory {
bookshelf_count,
seed: args.player.enchantment_seed(),
seed,
},
Some(*args.position),
Some(pos),
)
.await;
BlockActionResult::Success
})
});
BlockActionResult::Success
}
}

View File

@@ -1,7 +1,6 @@
use std::sync::Arc;
use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::OnEntityCollisionArgs;
use crate::block::PlacedArgs;
use crate::block::entities::end_portal::EndPortalBlockEntity;
@@ -12,36 +11,31 @@ use pumpkin_macros::pumpkin_block;
pub struct EndPortalBlock;
impl BlockBehaviour for EndPortalBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let target_world =
if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name {
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else {
args.server.get_world_from_dimension(&Dimension::THE_END)
};
if Arc::ptr_eq(&target_world, args.world) {
return;
}
tracing::info!(
"End portal collision at {:?}, targeting world {:?}",
args.position,
target_world.dimension.minecraft_name
);
args.entity
.get_entity()
.try_use_portal(0, target_world, *args.position)
.await;
})
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
let target_world =
if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name {
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else {
args.server.get_world_from_dimension(&Dimension::THE_END)
};
if Arc::ptr_eq(&target_world, args.world) {
return;
}
tracing::info!(
"End portal collision at {:?}, targeting world {:?}",
args.position,
target_world.dimension.minecraft_name
);
args.entity
.get_entity()
.try_use_portal(0, target_world, *args.position);
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let nbt = EndPortalBlockEntity::create_nbt(*args.position);
args.world.add_block_entity_nbt(*args.position, &nbt);
fn placed(&self, args: PlacedArgs<'_>) {
let nbt = EndPortalBlockEntity::create_nbt(*args.position);
args.world.add_block_entity_nbt(*args.position, &nbt);
args.world
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position)));
})
args.world
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position)));
}
}

View File

@@ -3,7 +3,7 @@ use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block;
use crate::{
block::{BlockBehaviour, BlockFuture, OnPlaceArgs},
block::{BlockBehaviour, OnPlaceArgs},
entity::EntityBase,
};
@@ -13,13 +13,10 @@ type EndPortalFrameProperties = pumpkin_data::block_properties::EndPortalFrameLi
pub struct EndPortalFrameBlock;
impl BlockBehaviour for EndPortalFrameBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut end_portal_frame_props = EndPortalFrameProperties::default(args.block);
end_portal_frame_props.facing =
args.player.get_entity().get_horizontal_facing().opposite();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut end_portal_frame_props = EndPortalFrameProperties::default(args.block);
end_portal_frame_props.facing = args.player.get_entity().get_horizontal_facing().opposite();
end_portal_frame_props.to_state_id(args.block)
})
end_portal_frame_props.to_state_id(args.block)
}
}

View File

@@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::{BlockBehaviour, OnPlaceArgs};
use pumpkin_data::Block;
use pumpkin_data::BlockStateId;
@@ -10,24 +9,22 @@ use pumpkin_macros::pumpkin_block;
pub struct EndRodBlock;
impl BlockBehaviour for EndRodBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = EndRodLikeProperties::default(args.block);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = EndRodLikeProperties::default(args.block);
let blockstate = args
.world
.get_block_state_id(&args.position.offset(args.direction.to_offset()));
let blockstate = args
.world
.get_block_state_id(&args.position.offset(args.direction.to_offset()));
if Block::from_state_id(blockstate).eq(args.block)
&& EndRodLikeProperties::from_state_id(blockstate, args.block).facing
== args.direction.to_facing().opposite()
{
props.facing = args.direction.to_facing();
} else {
props.facing = args.direction.to_facing().opposite();
}
if Block::from_state_id(blockstate).eq(args.block)
&& EndRodLikeProperties::from_state_id(blockstate, args.block).facing
== args.direction.to_facing().opposite()
{
props.facing = args.direction.to_facing();
} else {
props.facing = args.direction.to_facing().opposite();
}
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
}

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::block::entities::ender_chest::EnderChestBlockEntity;
use crate::block::{
BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
BlockBehaviour, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
registry::BlockActionResult,
};
use crate::world::World;
@@ -57,77 +57,69 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
pub struct EnderChestBlock;
impl BlockBehaviour for EnderChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = LadderLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = LadderLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
}
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move {
// On the server, we don't need to do more because the client is responsible for that.
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
})
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
// On the server, we don't need to do more because the client is responsible for that.
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if is_chest_blocked(args.world, args.position) {
return BlockActionResult::Success;
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if is_chest_blocked(args.world, args.position) {
return BlockActionResult::Success;
}
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
be
} else {
let be = Arc::new(EnderChestBlockEntity::new(*args.position));
args.world.add_block_entity(be.clone());
be
};
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
be
} else {
let be = Arc::new(EnderChestBlockEntity::new(*args.position));
args.world.add_block_entity(be.clone());
be
};
if let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<EnderChestBlockEntity>()
{
let inventory = args.player.ender_chest_inventory();
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
1,
)
.await;
args.player
if let Some(block_entity) = block_entity
.as_any()
.downcast_ref::<EnderChestBlockEntity>()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
let tracker = block_entity.get_tracker();
tokio::spawn(async move {
let inventory = player.ender_chest_inventory();
player
.open_handled_screen(
&EnderChestScreenFactory {
inventory: inventory.clone(),
tracker: Some(block_entity.get_tracker()),
tracker: Some(tracker),
},
Some(*args.position),
Some(pos),
)
.await;
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
}
});
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
}
BlockActionResult::Success
})
BlockActionResult::Success
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_entity = EnderChestBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let block_entity = EnderChestBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockMetadata, GetStateForNeighborUpdateArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
PlacedArgs,
},
entity::falling::FallingEntity,
};
@@ -29,33 +29,29 @@ impl BlockMetadata for FallingBlock {
}
impl BlockBehaviour for FallingBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
// TODO: make delay configurable
args.world
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal);
})
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
// TODO: make delay configurable
args.world
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal);
args.state_id
})
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);
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let (block, state) = args.world.get_block_and_state(&args.position.down());
if !Self::can_fall_through(state, block) || args.position.0.y < args.world.min_y {
return;
}
let state = args.world.get_block_state(args.position);
FallingEntity::replace_spawn(args.world, *args.position, state.id).await;
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let (block, state) = args.world.get_block_and_state(&args.position.down());
if !Self::can_fall_through(state, block) || args.position.0.y < args.world.min_y {
return;
}
let state = args.world.get_block_state(args.position);
FallingEntity::replace_spawn(args.world, *args.position, state.id);
}
}

View File

@@ -1,7 +1,6 @@
use std::sync::Arc;
use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
@@ -28,123 +27,72 @@ type FarmlandProperties = FarmlandLikeProperties;
pub struct FarmlandBlock;
impl BlockBehaviour for FarmlandBlock {
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// TODO: push up entities
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
// TODO: push up entities
args.world.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
if !can_place_at(args.world, args.position) {
return Block::DIRT.default_state.id;
}
args.block.default_state.id
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
args.world
.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
})
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
return Block::DIRT.default_state.id;
}
args.block.default_state.id
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// TODO: add rain check. Remember to check which one is most optimized.
if is_water_nearby(args.world, args.position) {
let mut props = FarmlandProperties::default(args.block);
props.moisture = 7;
let mut event = crate::plugin::block::moisture_change::MoistureChangeEvent {
block_pos: *args.position,
world: args.world.clone(),
new_moisture: 7,
cancelled: false,
};
if let Some(server) = args.world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
if !event.cancelled {
props.moisture = (event.new_moisture.clamp(0, 7)) as u8;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
fn random_tick(&self, args: RandomTickArgs<'_>) {
// TODO: add rain check. Remember to check which one is most optimized.
if is_water_nearby(args.world, args.position) {
let mut props = FarmlandProperties::default(args.block);
props.moisture = 7;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_NEIGHBORS,
);
} else {
let state_id = args.world.get_block_state_id(args.position);
let mut props = FarmlandProperties::from_state_id(state_id, args.block);
if props.moisture == 0 {
if !args
.world
.get_block(&args.position.up())
.has_tag(&tag::Block::MINECRAFT_MAINTAINS_FARMLAND)
{
//TODO push entities up
args.world.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
}
} else {
let state_id = args.world.get_block_state_id(args.position);
let mut props = FarmlandProperties::from_state_id(state_id, args.block);
if props.moisture == 0 {
if !args
.world
.get_block(&args.position.up())
.has_tag(&tag::Block::MINECRAFT_MAINTAINS_FARMLAND)
{
let mut event =
crate::plugin::api::events::block::block_fade::BlockFadeEvent::new(
*args.position,
&Block::DIRT,
);
if let Some(server) = args.world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
if event.cancelled {
return;
}
//TODO push entities up
args.world
.set_block_state(
args.position,
Block::DIRT.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
}
} else {
let mut event = crate::plugin::block::moisture_change::MoistureChangeEvent {
block_pos: *args.position,
world: args.world.clone(),
new_moisture: (props.moisture as i32) - 1,
cancelled: false,
};
if let Some(server) = args.world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
if !event.cancelled {
props.moisture = (event.new_moisture.clamp(0, 7)) as u8;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
}
}
props.moisture = (props.moisture as i32 - 1).clamp(0, 7) as u8;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_NEIGHBORS,
);
}
})
}
}
}

View File

@@ -3,8 +3,7 @@ use std::sync::Arc;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs,
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
};
use crate::entity::EntityBase;
use crate::entity::player::Player;
@@ -38,7 +37,7 @@ fn get_sound(block: &Block, open: bool) -> Sound {
}
}
pub async fn toggle_fence_gate(
pub fn toggle_fence_gate(
world: &Arc<World>,
block_pos: &BlockPos,
player: &Player,
@@ -68,13 +67,11 @@ pub async fn toggle_fence_gate(
*block_pos,
);
world
.set_block_state(
block_pos,
fence_gate_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
block_pos,
fence_gate_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
fence_gate_props.to_state_id(block)
}
@@ -82,43 +79,39 @@ pub async fn toggle_fence_gate(
pub struct FenceGateBlock;
impl BlockBehaviour for FenceGateBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut fence_gate_props = FenceGateProperties::default(args.block);
fence_gate_props.facing = args.player.get_entity().get_horizontal_facing();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut fence_gate_props = FenceGateProperties::default(args.block);
fence_gate_props.facing = args.player.get_entity().get_horizontal_facing();
let powered = block_receives_redstone_power(args.world, args.position).await;
fence_gate_props.powered = powered;
fence_gate_props.open = powered;
let powered = block_receives_redstone_power(args.world, args.position);
fence_gate_props.powered = powered;
fence_gate_props.open = powered;
fence_gate_props.to_state_id(args.block)
})
fence_gate_props.to_state_id(args.block)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let fence_props = is_in_wall(&args);
fence_props.to_state_id(args.block)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let fence_props = is_in_wall(&args);
fence_props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
toggle_fence_gate(args.world, args.position, args.player).await;
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
toggle_fence_gate(args.world, args.position, args.player);
BlockActionResult::Success
})
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
let block_state = args.world.get_block_state(args.position);
let mut fence_gate_props =
FenceGateProperties::from_state_id(block_state.id, args.block);
let powered = block_receives_redstone_power(args.world, args.position).await;
let powered = block_receives_redstone_power(args.world, args.position);
if powered == fence_gate_props.powered {
return;
@@ -136,14 +129,12 @@ impl BlockBehaviour for FenceGateBlock {
);
}
args.world
.set_block_state(
args.position,
fence_gate_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
})
args.world.set_block_state(
args.position,
fence_gate_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
);
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection;
@@ -22,23 +21,19 @@ use crate::world::World;
pub struct FenceBlock;
impl BlockBehaviour for FenceBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut fence_props = FenceProperties::default(args.block);
fence_props.waterlogged = args.replacing.water_source();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut fence_props = FenceProperties::default(args.block);
fence_props.waterlogged = args.replacing.water_source();
compute_fence_state(fence_props, args.world, args.block, args.position)
})
compute_fence_state(fence_props, args.world, args.block, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let fence_props = FenceProperties::from_state_id(args.state_id, args.block);
compute_fence_state(fence_props, args.world, args.block, args.position)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let fence_props = FenceProperties::from_state_id(args.state_id, args.block);
compute_fence_state(fence_props, args.world, args.block, args.position)
}
}

View File

@@ -15,7 +15,7 @@ use std::sync::Arc;
use crate::block::blocks::tnt::TNTBlock;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::world::World;
@@ -148,7 +148,7 @@ impl FireBlock {
)
}
async fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u8) {
fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u8) {
let block = world.get_block(pos);
let odds = Self::get_burn_odds(block);
if rand::rng().random_range(0..chance) < odds {
@@ -161,93 +161,70 @@ impl FireBlock {
let mut fire_props = FireProperties::from_state_id(state_id, &Block::FIRE);
fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE);
world
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
.await;
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
} else {
let mut burn_event = crate::plugin::block::block_burn::BlockBurnEvent {
igniting_block: &Block::FIRE,
block: old_block,
cancelled: false,
};
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut burn_event).await;
}
if burn_event.cancelled {
return;
}
world
.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
world.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
}
if old_block == &Block::TNT {
TNTBlock::prime(world, pos).await;
TNTBlock::prime(world, pos);
}
}
}
}
impl BlockBehaviour for FireBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.old_state_id == args.state_id {
// Already a fire
return;
}
fn placed(&self, args: PlacedArgs<'_>) {
if args.old_state_id == args.state_id {
// Already a fire
return;
}
let dimension = &args.world.dimension;
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
&& let Some(portal) =
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X)
{
portal.create(args.world).await;
return;
}
let dimension = &args.world.dimension;
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
&& let Some(portal) =
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X)
{
portal.create(args.world);
return;
}
args.world.schedule_block_tick(
args.block,
*args.position,
Self::get_fire_tick_delay() as u8,
TickPriority::Normal,
);
})
args.world.schedule_block_tick(
args.block,
*args.position,
Self::get_fire_tick_delay() as u8,
TickPriority::Normal,
);
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
FireBlockBase::apply_fire_collision(args, false)
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
FireBlockBase::apply_fire_collision(&args, false);
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if self.can_place_at(CanPlaceAtArgs {
server: None,
world: Some(args.world),
block_accessor: args.world,
block: &Block::FIRE,
state: Block::FIRE.default_state,
position: args.position,
direction: None,
player: None,
use_item_on: None,
}) {
let old_fire_props = FireProperties::from_state_id(args.state_id, &Block::FIRE);
let fire_state_id =
self.get_state_for_position(args.world, &Block::FIRE, args.position);
let mut fire_props = FireProperties::from_state_id(fire_state_id, &Block::FIRE);
fire_props.age = old_fire_props.age;
return fire_props.to_state_id(&Block::FIRE);
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if self.can_place_at(CanPlaceAtArgs {
server: None,
world: Some(args.world),
block_accessor: args.world,
block: &Block::FIRE,
state: Block::FIRE.default_state,
position: args.position,
direction: None,
player: None,
use_item_on: None,
}) {
self.get_state_for_position(args.world, args.block, args.position)
} else {
Block::AIR.default_state.id
})
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -259,247 +236,223 @@ impl BlockBehaviour for FireBlock {
}
#[expect(clippy::too_many_lines)]
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let (world, block, pos) = (args.world, args.block, args.position);
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let (world, block, pos) = (args.world, args.block, args.position);
// Schedule next tick first
world.schedule_block_tick(
block,
*pos,
Self::get_fire_tick_delay() as u8,
TickPriority::Normal,
// Schedule next tick first
world.schedule_block_tick(
block,
*pos,
Self::get_fire_tick_delay() as u8,
TickPriority::Normal,
);
// Check if fire can survive
if !self.can_place_at(CanPlaceAtArgs {
server: None,
world: Some(world),
block_accessor: world.as_ref(),
block,
state: block.default_state,
position: pos,
direction: None,
player: None,
use_item_on: None,
}) {
world.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
return;
}
// Check if fire can survive
if !self.can_place_at(CanPlaceAtArgs {
server: None,
world: Some(world),
block_accessor: world.as_ref(),
block,
state: block.default_state,
position: pos,
direction: None,
player: None,
use_item_on: None,
}) {
world
.set_block_state(
let block_state = world.get_block_state(pos);
let block_below = world.get_block(&pos.down());
// Check for infiniburn blocks (depending on dimension)
let infiniburn = match world.dimension.id {
id if id == Dimension::OVERWORLD.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD)
}
id if id == Dimension::THE_NETHER.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER)
}
id if id == Dimension::THE_END.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END)
}
_ => false,
};
let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE);
let age = fire_props.age;
// Check if rain should extinguish the fire
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
let rain_chance = 0.2 + (age as f32) * 0.03;
if rand::random::<f32>() < rain_chance {
world.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
return;
}
}
// Increment age
let random = (rand::rng().random_range(0..3) / 2) as u8;
let new_age = (age + random).min(15);
if new_age != age {
fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE);
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
}
if !infiniburn {
// Check if fire should extinguish due to lack of fuel
if !Self::are_blocks_around_flammable(world.as_ref(), pos) {
let block_below_state = world.get_block_state(&pos.down());
if !block_below_state.is_side_solid(BlockDirection::Up) || new_age > 3 {
world.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
);
return;
}
}
// At max age, fire has a chance to extinguish if not on flammable block
if new_age == 15
&& rand::rng().random_range(0..4) == 0
&& !Self::is_flammable(world.get_block_state(&pos.down()))
{
world.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
return;
}
}
let block_state = world.get_block_state(pos);
let block_below = world.get_block(&pos.down());
// Burn adjacent blocks
let extra = if Self::is_increased_burnout_biome(world, pos) {
-50 // Increases chance of block being destroyed
} else {
0
};
// Check for infiniburn blocks (depending on dimension)
let infiniburn = match world.dimension.id {
id if id == Dimension::OVERWORLD.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD)
}
id if id == Dimension::THE_NETHER.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER)
}
id if id == Dimension::THE_END.id => {
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END)
}
_ => false,
};
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::East.to_offset()),
300 + extra,
new_age,
);
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::West.to_offset()),
300 + extra,
new_age,
);
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::Down.to_offset()),
250 + extra,
new_age,
);
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::Up.to_offset()),
250 + extra,
new_age,
);
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::North.to_offset()),
300 + extra,
new_age,
);
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::South.to_offset()),
300 + extra,
new_age,
);
let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE);
let age = fire_props.age;
// Respect the `fire_spread_radius_around_player` gamerule.
// -1 = disabled (allow unlimited spread), 0 = disabled (no spread), >0 = radius in blocks
let spread_radius = world
.level_info
.load()
.game_rules
.fire_spread_radius_around_player;
// Check if rain should extinguish the fire
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
let rain_chance = 0.2 + (age as f32) * 0.03;
if rand::random::<f32>() < rain_chance {
world
.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
return;
}
}
// Try to spread fire to nearby air blocks
let difficulty = world.level_info.load().difficulty as i32;
for xx in -1..=1 {
for zz in -1..=1 {
for yy in -1..=4 {
if xx != 0 || yy != 0 || zz != 0 {
let offset_pos = pos.offset(Vector3::new(xx, yy, zz));
let ignite_odds = self.get_burn_chance(world, &offset_pos);
// Increment age
let random = (rand::rng().random_range(0..3) / 2) as u8;
let new_age = (age + random).min(15);
if new_age != age {
fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE);
world
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
.await;
}
if !infiniburn {
// Check if fire should extinguish due to lack of fuel
if !Self::are_blocks_around_flammable(world.as_ref(), pos) {
let block_below_state = world.get_block_state(&pos.down());
if !block_below_state.is_side_solid(BlockDirection::Up) || new_age > 3 {
world
.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
return;
}
}
// At max age, fire has a chance to extinguish if not on flammable block
if new_age == 15
&& rand::rng().random_range(0..4) == 0
&& !Self::is_flammable(world.get_block_state(&pos.down()))
{
world
.set_block_state(
pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
return;
}
}
// Burn adjacent blocks
let extra = if Self::is_increased_burnout_biome(world, pos) {
-50 // Increases chance of block being destroyed
} else {
0
};
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::East.to_offset()),
300 + extra,
new_age,
)
.await;
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::West.to_offset()),
300 + extra,
new_age,
)
.await;
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::Down.to_offset()),
250 + extra,
new_age,
)
.await;
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::Up.to_offset()),
250 + extra,
new_age,
)
.await;
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::North.to_offset()),
300 + extra,
new_age,
)
.await;
self.try_spreading_fire(
world,
&pos.offset(BlockDirection::South.to_offset()),
300 + extra,
new_age,
)
.await;
// Respect the `fire_spread_radius_around_player` gamerule.
// -1 = disabled (allow unlimited spread), 0 = disabled (no spread), >0 = radius in blocks
let spread_radius = world
.level_info
.load()
.game_rules
.fire_spread_radius_around_player;
// Try to spread fire to nearby air blocks
let difficulty = world.level_info.load().difficulty as i32;
for xx in -1..=1 {
for zz in -1..=1 {
for yy in -1..=4 {
if xx != 0 || yy != 0 || zz != 0 {
let offset_pos = pos.offset(Vector3::new(xx, yy, zz));
let ignite_odds = self.get_burn_chance(world, &offset_pos);
if ignite_odds > 0 {
// Skip if spreding is disabled or if there are no players nearby
if spread_radius == 0 {
if ignite_odds > 0 {
// Skip if spreding is disabled or if there are no players nearby
if spread_radius == 0 {
continue;
}
if spread_radius != -1 {
let center = offset_pos.to_centered_f64();
if world
.get_closest_player(center, spread_radius as f64)
.is_none()
{
continue;
}
if spread_radius != -1 {
let center = offset_pos.to_centered_f64();
if world
.get_closest_player(center, spread_radius as f64)
.is_none()
{
continue;
}
}
}
// Calculate spread rate based on height
let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 };
// Calculate spread rate based on height
let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 };
// Calculate odds of spreading
let mut odds =
(ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30);
// Calculate odds of spreading
let mut odds =
(ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30);
// Reduce spread odds in certain biomes
if Self::is_increased_burnout_biome(world, &offset_pos) {
odds /= 2; // Fire spreads 50% slower
}
// Reduce spread odds in certain biomes
if Self::is_increased_burnout_biome(world, &offset_pos) {
odds /= 2; // Fire spreads 50% slower
}
if odds > 0
&& rand::rng().random_range(0..rate) <= odds
&& !Self::is_near_rain(world.as_ref(), &offset_pos)
{
let spread_age = (new_age + rand::rng().random_range(0..5) / 4)
.min(15)
as u8;
let fire_state_id = self.get_state_for_position(
world.as_ref(),
block,
&offset_pos,
);
let mut new_fire_props =
FireProperties::from_state_id(fire_state_id, &Block::FIRE);
new_fire_props.age = spread_age;
if odds > 0
&& rand::rng().random_range(0..rate) <= odds
&& !Self::is_near_rain(world.as_ref(), &offset_pos)
{
let spread_age =
(new_age + rand::rng().random_range(0..5) / 4).min(15) as u8;
let fire_state_id =
self.get_state_for_position(world.as_ref(), block, &offset_pos);
let mut new_fire_props =
FireProperties::from_state_id(fire_state_id, &Block::FIRE);
new_fire_props.age = spread_age;
world
.set_block_state(
&offset_pos,
new_fire_props.to_state_id(&Block::FIRE),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
}
world.set_block_state(
&offset_pos,
new_fire_props.to_state_id(&Block::FIRE),
BlockFlags::NOTIFY_NEIGHBORS,
);
}
}
}
}
}
})
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn broken(&self, args: BrokenArgs<'_>) {
{
FireBlockBase::broken(args.world, *args.position);
})
}
}
}

View File

@@ -11,7 +11,7 @@ use rand::RngExt;
use soul_fire::SoulFireBlock;
use crate::block::blocks::fire::fire::FireBlock;
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, OnEntityCollisionArgs};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, OnEntityCollisionArgs};
use crate::entity::EntityBase;
use crate::world::World;
use crate::world::portal::nether::NetherPortal;
@@ -117,46 +117,34 @@ impl FireBlockBase {
}
/// Shared fire collision behavior used by `fire` and `soul_fire`.
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn apply_fire_collision(
args: OnEntityCollisionArgs<'_>,
extra_damage_for_living: bool,
) -> BlockFuture<'_, ()> {
Box::pin(async move {
let base_entity = args.entity.get_entity();
if !base_entity.entity_type.fire_immune
&& !base_entity.fire_immune.load(Ordering::Relaxed)
{
let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
pub fn apply_fire_collision(args: &OnEntityCollisionArgs<'_>, extra_damage_for_living: bool) {
let base_entity = args.entity.get_entity();
if !base_entity.entity_type.fire_immune && !base_entity.fire_immune.load(Ordering::Relaxed)
{
let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
// Timer logic
if ticks < 0 {
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
} else if base_entity.entity_type == &EntityType::PLAYER {
let rnd_ticks = rand::rng().random_range(1..3);
base_entity
.fire_ticks
.store(ticks + rnd_ticks, Ordering::Relaxed);
}
// Apply fire ticks
if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 {
args.entity.set_on_fire_for(8.0);
}
// Regular fire vs soul fire damage
if extra_damage_for_living {
base_entity
.damage(args.entity, 2.0, DamageType::IN_FIRE)
.await;
} else {
base_entity
.damage(args.entity, 1.0, DamageType::IN_FIRE)
.await;
}
// Timer logic
if ticks < 0 {
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
} else if base_entity.entity_type == &EntityType::PLAYER {
let rnd_ticks = rand::rng().random_range(1..3);
base_entity
.fire_ticks
.store(ticks + rnd_ticks, Ordering::Relaxed);
}
})
// Apply fire ticks
if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 {
args.entity.set_on_fire_for(8.0);
}
// Regular fire vs soul fire damage
if extra_damage_for_living {
base_entity.damage(args.entity, 2.0, DamageType::IN_FIRE);
} else {
base_entity.damage(args.entity, 1.0, DamageType::IN_FIRE);
}
}
}
fn broken(world: &World, block_pos: BlockPos) {

View File

@@ -3,9 +3,7 @@ use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
};
use crate::block::{BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
use super::FireBlockBase;
use crate::block::OnEntityCollisionArgs;
@@ -21,30 +19,28 @@ impl SoulFireBlock {
}
impl BlockBehaviour for SoulFireBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
FireBlockBase::apply_fire_collision(args, true)
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
FireBlockBase::apply_fire_collision(&args, true);
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !Self::is_soul_base(args.world.get_block(&args.position.down())) {
return Block::AIR.default_state.id;
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !Self::is_soul_base(args.world.get_block(&args.position.down())) {
return Block::AIR.default_state.id;
}
args.state_id
})
args.state_id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
Self::is_soul_base(args.block_accessor.get_block(&args.position.down()))
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn broken(&self, args: BrokenArgs<'_>) {
{
FireBlockBase::broken(args.world, *args.position);
})
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
use crate::block::{BlockBehaviour, NormalUseArgs};
use pumpkin_macros::pumpkin_block;
@@ -7,7 +7,7 @@ use pumpkin_macros::pumpkin_block;
pub struct FletchingTableBlock;
impl BlockBehaviour for FletchingTableBlock {
fn normal_use<'a>(&'a self, _args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move { BlockActionResult::Pass })
fn normal_use(&self, _args: NormalUseArgs<'_>) -> BlockActionResult {
BlockActionResult::Pass
}
}

View File

@@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, RandomTickArgs, UseWithItemArgs};
use crate::block::{BlockBehaviour, RandomTickArgs, UseWithItemArgs};
use pumpkin_data::dimension::Dimension;
use pumpkin_data::flower_pot_transformations::get_potted_item;
use pumpkin_data::{Block, BlockId};
@@ -10,23 +10,18 @@ use pumpkin_world::world::BlockFlags;
pub struct FlowerPotBlock;
impl BlockBehaviour for FlowerPotBlock {
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
{
let item = args.item_stack.item;
//Place the flower inside the pot
let potted_block_id = get_potted_item(item.id);
if args.block.eq(&Block::FLOWER_POT) {
if potted_block_id != BlockId::AIR {
args.world
.set_block_state(
args.position,
Block::from_id(potted_block_id).default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
Block::from_id(potted_block_id).default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
return BlockActionResult::Success;
} else if potted_block_id != BlockId::AIR {
@@ -35,43 +30,35 @@ impl BlockBehaviour for FlowerPotBlock {
}
//get the flower + empty the pot
args.world
.set_block_state(
args.position,
Block::FLOWER_POT.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
Block::FLOWER_POT.default_state.id,
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::Success
})
}
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
&& args.world.level_time.lock().await.time_of_day % 24000 > 14500
{
args.world
.set_block_state(
args.position,
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
if args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM)
&& args.world.level_time.lock().await.time_of_day % 24000 <= 14500
{
args.world
.set_block_state(
args.position,
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
&& args.world.get_time_of_day() % 24000 > 14500
{
args.world.set_block_state(
args.position,
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
if args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM)
&& args.world.get_time_of_day() % 24000 <= 14500
{
args.world.set_block_state(
args.position,
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
}
}

View File

@@ -20,8 +20,8 @@ use tokio::sync::Mutex;
use crate::{
block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@@ -82,79 +82,70 @@ impl ScreenHandlerFactory for FurnaceScreenFactory {
pub struct FurnaceBlock;
impl BlockBehaviour for FurnaceBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
1,
)
.await;
let furnace_screen_factory =
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
args.player
.open_handled_screen(&furnace_screen_factory, Some(*args.position))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
1,
);
let furnace_screen_factory =
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&furnace_screen_factory, Some(pos))
.await;
});
}
crate::block::registry::BlockActionResult::Consume
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
fn placed(&self, args: PlacedArgs<'_>) {
let furnace_block_entity = FurnaceBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(furnace_block_entity));
}
fn broken(&self, args: BrokenArgs<'_>) {
// Extract and drop accumulated XP as orbs before removing the block entity
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(experience_container) = block_entity.to_experience_container()
{
let xp = experience_container.extract_experience();
if xp > 0 {
let pos = args.position.to_f64();
ExperienceOrbEntity::spawn(args.world, pos, xp as u32);
}
crate::block::registry::BlockActionResult::Consume
})
}
args.world.remove_block_entity(args.position);
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let furnace_block_entity = FurnaceBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(furnace_block_entity));
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Extract and drop accumulated XP as orbs before removing the block entity
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(experience_container) = block_entity.to_experience_container()
{
let xp = experience_container.extract_experience();
if xp > 0 {
let pos = args.position.to_f64();
ExperienceOrbEntity::spawn(args.world, pos, xp as u32).await;
}
}
args.world.remove_block_entity(args.position);
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(
inventory.as_ref(),
))
} else {
None
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection;
@@ -20,23 +19,19 @@ use crate::world::World;
pub struct GlassPaneBlock;
impl BlockBehaviour for GlassPaneBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut pane_props = GlassPaneProperties::default(args.block);
pane_props.waterlogged = args.replacing.water_source();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut pane_props = GlassPaneProperties::default(args.block);
pane_props.waterlogged = args.replacing.water_source();
compute_pane_state(pane_props, args.world, args.block, args.position)
})
compute_pane_state(pane_props, args.world, args.block, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block);
compute_pane_state(pane_props, args.world, args.block, args.position)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block);
compute_pane_state(pane_props, args.world, args.block, args.position)
}
}

View File

@@ -1,4 +1,4 @@
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs};
use crate::block::{BlockBehaviour, OnPlaceArgs};
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_macros::pumpkin_block_from_tag;
@@ -7,16 +7,14 @@ use pumpkin_macros::pumpkin_block_from_tag;
pub struct GlazedTerracottaBlock;
impl BlockBehaviour for GlazedTerracottaBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut prop = WallTorchLikeProperties::default(args.block);
prop.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
prop.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut prop = WallTorchLikeProperties::default(args.block);
prop.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
prop.to_state_id(args.block)
}
}

View File

@@ -17,7 +17,7 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs};
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs};
#[pumpkin_block("minecraft:grass_block")]
pub struct GrassBlock;
@@ -30,139 +30,116 @@ impl BlockBehaviour for GrassBlock {
&& args.world.get_block_state(&above).is_air()
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
const SPREAD_ATTEMPTS: i32 = 128;
const ATTEMPTS_PER_STEP: i32 = 16;
const FLOWER_CHANCE: i32 = 8;
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
const SPREAD_ATTEMPTS: i32 = 128;
const ATTEMPTS_PER_STEP: i32 = 16;
const FLOWER_CHANCE: i32 = 8;
let origin = args.position.up();
for attempt in 0..SPREAD_ATTEMPTS {
let mut target = origin;
let mut valid = true;
for _ in 0..attempt / ATTEMPTS_PER_STEP {
let offset_x = rand::rng().random_range(0..3) - 1;
let offset_y =
((rand::rng().random_range(0..3) - 1) * rand::rng().random_range(0..3)) / 2;
let offset_z = rand::rng().random_range(0..3) - 1;
target = BlockPos::new(
target.0.x + offset_x,
target.0.y + offset_y,
target.0.z + offset_z,
);
let origin = args.position.up();
for attempt in 0..SPREAD_ATTEMPTS {
let mut target = origin;
let mut valid = true;
for _ in 0..attempt / ATTEMPTS_PER_STEP {
let offset_x = rand::rng().random_range(0..3) - 1;
let offset_y =
((rand::rng().random_range(0..3) - 1) * rand::rng().random_range(0..3)) / 2;
let offset_z = rand::rng().random_range(0..3) - 1;
target = BlockPos::new(
target.0.x + offset_x,
target.0.y + offset_y,
target.0.z + offset_z,
);
if !args.world.is_loaded(&target)
|| args.world.get_block(&target.down()) != args.block
|| args.world.get_block_state(&target).is_full_cube()
{
valid = false;
break;
}
if !args.world.is_loaded(&target)
|| args.world.get_block(&target.down()) != args.block
|| args.world.get_block_state(&target).is_full_cube()
{
valid = false;
break;
}
}
if !valid {
if !valid {
continue;
}
let target_state = args.world.get_block_state(&target);
if Block::from_state_id(target_state.id) == &Block::SHORT_GRASS
&& rand::rng().random_range(0..10) == 0
{
let above = target.up();
if args.world.is_in_height_limit(above.0.y)
&& args.world.is_loaded(&above)
&& args.world.get_block_state(&above).is_air()
{
place_tall_grass(args.world, target);
}
} else if target_state.is_air() && args.world.is_in_height_limit(target.0.y) {
let selected = if rand::rng().random_range(0..FLOWER_CHANCE) == 0 {
biome_bonemeal_state(args.world, target)
} else {
Some((Block::SHORT_GRASS.default_state, false))
};
let Some((state, schedule_tick)) = selected else {
continue;
};
let placed_block = Block::from_state_id(state.id);
if !args.world.block_registry.can_place_at(
None,
Some(args.world),
args.world.as_ref(),
None,
placed_block,
state,
&target,
None,
None,
) {
continue;
}
let target_state = args.world.get_block_state(&target);
if Block::from_state_id(target_state.id) == &Block::SHORT_GRASS
&& rand::rng().random_range(0..10) == 0
{
let above = target.up();
if args.world.is_in_height_limit(above.0.y)
&& args.world.is_loaded(&above)
&& args.world.get_block_state(&above).is_air()
{
place_tall_grass(args.world, target).await;
}
} else if target_state.is_air() && args.world.is_in_height_limit(target.0.y) {
let selected = if rand::rng().random_range(0..FLOWER_CHANCE) == 0 {
biome_bonemeal_state(args.world, target)
} else {
Some((Block::SHORT_GRASS.default_state, false))
};
let Some((state, schedule_tick)) = selected else {
continue;
};
let placed_block = Block::from_state_id(state.id);
if !args.world.block_registry.can_place_at(
None,
Some(args.world),
args.world.as_ref(),
None,
placed_block,
state,
&target,
None,
None,
) {
continue;
}
if placed_block == &Block::TALL_GRASS
&& (!args.world.is_loaded(&target.up())
|| !args.world.get_block_state(&target.up()).is_air())
{
continue;
}
args.world
.set_block_state(&target, state.id, BlockFlags::NOTIFY_LISTENERS);
if schedule_tick {
args.world
.set_block_state(&target, state.id, BlockFlags::NOTIFY_LISTENERS)
.await;
if schedule_tick {
args.world.schedule_block_tick(
placed_block,
target,
1,
TickPriority::Normal,
);
}
if placed_block == &Block::TALL_GRASS {
place_tall_grass_upper(args.world, target, state.id).await;
}
.schedule_block_tick(placed_block, target, 1, TickPriority::Normal);
}
}
})
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let block_above = args.world.get_block(&args.position.up());
let mut props =
GrassBlockLikeProperties::from_state_id(args.state_id, &Block::GRASS_BLOCK);
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
if props.snowy == should_be_snowy {
return args.state_id;
}
props.snowy = should_be_snowy;
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let block_above = args.world.get_block(&args.position.up());
let mut props = GrassBlockLikeProperties::from_state_id(args.state_id, &Block::GRASS_BLOCK);
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
if props.snowy == should_be_snowy {
return args.state_id;
}
props.snowy = should_be_snowy;
props.to_state_id(&Block::GRASS_BLOCK)
})
props.to_state_id(&Block::GRASS_BLOCK)
}
}
async fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) {
fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) {
let state = Block::TALL_GRASS.default_state.id;
world
.set_block_state(&position, state, BlockFlags::NOTIFY_LISTENERS)
.await;
place_tall_grass_upper(world, position, state).await;
world.set_block_state(&position, state, BlockFlags::NOTIFY_LISTENERS);
place_tall_grass_upper(world, position, state);
}
async fn place_tall_grass_upper(
fn place_tall_grass_upper(
world: &std::sync::Arc<crate::world::World>,
position: BlockPos,
lower_state: BlockStateId,
) {
let mut props = TallSeagrassLikeProperties::from_state_id(lower_state, &Block::TALL_GRASS);
props.half = DoubleBlockHalf::Upper;
world
.set_block_state(
&position.up(),
props.to_state_id(&Block::TALL_GRASS),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
&position.up(),
props.to_state_id(&Block::TALL_GRASS),
BlockFlags::NOTIFY_LISTENERS,
);
}
fn biome_bonemeal_state(

View File

@@ -6,8 +6,8 @@ use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockAccessor;
use crate::block::BlockBehaviour;
use crate::block::CanPlaceAtArgs;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::block::{GetStateForNeighborUpdateArgs, OnPlaceArgs};
use super::abstract_wall_mounting::WallMountedBlock;
@@ -16,15 +16,13 @@ use super::abstract_wall_mounting::WallMountedBlock;
pub struct GrindstoneBlock;
impl BlockBehaviour for GrindstoneBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
GrindstoneLikeProperties::from_state_id(args.block.default_state.id, args.block);
(props.face, props.facing) =
WallMountedBlock::get_placement_face(self, args.player, args.direction);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
GrindstoneLikeProperties::from_state_id(args.block.default_state.id, args.block);
(props.face, props.facing) =
WallMountedBlock::get_placement_face(self, args.player, args.direction);
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -36,19 +34,19 @@ impl BlockBehaviour for GrindstoneBlock {
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, direction)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move { WallMountedBlock::get_state_for_neighbor_update(self, args).await })
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
WallMountedBlock::get_state_for_neighbor_update(self, args)
}
}
impl WallMountedBlock for GrindstoneBlock {
fn can_place_at<'a>(
&'a self,
_world: &'a dyn BlockAccessor,
_pos: &'a BlockPos,
fn can_place_at(
&self,
_world: &dyn BlockAccessor,
_pos: &BlockPos,
_direction: BlockDirection,
) -> bool {
true

View File

@@ -1,18 +1,14 @@
use pumpkin_macros::pumpkin_block;
use crate::block::{BlockBehaviour, BlockFuture, OnLandedUponArgs};
use crate::block::{BlockBehaviour, OnLandedUponArgs};
#[pumpkin_block("minecraft:hay_block")]
pub struct HayBlock;
impl BlockBehaviour for HayBlock {
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, 0.2)
.await;
}
})
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, 0.2);
}
}
}

View File

@@ -1,9 +1,7 @@
use std::sync::Arc;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{
BlockFuture, GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
};
use crate::block::{GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@@ -58,93 +56,76 @@ pub struct HopperBlock;
type HopperLikeProperties = pumpkin_data::block_properties::HopperLikeProperties;
impl BlockBehaviour for HopperBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
1,
)
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(pos))
.await;
args.player
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(*args.position))
.await;
}
});
}
BlockActionResult::Success
})
BlockActionResult::Success
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = HopperLikeProperties::default(args.block);
props.facing = match args.direction {
BlockDirection::North => FacingHopper::North,
BlockDirection::East => FacingHopper::East,
BlockDirection::South => FacingHopper::South,
BlockDirection::West => FacingHopper::West,
BlockDirection::Up | BlockDirection::Down => FacingHopper::Down,
};
props.enabled = true;
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = HopperLikeProperties::default(args.block);
props.facing = match args.direction {
BlockDirection::North => FacingHopper::North,
BlockDirection::East => FacingHopper::East,
BlockDirection::South => FacingHopper::South,
BlockDirection::West => FacingHopper::West,
BlockDirection::Up | BlockDirection::Down => FacingHopper::Down,
};
props.enabled = true;
props.to_state_id(args.block)
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let props = HopperLikeProperties::from_state_id(args.state_id, args.block);
let hopper_block_entity = HopperBlockEntity::new(*args.position, props.facing);
args.world.add_block_entity(Arc::new(hopper_block_entity));
if Block::from_state_id(args.old_state_id) != Block::from_state_id(args.state_id) {
check_powered_state(args.world, args.position, args.state_id, args.block).await;
}
})
fn placed(&self, args: PlacedArgs<'_>) {
let props = HopperLikeProperties::from_state_id(args.state_id, args.block);
let hopper_block_entity = HopperBlockEntity::new(*args.position, props.facing);
args.world.add_block_entity(Arc::new(hopper_block_entity));
if Block::from_state_id(args.old_state_id) != Block::from_state_id(args.state_id) {
check_powered_state(args.world, args.position, args.state_id, args.block);
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
check_powered_state(
args.world,
args.position,
args.world.get_block_state_id(args.position),
args.block,
)
.await;
})
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
check_powered_state(
args.world,
args.position,
args.world.get_block_state_id(args.position),
args.block,
);
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
} else {
None
}
})
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
Some(crate::block::calculate_comparator_output(
inventory.as_ref(),
))
} else {
None
}
}
}
async fn check_powered_state(
world: &Arc<World>,
pos: &BlockPos,
state_id: BlockStateId,
block: &Block,
) {
let signal = !block_receives_redstone_power(world, pos).await;
fn check_powered_state(world: &Arc<World>, pos: &BlockPos, state_id: BlockStateId, block: &Block) {
let signal = !block_receives_redstone_power(world, pos);
let mut state = HopperLikeProperties::from_state_id(state_id, block);
if signal != state.enabled {
state.enabled = signal;
world
.set_block_state(pos, state.to_state_id(block), BlockFlags::NOTIFY_LISTENERS)
.await;
world.set_block_state(pos, state.to_state_id(block), BlockFlags::NOTIFY_LISTENERS);
}
}

View File

@@ -9,26 +9,21 @@ use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs,
OnScheduledTickArgs, PlacedArgs, RandomTickArgs,
BlockBehaviour, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnScheduledTickArgs,
PlacedArgs, RandomTickArgs,
};
use crate::world::World;
/// Melts ice at the given position into water (or removes it in ultrawarm dimensions like the Nether).
pub async fn melt(world: &Arc<World>, position: &BlockPos) {
pub fn melt(world: &Arc<World>, position: &BlockPos) {
if world.dimension == Dimension::THE_NETHER {
world
.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
} else {
world
.set_block_state(
position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
world.update_neighbors(position, None).await;
world.set_block_state(
position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
}
@@ -47,20 +42,14 @@ pub fn fewer_neighbors_than(world: &World, pos: &BlockPos, limit: usize) -> bool
true
}
/// Slightly melts the frosted ice at `pos`.
///
/// Increments its age if `age < 3`, or completely melts it if `age >= 3`.
/// Returns `true` if it completely melted, or `false` if it just aged.
async fn slightly_melt(world: &Arc<World>, pos: &BlockPos, block: &Block, age: u8) -> bool {
fn slightly_melt(world: &Arc<World>, pos: &BlockPos, block: &Block, age: u8) -> bool {
if age < 3 {
let mut new_props = NetherWartLikeProperties::default(block);
new_props.r#age = age + 1;
world
.set_block_state(pos, new_props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(pos, new_props.to_state_id(block), BlockFlags::NOTIFY_ALL);
false
} else {
melt(world, pos).await;
melt(world, pos);
true
}
}
@@ -74,15 +63,17 @@ impl BlockMetadata for IceBlock {
}
impl BlockBehaviour for IceBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let held_item = args.player.inventory().held_item().await;
fn broken(&self, args: BrokenArgs<'_>) {
{
let held_item = args.player.inventory().held_item();
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
if !has_silk_touch {
if args.world.dimension == Dimension::THE_NETHER {
args.world
.set_block_state(args.position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
.await;
args.world.set_block_state(
args.position,
BlockStateId::AIR,
BlockFlags::NOTIFY_ALL,
);
return;
}
@@ -93,26 +84,22 @@ impl BlockBehaviour for IceBlock {
|| below_state.is_liquid()
|| below_state.is_solid()
{
args.world
.set_block_state(
args.position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
Block::WATER.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
}
})
}
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
let block_light = args.world.get_block_light_level(args.position).unwrap_or(0);
if block_light > (11u8.saturating_sub(state.opacity)) {
melt(args.world, args.position).await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
let state = args.world.get_block_state(args.position);
let block_light = args.world.get_block_light_level(args.position).unwrap_or(0);
if block_light > (11u8.saturating_sub(state.opacity)) {
melt(args.world, args.position);
}
}
}
@@ -125,85 +112,75 @@ impl BlockMetadata for FrostedIceBlock {
}
impl BlockBehaviour for FrostedIceBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let delay = rand::rng().random_range(60..=120);
args.world
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let should_check_melt = rand::rng().random_range(0..3) == 0
|| fewer_neighbors_than(args.world, args.position, 4);
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let should_check_melt = rand::rng().random_range(0..3) == 0
|| fewer_neighbors_than(args.world, args.position, 4);
if should_check_melt {
let state_id = args.world.get_block_state_id(args.position);
let state = args.world.get_block_state(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
let age = props.r#age;
if should_check_melt {
let state_id = args.world.get_block_state_id(args.position);
let state = args.world.get_block_state(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
let age = props.r#age;
let brightness = if args.world.dimension == Dimension::THE_END {
args.world.get_block_light_level(args.position).unwrap_or(0)
} else {
args.world.get_max_local_raw_brightness(args.position)
};
let brightness = if args.world.dimension == Dimension::THE_END {
args.world.get_block_light_level(args.position).unwrap_or(0)
} else {
args.world.get_max_local_raw_brightness(args.position)
};
let threshold = 11u8.saturating_sub(age).saturating_sub(state.opacity);
if brightness > threshold
&& slightly_melt(args.world, args.position, args.block, age).await
{
for dir in BlockDirection::all() {
let neighbor_pos = args.position.offset(dir.to_offset());
let (neighbor_block, neighbor_state_id) =
args.world.get_block_and_state_id(&neighbor_pos);
if neighbor_block == &Block::FROSTED_ICE {
let neighbor_props = NetherWartLikeProperties::from_state_id(
neighbor_state_id,
let threshold = 11u8.saturating_sub(age).saturating_sub(state.opacity);
if brightness > threshold && slightly_melt(args.world, args.position, args.block, age) {
for dir in BlockDirection::all() {
let neighbor_pos = args.position.offset(dir.to_offset());
let (neighbor_block, neighbor_state_id) =
args.world.get_block_and_state_id(&neighbor_pos);
if neighbor_block == &Block::FROSTED_ICE {
let neighbor_props = NetherWartLikeProperties::from_state_id(
neighbor_state_id,
neighbor_block,
);
if !slightly_melt(
args.world,
&neighbor_pos,
neighbor_block,
neighbor_props.r#age,
) {
let delay = rand::rng().random_range(20..=40);
args.world.schedule_block_tick(
neighbor_block,
neighbor_pos,
delay,
TickPriority::Normal,
);
if !slightly_melt(
args.world,
&neighbor_pos,
neighbor_block,
neighbor_props.r#age,
)
.await
{
let delay = rand::rng().random_range(20..=40);
args.world.schedule_block_tick(
neighbor_block,
neighbor_pos,
delay,
TickPriority::Normal,
);
}
}
}
return;
}
return;
}
}
let delay = rand::rng().random_range(20..=40);
args.world
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
})
let delay = rand::rng().random_range(20..=40);
args.world
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.source_block == &Block::FROSTED_ICE
&& fewer_neighbors_than(args.world, args.position, 2)
{
melt(args.world, args.position).await;
}
})
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
if args.source_block == &Block::FROSTED_ICE
&& fewer_neighbors_than(args.world, args.position, 2)
{
melt(args.world, args.position);
}
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
IceBlock.broken(args).await;
})
fn broken(&self, args: BrokenArgs<'_>) {
IceBlock.broken(args);
}
}

View File

@@ -4,16 +4,16 @@ use pumpkin_data::entity::EntityType;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::GameMode;
use crate::block::BlockBehaviour;
use crate::block::BrokenArgs;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::entity::Entity;
#[pumpkin_block_from_tag("c:cobblestones/infested")]
pub struct InfestedBlock;
impl BlockBehaviour for InfestedBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {
fn broken(&self, args: BrokenArgs<'_>) {
{
// TODO: ugly fix, use onStacksDropped
if args.player.gamemode.load() == GameMode::Creative {
return;
@@ -24,7 +24,7 @@ impl BlockBehaviour for InfestedBlock {
&EntityType::SILVERFISH,
);
args.world.spawn_entity(Arc::new(entity)).await;
})
args.world.spawn_entity(Arc::new(entity));
}
}
}

View File

@@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection;
@@ -20,23 +19,19 @@ use crate::world::World;
pub struct IronBarsBlock;
impl BlockBehaviour for IronBarsBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut bars_props = IronBarsProperties::default(args.block);
bars_props.waterlogged = args.replacing.water_source();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut bars_props = IronBarsProperties::default(args.block);
bars_props.waterlogged = args.replacing.water_source();
compute_bars_state(bars_props, args.world, args.block, args.position)
})
compute_bars_state(bars_props, args.world, args.block, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block);
compute_bars_state(bars_props, args.world, args.block, args.position)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block);
compute_bars_state(bars_props, args.world, args.block, args.position)
}
}

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::block::entities::jigsaw_block::JigsawBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs, PlacedArgs};
use crate::entity::EntityBase;
use pumpkin_data::block_properties::{
BlockProperties, HorizontalFacing, JigsawLikeProperties, Orientation,
@@ -100,24 +100,21 @@ impl JigsawBlock {
}
impl BlockBehaviour for JigsawBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = JigsawLikeProperties::default(args.block);
let front = args.direction;
let top = if front == BlockDirection::Up || front == BlockDirection::Down {
horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing())
.opposite()
} else {
BlockDirection::Up
};
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = JigsawLikeProperties::default(args.block);
let front = args.direction;
let top = if front == BlockDirection::Up || front == BlockDirection::Down {
horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing()).opposite()
} else {
BlockDirection::Up
};
props.r#orientation = Self::from_front_top(front, top);
props.to_state_id(args.block)
})
props.r#orientation = Self::from_front_top(front, top);
props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
{
if args.player.permission_lvl.load() < PermissionLvl::Two {
return BlockActionResult::Pass;
}
@@ -129,14 +126,14 @@ impl BlockBehaviour for JigsawBlock {
};
args.world.update_block_entity(&block_entity);
BlockActionResult::SuccessServer
})
}
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let entity = JigsawBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity));
})
}
}
fn mirror(

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::block::entities::jukebox::JukeboxBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
GetRedstonePowerArgs, NormalUseArgs, OnStateReplacedArgs, PlacedArgs, UseWithItemArgs,
};
use crate::entity::Entity;
@@ -33,29 +33,22 @@ impl JukeboxBlock {
JukeboxLikeProperties::from_state_id(state_id, block).has_record
}
async fn set_record_state(
has_record: bool,
block: &Block,
position: &BlockPos,
world: &Arc<World>,
) {
fn set_record_state(has_record: bool, block: &Block, position: &BlockPos, world: &Arc<World>) {
let new_state = JukeboxLikeProperties { has_record };
world
.set_block_state(
position,
new_state.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
position,
new_state.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
);
}
/// Drops the record from the jukebox - matches vanilla's `JukeboxBlockEntity.dropRecord()`
/// Spawns item at (pos + 0.5, pos + 1.01, pos + 0.5) with horizontal random offset
async fn drop_record(position: &BlockPos, world: &Arc<World>) {
fn drop_record(position: &BlockPos, world: &Arc<World>) {
if let Some(block_entity) = world.get_block_entity(position)
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
{
let record = jukebox_entity.clear_record().await;
let record = jukebox_entity.clear_record();
if !record.is_empty() {
// Vanilla: Vec3d.add(pos, 0.5, 1.01, 0.5).addHorizontalRandom(random, 0.7F)
// addHorizontalRandom adds random in range [-0.35, 0.35] to x and z
@@ -68,14 +61,14 @@ impl JukeboxBlock {
let entity = Entity::new(world.clone(), spawn_pos, &EntityType::ITEM);
// Vanilla: setToDefaultPickupDelay() = 10 ticks
let item_entity = Arc::new(ItemEntity::new(entity, record));
world.spawn_entity(item_entity).await;
world.spawn_entity(item_entity);
}
}
}
/// Stops the music and updates block state
async fn stop_playing(block: &Block, position: &BlockPos, world: &Arc<World>) {
Self::set_record_state(false, block, position, world).await;
fn stop_playing(block: &Block, position: &BlockPos, world: &Arc<World>) {
Self::set_record_state(false, block, position, world);
world.sync_world_event(WorldEvent::SoundStopJukeboxSong, *position, 0);
}
@@ -87,168 +80,137 @@ impl JukeboxBlock {
impl BlockBehaviour for JukeboxBlock {
/// Called when the jukebox is placed - creates the block entity
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_entity = JukeboxBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let block_entity = JukeboxBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
}
/// Called when player right-clicks with empty hand or non-disc item
/// Vanilla: `JukeboxBlock.onUse()` - drops record if present
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state(args.position).id;
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let state_id = args.world.get_block_state(args.position).id;
// Vanilla: if (state.get(HAS_RECORD) && world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv)
if Self::has_record_state(args.block, state_id) {
// Drop the record
Self::drop_record(args.position, args.world).await;
// Stop the music and update block state
Self::stop_playing(args.block, args.position, args.world).await;
return BlockActionResult::Success;
}
// Vanilla: if (state.get(HAS_RECORD) && world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv)
if Self::has_record_state(args.block, state_id) {
// Drop the record
Self::drop_record(args.position, args.world);
// Stop the music and update block state
Self::stop_playing(args.block, args.position, args.world);
return BlockActionResult::Success;
}
BlockActionResult::Pass
})
BlockActionResult::Pass
}
/// Called when player right-clicks with an item
/// Vanilla: `JukeboxBlock.onUseWithItem()` -> `JukeboxPlayableComponent.tryPlayStack()`
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let world = args.world;
let state_id = world.get_block_state(args.position).id;
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let world = args.world;
let state_id = world.get_block_state(args.position).id;
// Vanilla: if (state.get(HAS_RECORD)) return PASS_TO_DEFAULT_BLOCK_ACTION
if Self::has_record_state(args.block, state_id) {
return BlockActionResult::PassToDefaultBlockAction;
}
// Vanilla: if (state.get(HAS_RECORD)) return PASS_TO_DEFAULT_BLOCK_ACTION
if Self::has_record_state(args.block, state_id) {
return BlockActionResult::PassToDefaultBlockAction;
}
let item_stack = &mut *args.item_stack;
let item_stack = &mut *args.item_stack;
// Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
let jukebox_playable = item_stack
.get_data_component::<JukeboxPlayableImpl>()
.map(|i| i.song);
// Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
let jukebox_playable = item_stack
.get_data_component::<JukeboxPlayableImpl>()
.map(|i| i.song);
// Vanilla: if (lv == null) return PASS_TO_DEFAULT_BLOCK_ACTION
let Some(jukebox_playable) = jukebox_playable else {
return BlockActionResult::PassToDefaultBlockAction;
};
// Vanilla: if (lv == null) return PASS_TO_DEFAULT_BLOCK_ACTION
let Some(jukebox_playable) = jukebox_playable else {
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(song_name) = jukebox_playable.split(':').nth(1) else {
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(song_name) = jukebox_playable.split(':').nth(1) else {
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(jukebox_song) = JukeboxSong::from_name(song_name) else {
error!("Jukebox playable song not registered: {song_name}");
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(jukebox_song) = JukeboxSong::from_name(song_name) else {
error!("Jukebox playable song not registered: {song_name}");
return BlockActionResult::PassToDefaultBlockAction;
};
// Vanilla: ItemStack lv3 = stack.splitUnlessCreative(1, player)
let record = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
// Vanilla: ItemStack lv3 = stack.splitUnlessCreative(1, player)
let record = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
// Vanilla: lv4.setStack(lv3)
if let Some(block_entity) = world.get_block_entity(args.position)
&& let Some(jukebox_entity) =
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
{
jukebox_entity.set_record(record).await;
// Start tracking playback with song duration
jukebox_entity.start_playing(jukebox_song.length_in_ticks());
}
// Vanilla: lv4.setStack(lv3)
if let Some(block_entity) = world.get_block_entity(args.position)
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
{
jukebox_entity.set_record(record);
// Start tracking playback with song duration
jukebox_entity.start_playing(jukebox_song.length_in_ticks());
}
// Update block state to has_record = true
Self::set_record_state(true, args.block, args.position, world).await;
// Update block state to has_record = true
Self::set_record_state(true, args.block, args.position, world);
// Start playing the music (client-side audio)
Self::start_playing(args.position, world, jukebox_song.get_id());
// Start playing the music (client-side audio)
Self::start_playing(args.position, world, jukebox_song.get_id());
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::PlayRecord as i32,
1,
)
.await;
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::PlayRecord as i32,
1,
);
// TODO: world.emitGameEvent(GameEvent.BLOCK_CHANGE, pos, ...)
// TODO: world.emitGameEvent(GameEvent.BLOCK_CHANGE, pos, ...)
BlockActionResult::Success
})
BlockActionResult::Success
}
/// Called when the jukebox is broken
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Drop the record if there is one
Self::drop_record(args.position, args.world).await;
// Stop the music
args.world
.sync_world_event(WorldEvent::SoundStopJukeboxSong, *args.position, 0);
})
fn broken(&self, args: BrokenArgs<'_>) {
// Drop the record if there is one
Self::drop_record(args.position, args.world);
// Stop the music
args.world
.sync_world_event(WorldEvent::SoundStopJukeboxSong, *args.position, 0);
}
/// Vanilla: `JukeboxBlock.onStateReplaced()` -> `ItemScatterer.onStateReplaced()`
fn on_state_replaced<'a>(&'a self, _args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Vanilla calls ItemScatterer.onStateReplaced which updates comparators
// TODO: world.updateComparators(pos, block) when implemented
})
fn on_state_replaced(&self, _args: OnStateReplacedArgs<'_>) {
// Vanilla calls ItemScatterer.onStateReplaced which updates comparators
// TODO: world.updateComparators(pos, block) when implemented
}
/// Vanilla: `JukeboxBlock.emitsRedstonePower()` returns true
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { true })
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
true
}
/// Vanilla: Returns 15 if playing, 0 otherwise
fn get_weak_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv && lv.getManager().isPlaying() ? 15 : 0
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(jukebox_entity) =
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
&& jukebox_entity.is_playing()
{
15
} else {
0
}
})
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv && lv.getManager().isPlaying() ? 15 : 0
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
&& jukebox_entity.is_playing()
{
15
} else {
0
}
}
/// Vanilla: Returns the song's comparator output (0-15)
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv ? lv.getComparatorOutput() : 0
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(jukebox_entity) =
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv ? lv.getComparatorOutput() : 0
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
{
let record = jukebox_entity.get_record();
// Get the song from the record's jukebox_playable component
if let Some(playable) = record.get_data_component::<JukeboxPlayableImpl>()
&& let Some(song_name) = playable.song.split(':').nth(1)
&& let Some(song) = JukeboxSong::from_name(song_name)
{
let record = jukebox_entity.get_record().await;
// Get the song from the record's jukebox_playable component
if let Some(playable) = record.get_data_component::<JukeboxPlayableImpl>()
&& let Some(song_name) = playable.song.split(':').nth(1)
&& let Some(song) = JukeboxSong::from_name(song_name)
{
return Some(song.comparator_output());
}
return Some(song.comparator_output());
}
Some(0)
})
}
Some(0)
}
}

View File

@@ -1,6 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
};
use crate::entity::EntityBase;
use crate::world::World;
@@ -16,40 +15,37 @@ use pumpkin_world::tick::TickPriority;
pub struct LadderBlock;
impl BlockBehaviour for LadderBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let clicked_pos = args.use_item_on.position;
let (clicked_block, clicked_block_state_id) =
args.world.get_block_and_state_id(&clicked_pos);
if clicked_block == &Block::LADDER {
//you can't click on a ladder and place a ladder
let props =
LadderLikeProperties::from_state_id(clicked_block_state_id, clicked_block);
let sub = args.position.0.sub(&clicked_pos.0);
if let Some(dir) = horizontal_facing_from_offset(sub)
&& let Some(horizontal_facing) = dir.to_horizontal_facing()
&& props.facing == horizontal_facing
{
return Block::AIR.default_state.id;
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let clicked_pos = args.use_item_on.position;
let (clicked_block, clicked_block_state_id) =
args.world.get_block_and_state_id(&clicked_pos);
if clicked_block == &Block::LADDER {
//you can't click on a ladder and place a ladder
let props = LadderLikeProperties::from_state_id(clicked_block_state_id, clicked_block);
let sub = args.position.0.sub(&clicked_pos.0);
if let Some(dir) = horizontal_facing_from_offset(sub)
&& let Some(horizontal_facing) = dir.to_horizontal_facing()
&& props.facing == horizontal_facing
{
return Block::AIR.default_state.id;
}
let mut props = LadderLikeProperties::default(args.block);
}
let mut props = LadderLikeProperties::default(args.block);
let directions = args.player.get_entity().get_entity_facing_order();
for dir in directions {
if dir == Facing::Up || dir == Facing::Down {
continue;
}
if !can_place_ladder_at(args.world, args.position, dir.to_block_direction()) {
continue;
}
if let Some(facing) = dir.opposite().to_horizontal_facing() {
props.facing = facing;
return props.to_state_id(args.block);
}
let directions = args.player.get_entity().get_entity_facing_order();
for dir in directions {
if dir == Facing::Up || dir == Facing::Down {
continue;
}
Block::AIR.default_state.id
})
if !can_place_ladder_at(args.world, args.position, dir.to_block_direction()) {
continue;
}
if let Some(facing) = dir.opposite().to_horizontal_facing() {
props.facing = facing;
return props.to_state_id(args.block);
}
}
Block::AIR.default_state.id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
for dir in BlockDirection::horizontal() {
@@ -63,41 +59,37 @@ impl BlockBehaviour for LadderBlock {
}
false
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = LadderLikeProperties::from_state_id(args.state_id, args.block);
if props.facing.to_block_direction().opposite() == args.direction
&& !can_place_ladder_at(
args.world,
args.position,
props.facing.to_block_direction().opposite(),
)
{
return BlockStateId::AIR;
}
args.state_id
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
if Block::from_state_id(state_id) != &Block::LADDER {
return;
}
let props = LadderLikeProperties::from_state_id(state_id, args.block);
if !can_place_ladder_at(
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let props = LadderLikeProperties::from_state_id(args.state_id, args.block);
if props.facing.to_block_direction().opposite() == args.direction
&& !can_place_ladder_at(
args.world,
args.position,
props.facing.to_block_direction().opposite(),
) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
})
)
{
return BlockStateId::AIR;
}
args.state_id
}
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let state_id = args.world.get_block_state_id(args.position);
if Block::from_state_id(state_id) != &Block::LADDER {
return;
}
let props = LadderLikeProperties::from_state_id(state_id, args.block);
if !can_place_ladder_at(
args.world,
args.position,
props.facing.to_block_direction().opposite(),
) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
}
}
#[must_use]

View File

@@ -1,6 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
};
use crate::world::World;
use pumpkin_data::BlockStateId;
@@ -16,19 +15,16 @@ use pumpkin_world::world::BlockFlags;
pub struct LanternBlock;
impl BlockBehaviour for LanternBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
pumpkin_data::block_properties::LanternLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = pumpkin_data::block_properties::LanternLikeProperties::default(args.block);
props.r#waterlogged = args.replacing.water_source();
let block_up_state = args.world.get_block_state(&args.position.up());
if block_up_state.is_center_solid(BlockDirection::Down) {
props.r#hanging = true;
}
let block_up_state = args.world.get_block_state(&args.position.up());
if block_up_state.is_center_solid(BlockDirection::Down) {
props.r#hanging = true;
}
props.to_state_id(args.block)
})
props.to_state_id(args.block)
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@@ -36,27 +32,22 @@ impl BlockBehaviour for LanternBlock {
.is_some_and(|world| can_place_at(world, args.position))
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world, args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}

View File

@@ -12,8 +12,7 @@ use pumpkin_world::{
};
use crate::block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
RandomTickArgs,
BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs,
};
pub const DECAY_DISTANCE: u8 = 7;
@@ -53,59 +52,49 @@ pub fn update_distance(
}
impl BlockBehaviour for LeavesBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props =
OakLeavesLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.persistent = true;
props.waterlogged = args.replacing.water_source();
props = update_distance(args.world, args.position, props);
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props =
OakLeavesLikeProperties::from_state_id(args.block.default_state.id, args.block);
props.persistent = true;
props.waterlogged = args.replacing.water_source();
props = update_distance(args.world, args.position, props);
props.to_state_id(args.block)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let neighbor_block = args.world.get_block(args.neighbor_position);
let distance_from_neighbor =
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let neighbor_block = args.world.get_block(args.neighbor_position);
let distance_from_neighbor =
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
let updated_props = update_distance(&**args.world, args.position, props);
let new_state_id = updated_props.to_state_id(args.block);
if new_state_id != state_id {
args.world
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
let updated_props = update_distance(&**args.world, args.position, props);
let new_state_id = updated_props.to_state_id(args.block);
if new_state_id != state_id {
args.world
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL);
}
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
if !props.persistent && props.distance == DECAY_DISTANCE {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
let state_id = args.world.get_block_state_id(args.position);
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
if !props.persistent && props.distance == DECAY_DISTANCE {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}

View File

@@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
use crate::block::entities::lectern::LecternBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
GetRedstonePowerArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs,
PlacedArgs, UseWithItemArgs,
};
@@ -63,7 +63,7 @@ impl LecternController for LecternPageController {
}
entity.page.store(page as usize, Ordering::Relaxed);
entity.mark_dirty();
LecternBlock::pulse(&self.world, &self.position).await;
LecternBlock::pulse(&self.world, &self.position);
})
}
@@ -72,7 +72,7 @@ impl LecternController for LecternPageController {
if let Some(entity) = self.entity() {
entity.page.store(0, Ordering::Relaxed);
}
LecternBlock::set_has_book(&self.world, &self.position, false).await;
LecternBlock::set_has_book(&self.world, &self.position, false);
})
}
}
@@ -113,22 +113,20 @@ impl LecternBlock {
/// The lectern strongly powers the block below it, so its neighbors need
/// updating whenever the power or book state changes.
async fn update_neighbors_below(world: &Arc<World>, position: &BlockPos) {
world.update_neighbors(&position.down(), None).await;
fn update_neighbors_below(world: &Arc<World>, position: &BlockPos) {
world.update_neighbors(&position.down(), None);
}
/// Emits the vanilla page-turn redstone pulse: powered for two game ticks.
pub(crate) async fn pulse(world: &Arc<World>, position: &BlockPos) {
pub(crate) fn pulse(world: &Arc<World>, position: &BlockPos) {
let (block, state_id) = world.get_block_and_state_id(position);
if block != &Block::LECTERN {
return;
}
let mut props = LecternLikeProperties::from_state_id(state_id, block);
props.powered = true;
world
.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
Self::update_neighbors_below(world, position).await;
world.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
Self::update_neighbors_below(world, position);
world.schedule_block_tick(
block,
*position,
@@ -139,7 +137,7 @@ impl LecternBlock {
}
/// Sets `has_book`, dropping any pending pulse like vanilla `setHasBook`.
pub(crate) async fn set_has_book(world: &Arc<World>, position: &BlockPos, has_book: bool) {
pub(crate) fn set_has_book(world: &Arc<World>, position: &BlockPos, has_book: bool) {
let (block, state_id) = world.get_block_and_state_id(position);
if block != &Block::LECTERN {
return;
@@ -147,214 +145,175 @@ impl LecternBlock {
let mut props = LecternLikeProperties::from_state_id(state_id, block);
props.powered = false;
props.has_book = has_book;
world
.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
Self::update_neighbors_below(world, position).await;
world.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
Self::update_neighbors_below(world, position);
}
}
impl BlockBehaviour for LecternBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_entity = LecternBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
})
fn placed(&self, args: PlacedArgs<'_>) {
let block_entity = LecternBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(block_entity));
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = LecternLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = LecternLikeProperties::default(args.block);
props.facing = args
.player
.living_entity
.entity
.get_horizontal_facing()
.opposite();
props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if !props.has_book {
return BlockActionResult::Pass;
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if !props.has_book {
return BlockActionResult::Pass;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return BlockActionResult::Pass;
};
let Some(inventory) = block_entity.get_inventory() else {
return BlockActionResult::Pass;
};
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return BlockActionResult::Pass;
};
let Some(inventory) = block_entity.get_inventory() else {
return BlockActionResult::Pass;
};
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLectern as i32,
1,
)
.await;
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLectern as i32,
1,
);
let controller = Arc::new(LecternPageController {
world: args.world.clone(),
position: *args.position,
inventory: inventory.clone(),
});
args.player
let controller = Arc::new(LecternPageController {
world: args.world.clone(),
position: *args.position,
inventory: inventory.clone(),
});
let player = args.player.clone();
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(
&LecternScreenFactory {
inventory,
controller,
},
Some(*args.position),
Some(pos),
)
.await;
});
BlockActionResult::Success
})
BlockActionResult::Success
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let item_stack = &mut *args.item_stack;
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
return BlockActionResult::PassToDefaultBlockAction;
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let item_stack = &mut *args.item_stack;
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
return BlockActionResult::PassToDefaultBlockAction;
}
let props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if props.has_book {
// Fall through so `normal_use` opens the reading screen.
return BlockActionResult::PassToDefaultBlockAction;
}
let Some(lectern) = args.world.get_block_entity(args.position) else {
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(lectern) = lectern.as_any().downcast_ref::<LecternBlockEntity>() else {
return BlockActionResult::PassToDefaultBlockAction;
};
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
futures::executor::block_on(lectern.set_stack(0, book));
Self::set_has_book(args.world, args.position, true);
args.world
.play_block_sound(Sound::ItemBookPut, SoundCategory::Blocks, *args.position);
BlockActionResult::Success
}
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let mut props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
props.powered = false;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
}
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
true
}
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
if props.powered { 15 } else { 0 }
}
fn get_strong_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
if props.powered && args.direction == BlockDirection::Up {
15
} else {
0
}
}
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
if !args.moved {
let props = LecternLikeProperties::from_state_id(args.old_state_id, args.block);
if props.powered {
Self::update_neighbors_below(args.world, args.position);
}
}
}
let props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
if props.has_book {
// Fall through so `normal_use` opens the reading screen.
return BlockActionResult::PassToDefaultBlockAction;
fn broken(&self, args: BrokenArgs<'_>) {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
let book = futures::executor::block_on(lectern_entity.remove_stack(0));
if !book.is_empty() {
// Drop the book item
let entity = Entity::new(
args.world.clone(),
Vector3::new(
f64::from(args.position.0.x) + 0.5,
f64::from(args.position.0.y) + 0.5,
f64::from(args.position.0.z) + 0.5,
),
&EntityType::ITEM,
);
let item_entity = ItemEntity::new(entity, book);
args.world.spawn_entity(Arc::new(item_entity));
}
let Some(lectern) = args.world.get_block_entity(args.position) else {
return BlockActionResult::PassToDefaultBlockAction;
};
let Some(lectern) = lectern.as_any().downcast_ref::<LecternBlockEntity>() else {
return BlockActionResult::PassToDefaultBlockAction;
};
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
let _ = item_stack;
lectern.set_stack(0, book).await;
Self::set_has_book(args.world, args.position, true).await;
args.world
.play_block_sound(Sound::ItemBookPut, SoundCategory::Blocks, *args.position);
BlockActionResult::Success
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let mut props = LecternLikeProperties::from_state_id(
args.world.get_block_state(args.position).id,
args.block,
);
props.powered = false;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
Self::update_neighbors_below(args.world, args.position).await;
})
}
fn emits_redstone_power<'a>(
&'a self,
_args: EmitsRedstonePowerArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move { true })
}
fn get_weak_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
if props.powered { 15 } else { 0 }
})
}
fn get_strong_redstone_power<'a>(
&'a self,
args: GetRedstonePowerArgs<'a>,
) -> BlockFuture<'a, u8> {
Box::pin(async move {
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
if props.powered && args.direction == BlockDirection::Up {
15
} else {
0
}
})
}
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !args.moved {
let props = LecternLikeProperties::from_state_id(args.old_state_id, args.block);
if props.powered {
Self::update_neighbors_below(args.world, args.position).await;
}
}
})
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) =
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
let book = lectern_entity.remove_stack(0).await;
if !book.is_empty() {
// Drop the book item
let entity = Entity::new(
args.world.clone(),
Vector3::new(
f64::from(args.position.0.x) + 0.5,
f64::from(args.position.0.y) + 0.5,
f64::from(args.position.0.z) + 0.5,
),
&EntityType::ITEM,
);
let item_entity = ItemEntity::new(entity, book);
args.world.spawn_entity(Arc::new(item_entity)).await;
}
}
})
}
fn get_comparator_output<'a>(
&'a self,
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) =
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
Some(lectern_entity.comparator_output().await)
} else {
Some(0)
}
})
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
{
Some(futures::executor::block_on(
lectern_entity.comparator_output(),
))
} else {
Some(0)
}
}
}

View File

@@ -2,8 +2,8 @@ use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block_from_tag;
use crate::block::BlockBehaviour;
use crate::block::OnPlaceArgs;
use crate::block::{BlockBehaviour, BlockFuture};
type LogProperties = pumpkin_data::block_properties::PaleOakWoodLikeProperties;
@@ -11,12 +11,10 @@ type LogProperties = pumpkin_data::block_properties::PaleOakWoodLikeProperties;
pub struct LogBlock;
impl BlockBehaviour for LogBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut log_props = LogProperties::default(args.block);
log_props.axis = args.direction.to_axis();
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut log_props = LogProperties::default(args.block);
log_props.axis = args.direction.to_axis();
log_props.to_state_id(args.block)
})
log_props.to_state_id(args.block)
}
}

View File

@@ -1,10 +1,10 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs};
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs};
use crate::entity::EntityBase;
use pumpkin_data::FacingExt;
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_data::translation;
use pumpkin_data::{BlockStateId, FacingExt};
use pumpkin_inventory::loom_screen_handler::LoomScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{
@@ -19,40 +19,35 @@ use tokio::sync::Mutex;
pub struct LoomBlock;
impl BlockBehaviour for LoomBlock {
fn on_place<'a>(
&'a self,
args: OnPlaceArgs<'a>,
) -> BlockFuture<'a, pumpkin_data::BlockStateId> {
Box::pin(async move {
let mut props = WallTorchLikeProperties::default(args.block);
if let Some(facing) = args
.player
.get_entity()
.get_facing()
.opposite()
.to_horizontal_facing()
{
props.facing = facing;
}
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = WallTorchLikeProperties::default(args.block);
if let Some(facing) = args
.player
.get_entity()
.get_facing()
.opposite()
.to_horizontal_facing()
{
props.facing = facing;
}
props.to_state_id(args.block)
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
1,
)
.await;
args.player
.open_handled_screen(&LoomScreenFactory, Some(*args.position))
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
1,
);
let player = Arc::clone(args.player);
let pos = *args.position;
tokio::spawn(async move {
player
.open_handled_screen(&LoomScreenFactory, Some(pos))
.await;
});
BlockActionResult::Success
})
BlockActionResult::Success
}
}

View File

@@ -5,14 +5,14 @@ use pumpkin_data::{
};
use pumpkin_macros::pumpkin_block;
use crate::block::{BlockBehaviour, BlockFuture, OnEntityStepArgs};
use crate::block::{BlockBehaviour, OnEntityStepArgs};
#[pumpkin_block("minecraft:magma_block")]
pub struct MagmaBlock;
impl BlockBehaviour for MagmaBlock {
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_entity_step(&self, args: OnEntityStepArgs<'_>) {
{
// Only living entities take damage
let Some(living_entity) = args.entity.get_living_entity() else {
return;
@@ -31,7 +31,10 @@ impl BlockBehaviour for MagmaBlock {
}
let has_frost_walker = {
let equipment = living_entity.entity_equipment.lock().await;
let equipment = living_entity
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment
.equipment
.get(&EquipmentSlot::FEET)
@@ -45,16 +48,13 @@ impl BlockBehaviour for MagmaBlock {
if living_entity
.get_effect(&StatusEffect::FIRE_RESISTANCE)
.await
.is_some()
{
return;
}
// Apply damage
args.entity
.damage(args.entity, 1.0, DamageType::HOT_FLOOR)
.await;
})
args.entity.damage(args.entity, 1.0, DamageType::HOT_FLOOR);
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs};
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs};
use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, MangroveRootsLikeProperties};
use pumpkin_data::fluid::Fluid;
@@ -9,29 +9,25 @@ use pumpkin_world::tick::TickPriority;
pub struct MangroveRootsBlock;
impl BlockBehaviour for MangroveRootsBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = MangroveRootsLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = MangroveRootsLikeProperties::default(args.block);
props.waterlogged = args.replacing.water_source();
props.to_state_id(args.block)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = MangroveRootsLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
props.to_state_id(args.block)
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let props = MangroveRootsLikeProperties::from_state_id(args.state_id, args.block);
if props.waterlogged {
args.world.schedule_fluid_tick(
&Fluid::WATER,
*args.position,
Fluid::WATER.flow_speed as u8,
TickPriority::Normal,
);
}
props.to_state_id(args.block)
}
}

View File

@@ -13,8 +13,8 @@ use uuid::Uuid;
use crate::{
block::{
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnStateReplacedArgs, RandomTickArgs,
BlockBehaviour, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs, OnStateReplacedArgs,
RandomTickArgs,
},
entity::{EntityBase, r#type::from_type},
world::{World, portal::nether::NetherPortal},
@@ -44,123 +44,116 @@ impl NetherPortalBlock {
}
impl BlockBehaviour for NetherPortalBlock {
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let direction_axis = args.direction.to_axis();
let state_axis =
NetherPortalLikeProperties::from_state_id(args.state_id, &Block::NETHER_PORTAL)
.axis;
// Convert HorizontalAxis to Axis for comparison
let state_axis_full: Axis = match state_axis {
HorizontalAxis::X => Axis::X,
HorizontalAxis::Z => Axis::Z,
};
// Vanilla logic: keep portal if direction is horizontal AND different from portal axis
let is_horizontal_and_different =
args.direction.is_horizontal() && direction_axis != state_axis_full;
if is_horizontal_and_different
|| args.neighbor_state_id == args.state_id
|| NetherPortal::get_on_axis(args.world, args.position, state_axis)
.is_some_and(|e| e.was_already_valid())
{
return args.state_id;
}
Block::AIR.default_state.id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let direction_axis = args.direction.to_axis();
let state_axis =
NetherPortalLikeProperties::from_state_id(args.state_id, &Block::NETHER_PORTAL).axis;
// Convert HorizontalAxis to Axis for comparison
let state_axis_full: Axis = match state_axis {
HorizontalAxis::X => Axis::X,
HorizontalAxis::Z => Axis::Z,
};
// Vanilla logic: keep portal if direction is horizontal AND different from portal axis
let is_horizontal_and_different =
args.direction.is_horizontal() && direction_axis != state_axis_full;
if is_horizontal_and_different
|| args.neighbor_state_id == args.state_id
|| NetherPortal::get_on_axis(args.world, args.position, state_axis)
.is_some_and(|e| e.was_already_valid())
{
return args.state_id;
}
Block::AIR.default_state.id
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let level_info = args.world.level_info.load();
let difficulty = level_info.difficulty;
if !level_info.game_rules.spawn_monsters
|| !level_info.game_rules.spawn_mobs
|| difficulty == Difficulty::Peaceful
|| (args.world.dimension != Dimension::OVERWORLD
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
{
return;
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
let level_info = args.world.level_info.load();
let difficulty = level_info.difficulty;
if !level_info.game_rules.spawn_mobs
|| difficulty == Difficulty::Peaceful
|| (args.world.dimension != Dimension::OVERWORLD
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
{
return;
}
let difficulty_id = difficulty as u32;
let roll = rand::rng().random_range(0..2000);
if roll >= difficulty_id {
return;
}
let difficulty_id = difficulty as u32;
let roll = rand::rng().random_range(0..2000);
if roll >= difficulty_id {
return;
}
let player_close = args
.world
.get_closest_player(args.position.to_centered_f64(), 128.0)
.is_some();
if !player_close {
return;
}
let player_close = args
.world
.get_closest_player(args.position.to_centered_f64(), 128.0)
.is_some();
if !player_close {
return;
}
let mut bottom_pos = *args.position;
while args.world.get_block(&bottom_pos) == &Block::NETHER_PORTAL {
bottom_pos = bottom_pos.down();
}
let mut bottom_pos = *args.position;
while args.world.get_block(&bottom_pos) == &Block::NETHER_PORTAL {
bottom_pos = bottom_pos.down();
}
if args
.world
.get_block_state(&bottom_pos)
.is_side_solid(BlockDirection::Up)
{
let spawn_pos = Vector3::new(
bottom_pos.0.x as f64 + 0.5,
(bottom_pos.0.y + 1) as f64,
bottom_pos.0.z as f64 + 0.5,
);
let mob = from_type(
&EntityType::ZOMBIFIED_PIGLIN,
spawn_pos,
args.world,
Uuid::new_v4(),
);
mob.get_entity()
.portal_cooldown
.store(300, Ordering::Relaxed);
args.world.spawn_entity(mob).await;
}
})
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let target_world =
if args.world.dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else {
args.server.get_world_from_dimension(&Dimension::THE_NETHER)
};
if Arc::ptr_eq(&target_world, args.world) {
return;
}
tracing::debug!(
"Nether portal collision at {:?}, targeting world {:?}",
args.position,
target_world.dimension.minecraft_name
if args
.world
.get_block_state(&bottom_pos)
.is_side_solid(BlockDirection::Up)
{
let spawn_pos = Vector3::new(
bottom_pos.0.x as f64 + 0.5,
(bottom_pos.0.y + 1) as f64,
bottom_pos.0.z as f64 + 0.5,
);
let portal_delay = Self::get_portal_time(args.world, args.entity);
args.entity
.get_entity()
.try_use_portal(portal_delay, target_world, *args.position)
.await;
})
let mob = from_type(
&EntityType::ZOMBIFIED_PIGLIN,
spawn_pos,
args.world,
Uuid::new_v4(),
);
mob.get_entity()
.portal_cooldown
.store(300, Ordering::Relaxed);
args.world.spawn_entity_non_save(mob);
}
}
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Remove from POI storage when portal block is replaced
let mut poi_storage = args.world.portal_poi.lock().await;
poi_storage.remove(args.position);
})
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
let target_world =
if args.world.dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else {
args.server.get_world_from_dimension(&Dimension::THE_NETHER)
};
if Arc::ptr_eq(&target_world, args.world) {
return;
}
tracing::debug!(
"Nether portal collision at {:?}, targeting world {:?}",
args.position,
target_world.dimension.minecraft_name
);
let portal_delay = Self::get_portal_time(args.world, args.entity);
args.entity
.get_entity()
.try_use_portal(portal_delay, target_world, *args.position);
}
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
// Remove from POI storage when portal block is replaced
let mut poi_storage = args
.world
.portal_poi
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
poi_storage.remove(args.position);
}
fn rotate(

View File

@@ -1,6 +1,6 @@
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
UseWithItemArgs,
};
use pumpkin_data::BlockStateId;
@@ -25,7 +25,7 @@ use super::redstone::block_receives_redstone_power;
pub struct NoteBlock;
impl NoteBlock {
pub async fn play_note(props: &NoteBlockLikeProperties, world: &World, pos: &BlockPos) {
pub fn play_note(props: &NoteBlockLikeProperties, world: &World, pos: &BlockPos) {
if !is_base_block(props.instrument) || world.get_block_state(&pos.up()).is_air() {
let mut event = crate::plugin::api::events::block::note_play::NotePlayEvent::new(
*pos,
@@ -33,12 +33,12 @@ impl NoteBlock {
props.note,
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
server.plugin_manager.fire_blocking(&server, &mut event);
}
if event.cancelled {
return;
}
world.add_synced_block_event(*pos, 0, 0).await;
world.add_synced_block_event(*pos, 0, 0);
}
}
fn get_note_pitch(note: u16) -> f32 {
@@ -70,116 +70,92 @@ impl NoteBlock {
}
impl BlockBehaviour for NoteBlock {
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_state = args.world.get_block_state(args.position);
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let powered = block_receives_redstone_power(args.world, args.position).await;
// check if powered state changed
if note_props.powered != powered {
if powered {
Self::play_note(&note_props, args.world, args.position).await;
}
note_props.powered = powered;
args.world
.set_block_state(
args.position,
note_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
let block_state = args.world.get_block_state(args.position);
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let powered = block_receives_redstone_power(args.world, args.position);
// check if powered state changed
if note_props.powered != powered {
if powered {
Self::play_note(&note_props, args.world, args.position);
}
})
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let block_state = args.world.get_block_state(args.position);
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
note_props.note = (note_props.note + 1) % 25;
args.world
.set_block_state(
args.position,
note_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
Self::play_note(&note_props, args.world, args.position).await;
args.player
.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::TuneNoteblock as i32,
1,
)
.await;
BlockActionResult::Success
})
}
fn use_with_item<'a>(
&'a self,
_args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
// TODO
BlockActionResult::PassToDefaultBlockAction
})
}
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move {
let block_state = args.world.get_block_state(args.position);
let note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let instrument = note_props.instrument;
let pitch = if is_base_block(instrument) {
// checks if can be pitched
Self::get_note_pitch(u16::from(note_props.note))
} else {
1.0 // default pitch
};
// check hasCustomSound
args.world.play_sound_raw(
convert_instrument_to_sound(instrument) as u16,
SoundCategory::Records,
&args.position.to_f64(),
3.0,
pitch,
note_props.powered = powered;
args.world.set_block_state(
args.position,
note_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
true
})
}
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
Self::get_state_with_instrument(
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let block_state = args.world.get_block_state(args.position);
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
note_props.note = (note_props.note + 1) % 25;
args.world.set_block_state(
args.position,
note_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
Self::play_note(&note_props, args.world, args.position);
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::TuneNoteblock as i32,
1,
);
BlockActionResult::Success
}
fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult {
// TODO
BlockActionResult::PassToDefaultBlockAction
}
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
let block_state = args.world.get_block_state(args.position);
let note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let instrument = note_props.instrument;
let pitch = if is_base_block(instrument) {
// checks if can be pitched
Self::get_note_pitch(u16::from(note_props.note))
} else {
1.0 // default pitch
};
// check hasCustomSound
args.world.play_sound_raw(
convert_instrument_to_sound(instrument) as u16,
SoundCategory::Records,
&args.position.to_f64(),
3.0,
pitch,
);
true
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Self::get_state_with_instrument(
args.world,
args.position,
Block::NOTE_BLOCK.default_state.id,
args.block,
)
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction.to_axis() == Axis::Y {
return Self::get_state_with_instrument(
args.world,
args.position,
Block::NOTE_BLOCK.default_state.id,
args.state_id,
args.block,
)
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction.to_axis() == Axis::Y {
return Self::get_state_with_instrument(
args.world,
args.position,
args.state_id,
args.block,
);
}
args.state_id
})
);
}
args.state_id
}
}

View File

@@ -7,7 +7,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, BonemealArgs, RandomTickArgs};
use crate::block::{BlockBehaviour, BonemealArgs, RandomTickArgs};
use crate::world::World;
#[pumpkin_block_from_tag("minecraft:nylium")]
@@ -25,18 +25,14 @@ impl NyliumBlock {
}
impl BlockBehaviour for NyliumBlock {
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !Self::can_be_nylium(args.world, args.position) {
args.world
.set_block_state(
args.position,
Block::NETHERRACK.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
if !Self::can_be_nylium(args.world, args.position) {
args.world.set_block_state(
args.position,
Block::NETHERRACK.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
}
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
@@ -50,26 +46,24 @@ impl BlockBehaviour for NyliumBlock {
true
}
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let world = args.world;
let block = args.block;
let above_pos = args.position.up();
fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
let world = args.world;
let block = args.block;
let above_pos = args.position.up();
if block == &Block::CRIMSON_NYLIUM {
place_crimson_vegetation(world, &above_pos).await;
} else if block == &Block::WARPED_NYLIUM {
place_warped_vegetation(world, &above_pos).await;
place_nether_sprouts(world, &above_pos).await;
if rand::rng().random_range(0..8) == 0 {
place_twisting_vines(world, &above_pos).await;
}
if block == &Block::CRIMSON_NYLIUM {
place_crimson_vegetation(world, &above_pos);
} else if block == &Block::WARPED_NYLIUM {
place_warped_vegetation(world, &above_pos);
place_nether_sprouts(world, &above_pos);
if rand::rng().random_range(0..8) == 0 {
place_twisting_vines(world, &above_pos);
}
})
}
}
}
async fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
@@ -116,13 +110,11 @@ async fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
}
}
async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
@@ -171,13 +163,11 @@ async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
}
}
async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
@@ -216,13 +206,11 @@ async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
continue;
}
world
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
}
}
async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
@@ -256,22 +244,18 @@ async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
|| !world.get_block_state(&current_pos.up()).is_air();
if is_top {
world
.set_block_state(
&current_pos,
Block::TWISTING_VINES.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
&current_pos,
Block::TWISTING_VINES.default_state.id,
BlockFlags::NOTIFY_ALL,
);
break;
}
world
.set_block_state(
&current_pos,
Block::TWISTING_VINES_PLANT.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
&current_pos,
Block::TWISTING_VINES_PLANT.default_state.id,
BlockFlags::NOTIFY_ALL,
);
current_pos = current_pos.up();
}
}

View File

@@ -46,7 +46,7 @@ impl<'a> PistonHandler<'a> {
}
}
pub async fn calculate_push(&mut self) -> bool {
pub fn calculate_push(&mut self) -> bool {
self.moved_blocks.clear();
self.broken_blocks.clear();
let (block, block_state) = self.world.get_block_and_state(&self.pos_to);
@@ -64,15 +64,13 @@ impl<'a> PistonHandler<'a> {
}
return false;
}
if !self.try_move(self.pos_to, self.motion_direction).await {
if !self.try_move(self.pos_to, self.motion_direction) {
return false;
}
for i in 0..self.moved_blocks.len() {
let block_pos = self.moved_blocks[i];
let block = self.world.get_block(&block_pos);
if Self::is_block_sticky(block)
&& !self.try_move_adjacent_block(block, &block_pos).await
{
if Self::is_block_sticky(block) && !self.try_move_adjacent_block(block, &block_pos) {
return false;
}
}
@@ -98,7 +96,7 @@ impl<'a> PistonHandler<'a> {
|| (!self.retracted && pos == self.pos_from.offset(self.piston_direction.to_offset()))
}
async fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool {
fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool {
let (mut block, block_state) = self.world.get_block_and_state(&pos);
if block_state.is_air() {
return true;
@@ -154,7 +152,7 @@ impl<'a> PistonHandler<'a> {
let block_pos3 = self.moved_blocks[m];
let block = self.world.get_block(&block_pos3);
if Self::is_block_sticky(block)
&& !Box::pin(self.try_move_adjacent_block(block, &block_pos3)).await
&& !self.try_move_adjacent_block(block, &block_pos3)
{
return false;
}
@@ -204,7 +202,7 @@ impl<'a> PistonHandler<'a> {
self.moved_blocks.extend(list3);
}
async fn try_move_adjacent_block(&mut self, block: &Block, pos: &BlockPos) -> bool {
fn try_move_adjacent_block(&mut self, block: &Block, pos: &BlockPos) -> bool {
for direction in BlockDirection::all() {
if direction.to_axis() == self.motion_direction.to_axis() {
continue;
@@ -212,7 +210,7 @@ impl<'a> PistonHandler<'a> {
let block_pos = pos.offset(direction.to_offset());
let block_state2 = self.world.get_block(&block_pos);
if Self::is_adjacent_block_stuck(block_state2, block)
&& !self.try_move(block_pos, direction).await
&& !self.try_move(block_pos, direction)
{
return false;
}

View File

@@ -18,7 +18,7 @@ use rustc_hash::FxHashMap;
use crate::{
block::{
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs,
BlockBehaviour, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs,
OnSyncedBlockEventArgs, PlacedArgs,
blocks::{piston::piston_head::PistonHeadProperties, redstone::is_emitting_redstone_power},
},
@@ -78,249 +78,229 @@ impl PistonBlock {
}
impl BlockBehaviour for PistonBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = PistonProps::default(args.block);
props.extended = false;
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = PistonProps::default(args.block);
props.extended = false;
props.facing = args.player.get_entity().get_facing().opposite();
props.to_state_id(args.block)
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let props = PistonProps::from_state_id(args.state.id, args.block);
let pos = args
.position
.offset(props.facing.to_block_direction().to_offset());
let (block_to_check, block_to_check_state_id) = args.world.get_block_and_state_id(&pos);
if &Block::PISTON_HEAD == block_to_check {
let head_props =
PistonHeadProperties::from_state_id(block_to_check_state_id, block_to_check);
fn broken(&self, args: BrokenArgs<'_>) {
let props = PistonProps::from_state_id(args.state.id, args.block);
let pos = args
.position
.offset(props.facing.to_block_direction().to_offset());
let (block_to_check, block_to_check_state_id) = args.world.get_block_and_state_id(&pos);
if &Block::PISTON_HEAD == block_to_check {
let head_props =
PistonHeadProperties::from_state_id(block_to_check_state_id, block_to_check);
if (head_props.facing.to_block_direction() != props.facing.to_block_direction())
&& &Block::PISTON_HEAD == block_to_check
{
//Then this is a head of some other piston.
return;
}
args.world
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
.await;
} else if &Block::MOVING_PISTON == block_to_check {
args.world
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
.await;
}
})
}
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if args.old_state_id == args.state_id {
if (head_props.facing.to_block_direction() != props.facing.to_block_direction())
&& &Block::PISTON_HEAD == block_to_check
{
//Then this is a head of some other piston.
return;
}
try_move(args.world, args.block, args.position).await;
})
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
} else if &Block::MOVING_PISTON == block_to_check {
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
try_move(args.world, args.block, args.position).await;
})
fn placed(&self, args: PlacedArgs<'_>) {
if args.old_state_id == args.state_id {
return;
}
try_move(args.world, args.block, args.position);
}
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
try_move(args.world, args.block, args.position);
}
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
let block_id = args.block.id;
let block = Block::from_id(block_id);
Self::handle_synced_block_event(block, args.world, args.position, args.r#type, args.data)
}
}
impl PistonBlock {
#[expect(clippy::too_many_lines)]
fn on_synced_block_event<'a>(
&'a self,
args: OnSyncedBlockEventArgs<'a>,
) -> BlockFuture<'a, bool> {
Box::pin(async move {
let (block, world, pos, r#type, data) = (
args.block,
args.world,
args.position,
args.r#type,
args.data,
);
fn handle_synced_block_event(
block: &Block,
world: &Arc<World>,
pos: &BlockPos,
r#type: u8,
data: u8,
) -> bool {
let state = world.get_block_state(pos);
let mut props = PistonProps::from_state_id(state.id, block);
let dir = props.facing.to_block_direction();
let state = world.get_block_state(pos);
let mut props = PistonProps::from_state_id(state.id, block);
let dir = props.facing.to_block_direction();
// I don't think this is optimal ?
let sticky = block == &Block::STICKY_PISTON;
// I don't think this is optimal ?
let sticky = block == &Block::STICKY_PISTON;
let should_extend = should_extend(world, pos, dir);
if should_extend && (r#type == 1 || r#type == 2) {
props.extended = true;
world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS);
return false;
}
let should_extend = should_extend(world, pos, dir).await;
if should_extend && (r#type == 1 || r#type == 2) {
props.extended = true;
world
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS)
.await;
return false;
}
// This may prevents when something happens in the one tick before this function got called
if !should_extend && r#type == 0 {
return false;
}
// Extend Piston
if r#type == 0 {
let mut event =
crate::plugin::api::events::block::block_piston::BlockPistonExtendEvent::new(
*pos,
format!("{dir:?}"),
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
if event.cancelled {
return false;
}
if !move_piston(world, dir, pos, true, sticky).await {
return false;
}
props.extended = true;
world
.set_block_state(
pos,
props.to_state_id(block),
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
)
.await;
// Play piston extend sound
let pitch = rand::rng().random_range(0.6f32..0.85);
world.play_sound_fine(
Sound::BlockPistonExtend,
SoundCategory::Blocks,
&pos.to_centered_f64(),
0.5,
pitch,
);
return true;
}
// Reduce Piston
// This may prevents when something happens in the one tick before this function got called
if !should_extend && r#type == 0 {
return false;
}
// Extend Piston
if r#type == 0 {
let mut event =
crate::plugin::api::events::block::block_piston::BlockPistonRetractEvent::new(
crate::plugin::api::events::block::block_piston::BlockPistonExtendEvent::new(
*pos,
format!("{dir:?}"),
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
server.plugin_manager.fire_blocking(&server, &mut event);
}
if event.cancelled {
return false;
}
let extended_pos = pos.offset(dir.to_offset());
if let Some(block_entity) = world.get_block_entity(&extended_pos)
&& let Some(piston) = block_entity.as_any().downcast_ref::<PistonBlockEntity>()
{
piston.finish(world.clone()).await;
if !move_piston(world, dir, pos, true, sticky) {
return false;
}
let mut props = MovingPistonLikeProperties::default(&Block::MOVING_PISTON);
props.facing = dir.to_facing();
props.r#type = if sticky {
PistonType::Sticky
} else {
PistonType::Normal
};
world
.set_block_state(
pos,
props.to_state_id(&Block::MOVING_PISTON),
BlockFlags::FORCE_STATE,
)
.await;
let mut props = PistonProps::default(block);
props.facing = BlockDirection::by_index((data & 7) as usize)
.unwrap_or(BlockDirection::North)
.to_facing();
world.add_block_entity(Arc::new(PistonBlockEntity {
position: *pos,
facing: dir,
pushed_block_state: BlockState::from_id(props.to_state_id(block)),
current_progress: 0.0.into(),
last_progress: 0.0.into(),
extending: false,
source: true,
}));
world.update_neighbors(pos, None).await;
if sticky {
let pull_pos = pos.offset_dir(dir.to_offset(), 2);
let (block, state) = world.get_block_and_state(&pull_pos);
let piston_piece = if block == &Block::MOVING_PISTON
&& let Some(entity) = world.get_block_entity(&pull_pos)
&& let Some(piston) = entity.as_any().downcast_ref::<PistonBlockEntity>()
&& piston.facing == dir
&& piston.extending
{
piston.finish(world.clone()).await;
true
} else {
false
};
if !piston_piece {
if r#type == 1
&& !state.is_air()
&& Self::is_movable(block, state, dir, false, dir)
&& (state.piston_behavior == PistonBehavior::Normal
|| block == &Block::PISTON
|| block == &Block::STICKY_PISTON)
{
move_piston(world, dir, pos, false, sticky).await;
} else {
// remove
world
.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
}
} else {
// remove
world
.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
// Play piston contract sound
let pitch = rand::rng().random_range(0.6f32..0.75);
props.extended = true;
world.set_block_state(
pos,
props.to_state_id(block),
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
);
// Play piston extend sound
let pitch = rand::rng().random_range(0.6f32..0.85);
world.play_sound_fine(
Sound::BlockPistonContract,
Sound::BlockPistonExtend,
SoundCategory::Blocks,
&pos.to_centered_f64(),
0.5,
pitch,
);
true
})
return true;
}
// Reduce Piston
let mut event =
crate::plugin::api::events::block::block_piston::BlockPistonRetractEvent::new(
*pos,
format!("{dir:?}"),
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
if event.cancelled {
return false;
}
let extended_pos = pos.offset(dir.to_offset());
if let Some(block_entity) = world.get_block_entity(&extended_pos)
&& let Some(piston) = block_entity.as_any().downcast_ref::<PistonBlockEntity>()
{
piston.finish(world);
}
let mut props = MovingPistonLikeProperties::default(&Block::MOVING_PISTON);
props.facing = dir.to_facing();
props.r#type = if sticky {
PistonType::Sticky
} else {
PistonType::Normal
};
world.set_block_state(
pos,
props.to_state_id(&Block::MOVING_PISTON),
BlockFlags::FORCE_STATE,
);
let mut props = PistonProps::default(block);
props.facing = BlockDirection::by_index((data & 7) as usize)
.unwrap_or(BlockDirection::North)
.to_facing();
world.add_block_entity(Arc::new(PistonBlockEntity {
position: *pos,
facing: dir,
pushed_block_state: BlockState::from_id(props.to_state_id(block)),
current_progress: 0.0.into(),
last_progress: 0.0.into(),
extending: false,
source: true,
}));
world.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::FORCE_STATE,
);
world.update_neighbors(pos, None);
if sticky {
let pull_pos = pos.offset_dir(dir.to_offset(), 2);
let (block, state) = world.get_block_and_state(&pull_pos);
if data == 2 {
world.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
);
} else {
let is_air = state.is_air();
if !is_air
&& (Self::is_movable(block, state, dir, false, dir.opposite())
|| Self::is_movable(block, state, dir, false, dir))
&& (state.piston_behavior == PistonBehavior::Normal
|| block == &Block::PISTON
|| block == &Block::STICKY_PISTON)
{
move_piston(world, dir, pos, false, sticky);
} else {
// remove
world.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
}
} else {
// remove
world.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
// Play piston contract sound
let pitch = rand::rng().random_range(0.6f32..0.75);
world.play_sound_fine(
Sound::BlockPistonContract,
SoundCategory::Blocks,
&pos.to_centered_f64(),
0.5,
pitch,
);
true
}
}
async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDirection) -> bool {
fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDirection) -> bool {
for dir in BlockDirection::all() {
let neighbor_pos = block_pos.offset(dir.to_offset());
let (block, state) = world.get_block_and_state(&neighbor_pos);
// Pistons can't be powered from the same direction as they are facing
if dir == piston_dir
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir).await
if dir == piston_dir || !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir)
{
continue;
}
@@ -328,14 +308,14 @@ async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDir
}
let neighbor_pos = block_pos.offset(BlockDirection::Down.to_offset());
let (block, state) = world.get_block_and_state(&neighbor_pos);
if is_emitting_redstone_power(block, state, world, block_pos, BlockDirection::Down).await {
if is_emitting_redstone_power(block, state, world, block_pos, BlockDirection::Down) {
return true;
}
for dir in BlockDirection::all() {
let neighbor_pos = block_pos.up().offset(dir.to_offset());
let (block, state) = world.get_block_and_state(&neighbor_pos);
if dir == BlockDirection::Down
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir).await
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir)
{
continue;
}
@@ -344,20 +324,15 @@ async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDir
false
}
pub async fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
pub fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
let state = world.get_block_state(block_pos);
let props = PistonProps::from_state_id(state.id, block);
let dir = props.facing.to_block_direction();
let should_extent = should_extend(world, block_pos, dir).await;
let should_extent = should_extend(world, block_pos, dir);
if should_extent && !props.extended {
if PistonHandler::new(world, *block_pos, dir, true)
.calculate_push()
.await
{
world
.add_synced_block_event(*block_pos, 0, dir.to_index())
.await;
if PistonHandler::new(world, *block_pos, dir, true).calculate_push() {
world.add_synced_block_event(*block_pos, 0, dir.to_index());
}
} else if !should_extent && props.extended {
let new_pos = block_pos.offset_dir(dir.to_offset(), 2);
@@ -380,14 +355,12 @@ pub async fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
}
}
}
world
.add_synced_block_event(*block_pos, r#type, dir.to_index())
.await;
world.add_synced_block_event(*block_pos, r#type, dir.to_index());
}
}
#[expect(clippy::too_many_lines)]
async fn move_piston(
fn move_piston(
world: &Arc<World>,
dir: BlockDirection,
block_pos: &BlockPos,
@@ -396,16 +369,14 @@ async fn move_piston(
) -> bool {
let extended_pos = block_pos.offset(dir.to_offset());
if !extend && world.get_block(&extended_pos) == &Block::PISTON_HEAD {
world
.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::FORCE_STATE,
)
.await;
world.set_block_state(
&extended_pos,
Block::AIR.default_state.id,
BlockFlags::FORCE_STATE,
);
}
let mut handler = PistonHandler::new(world, *block_pos, dir, extend);
if !handler.calculate_push().await {
if !handler.calculate_push() {
return false;
}
@@ -427,13 +398,11 @@ async fn move_piston(
for &broken_block_pos in broken_blocks.iter().rev() {
let block_state = world.get_block_state(&broken_block_pos);
world
.break_block(
&broken_block_pos,
None,
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE,
)
.await;
world.break_block(
&broken_block_pos,
None,
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE,
);
affected_block_states.push(block_state);
}
@@ -446,9 +415,7 @@ async fn move_piston(
props.facing = dir.to_facing();
let state = props.to_state_id(&Block::MOVING_PISTON);
world
.set_block_state(&target_pos, state, BlockFlags::MOVED)
.await;
world.set_block_state(&target_pos, state, BlockFlags::MOVED);
if let Some(moved_state) = moved_block_states.get(moved_blocks.len() - 1 - index) {
world.add_block_entity(Arc::new(PistonBlockEntity {
@@ -474,13 +441,11 @@ async fn move_piston(
props.facing = dir.to_facing();
props.r#type = pistion_type;
moved_blocks_map.remove(&extended_pos);
world
.set_block_state(
&extended_pos,
props.to_state_id(&Block::MOVING_PISTON),
BlockFlags::MOVED,
)
.await;
world.set_block_state(
&extended_pos,
props.to_state_id(&Block::MOVING_PISTON),
BlockFlags::MOVED,
);
let mut props = PistonHeadLikeProperties::default(&Block::PISTON_HEAD);
props.facing = dir.to_facing();
props.r#type = pistion_type;
@@ -497,70 +462,56 @@ async fn move_piston(
let air_state = Block::AIR.default_state.id;
for &pos in moved_blocks_map.keys() {
world
.set_block_state(
&pos,
air_state,
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE | BlockFlags::MOVED,
)
.await;
world.set_block_state(
&pos,
air_state,
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE | BlockFlags::MOVED,
);
}
for (pos, state) in &moved_blocks_map {
world
.block_registry
.prepare(
world,
pos,
Block::from_state_id(state.id),
state.id,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.update_neighbors(pos, None).await;
world
.block_registry
.prepare(
world,
pos,
&Block::AIR,
air_state,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.block_registry.prepare(
world,
pos,
Block::from_state_id(state.id),
state.id,
BlockFlags::NOTIFY_LISTENERS,
);
world.update_neighbors(pos, None);
world.block_registry.prepare(
world,
pos,
&Block::AIR,
air_state,
BlockFlags::NOTIFY_LISTENERS,
);
}
for (i, &broken_block_pos) in broken_blocks.iter().rev().enumerate() {
if let Some(block_state) = affected_block_states.get(i) {
world
.block_registry
.on_state_replaced(
world,
Block::from_state_id(block_state.id),
&broken_block_pos,
block_state.id, // ?
false,
)
.await;
world
.block_registry
.prepare(
world,
&broken_block_pos,
Block::from_state_id(block_state.id),
block_state.id,
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.update_neighbors(&broken_block_pos, None).await;
world.block_registry.on_state_replaced(
world,
Block::from_state_id(block_state.id),
&broken_block_pos,
block_state.id, // ?
false,
);
world.block_registry.prepare(
world,
&broken_block_pos,
Block::from_state_id(block_state.id),
block_state.id,
BlockFlags::NOTIFY_LISTENERS,
);
world.update_neighbors(&broken_block_pos, None);
}
}
for &moved_block_pos in moved_blocks.iter().rev() {
world.update_neighbors(&moved_block_pos, None).await;
world.update_neighbors(&moved_block_pos, None);
}
if extend {
world.update_neighbors(&extended_pos, None).await;
world.update_neighbors(&extended_pos, None);
}
true

View File

@@ -3,8 +3,8 @@ use pumpkin_data::{Block, FacingExt};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::world::BlockFlags;
use crate::block::BlockBehaviour;
use crate::block::BrokenArgs;
use crate::block::{BlockBehaviour, BlockFuture};
use super::piston::PistonProps;
@@ -14,8 +14,8 @@ pub(crate) type MovingPistonProps = pumpkin_data::block_properties::MovingPiston
pub struct PistonExtensionBlock;
impl BlockBehaviour for PistonExtensionBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn broken(&self, args: BrokenArgs<'_>) {
{
let props = MovingPistonProps::from_state_id(args.state.id, &Block::MOVING_PISTON);
let pos = args
.position
@@ -25,11 +25,9 @@ impl BlockBehaviour for PistonExtensionBlock {
let props = PistonProps::from_state_id(new_state, new_block);
if props.extended {
// TODO: use player
args.world
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
.await;
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
}
}
})
}
}
}

View File

@@ -3,8 +3,8 @@ use pumpkin_data::{Block, FacingExt};
use pumpkin_macros::pumpkin_block;
use pumpkin_world::world::BlockFlags;
use crate::block::BlockBehaviour;
use crate::block::blocks::piston::piston::try_move;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::block::{BrokenArgs, OnNeighborUpdateArgs};
use super::piston::PistonProps;
@@ -15,50 +15,43 @@ pub(crate) type PistonHeadProperties = pumpkin_data::block_properties::PistonHea
pub struct PistonHeadBlock;
impl BlockBehaviour for PistonHeadBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD);
let pos = args
.position
.offset(props.facing.opposite().to_block_direction().to_offset());
let (new_block, new_state) = args.world.get_block_and_state_id(&pos);
if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block {
let props = PistonProps::from_state_id(new_state, new_block);
if props.extended {
// TODO: use player
args.world
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
.await;
}
fn broken(&self, args: BrokenArgs<'_>) {
let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD);
let pos = args
.position
.offset(props.facing.opposite().to_block_direction().to_offset());
let (new_block, new_state) = args.world.get_block_and_state_id(&pos);
if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block {
let props = PistonProps::from_state_id(new_state, new_block);
if props.extended {
// TODO: use player
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
}
})
}
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let head_state_id = args.world.get_block_state_id(args.position);
let head_props =
PistonHeadProperties::from_state_id(head_state_id, &Block::PISTON_HEAD);
if head_props.facing != Facing::Up {
return;
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
let head_state_id = args.world.get_block_state_id(args.position);
let head_props = PistonHeadProperties::from_state_id(head_state_id, &Block::PISTON_HEAD);
if head_props.facing != Facing::Up {
return;
}
let piston_pos = args.position.offset(
head_props
.facing
.opposite()
.to_block_direction()
.to_offset(),
);
let piston_block = args.world.get_block(&piston_pos);
if &Block::PISTON == piston_block || &Block::STICKY_PISTON == piston_block {
let up_pos = args
.position
.offset(head_props.facing.to_block_direction().to_offset());
let upper_block = args.world.get_block(&up_pos);
if upper_block != &Block::REDSTONE_BLOCK {
//Then somebody probably broke the redstone block, try to check if piston should still be extended.
try_move(args.world, piston_block, &piston_pos);
}
let piston_pos = args.position.offset(
head_props
.facing
.opposite()
.to_block_direction()
.to_offset(),
);
let piston_block = args.world.get_block(&piston_pos);
if &Block::PISTON == piston_block || &Block::STICKY_PISTON == piston_block {
let up_pos = args
.position
.offset(head_props.facing.to_block_direction().to_offset());
let upper_block = args.world.get_block(&up_pos);
if upper_block != &Block::REDSTONE_BLOCK {
//Then somebody probably broke the redstone block, try to check if piston should still be extended.
try_move(args.world, piston_block, &piston_pos).await;
}
}
})
}
}
}

View File

@@ -12,7 +12,7 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, blocks::plant::PlantBlockBase};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, blocks::plant::PlantBlockBase};
use crate::block::{
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs,
};
@@ -37,105 +37,89 @@ impl BlockBehaviour for BambooBlock {
&& args.world.get_block_state(&top.up()).is_air()
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
bone_meal(Arc::clone(args.world), args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
bone_meal(args.world, args.position);
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let (block_below, state_id_below) =
args.world.get_block_and_state_id(&args.position.down());
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let (block_below, state_id_below) =
args.world.get_block_and_state_id(&args.position.down());
if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
let mut props = BambooLikeProperties::from_state_id(
Block::BAMBOO.default_state.id,
&Block::BAMBOO,
);
if block_below == &Block::BAMBOO_SAPLING {
return Block::BAMBOO.default_state.id;
} else if block_below == &Block::BAMBOO {
let props_below =
BambooLikeProperties::from_state_id(state_id_below, block_below);
if props_below.age > 0 {
props.age = 1;
}
if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
let mut props =
BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
if block_below == &Block::BAMBOO_SAPLING {
return Block::BAMBOO.default_state.id;
} else if block_below == &Block::BAMBOO {
let props_below = BambooLikeProperties::from_state_id(state_id_below, block_below);
if props_below.age > 0 {
props.age = 1;
}
} else {
let (block_above, state_id_above) =
args.world.get_block_and_state_id(&args.position.up());
if block_above == &Block::BAMBOO {
let props_above =
BambooLikeProperties::from_state_id(state_id_above, block_above);
props.age = props_above.age;
} else {
let (block_above, state_id_above) =
args.world.get_block_and_state_id(&args.position.up());
if block_above == &Block::BAMBOO {
let props_above =
BambooLikeProperties::from_state_id(state_id_above, block_above);
props.age = props_above.age;
} else {
return Block::BAMBOO_SAPLING.default_state.id;
}
return Block::BAMBOO_SAPLING.default_state.id;
}
return props.to_state_id(&Block::BAMBOO);
}
Block::AIR.default_state.id
})
return props.to_state_id(&Block::BAMBOO);
}
Block::AIR.default_state.id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
} else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING {
args.world
.set_block_state(
&args.position.down(),
Block::BAMBOO.default_state.id,
BlockFlags::empty(),
)
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
} else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING {
args.world.set_block_state(
&args.position.down(),
Block::BAMBOO.default_state.id,
BlockFlags::empty(),
);
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
let neighbor_block = args.world.get_block(args.neighbor_position);
if args.direction == BlockDirection::Up && neighbor_block == &Block::BAMBOO {
let neighbor_props =
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block);
let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block);
if neighbor_props.age > props.age {
props.age = match props.age {
0 => 1,
_ => 0,
};
return props.to_state_id(args.block);
}
let neighbor_block = args.world.get_block(args.neighbor_position);
if args.direction == BlockDirection::Up && neighbor_block == &Block::BAMBOO {
let neighbor_props =
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block);
let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block);
if neighbor_props.age > props.age {
props.age = match props.age {
0 => 1,
_ => 0,
};
return props.to_state_id(args.block);
}
}
args.state_id
})
}
args.state_id
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::rng().random_range(0..=3) == 0 {
update_leaves_and_grow(args.world.clone(), args.position).await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
if rand::rng().random_range(0..=3) == 0 {
update_leaves_and_grow(args.world, args.position);
}
}
}
async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
fn update_leaves_and_grow(world: &Arc<World>, position: &BlockPos) {
let above_pos = position.up();
let below_pos = position.down();
let two_below_pos = position.down_height(2);
@@ -152,7 +136,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
return;
}
let bamboo_count = count_bamboo_below(&world, position);
let bamboo_count = count_bamboo_below(world, position);
if bamboo_count >= 16 {
return;
}
@@ -178,20 +162,16 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
BambooLikeProperties::from_state_id(state_id_two_below, block_two_below);
props_two_below.leaves = BambooLeaves::None;
world
.set_block_state(
&below_pos,
props_below.to_state_id(block_below),
BlockFlags::NOTIFY_ALL,
)
.await;
world
.set_block_state(
&two_below_pos,
props_two_below.to_state_id(block_two_below),
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
&below_pos,
props_below.to_state_id(block_below),
BlockFlags::NOTIFY_ALL,
);
world.set_block_state(
&two_below_pos,
props_two_below.to_state_id(block_two_below),
BlockFlags::NOTIFY_ALL,
);
}
}
@@ -201,9 +181,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
!((bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15),
);
world
.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
world.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
}
fn count_bamboo_below(world: &World, pos: &BlockPos) -> usize {
@@ -236,12 +214,12 @@ fn count_bamboo_above(world: &World, pos: &BlockPos) -> usize {
bamboo_count
}
async fn bone_meal(world: Arc<World>, position: &BlockPos) {
let bamboo_below = count_bamboo_below(&world, position);
fn bone_meal(world: &Arc<World>, position: &BlockPos) {
let bamboo_below = count_bamboo_below(world, position);
let growth_amount = rand::rng().random_range(1..=2);
for (bamboo_above, _) in (count_bamboo_above(&world, position)..).zip(0..growth_amount) {
for (bamboo_above, _) in (count_bamboo_above(world, position)..).zip(0..growth_amount) {
let current_total_height = bamboo_above + bamboo_below + 1;
// `next_pos` is the topmost bamboo of the stalk, so the free space we grow into is the
@@ -261,7 +239,7 @@ async fn bone_meal(world: Arc<World>, position: &BlockPos) {
return;
}
update_leaves_and_grow(Arc::clone(&world), &next_pos).await;
update_leaves_and_grow(world, &next_pos);
}
}

View File

@@ -9,8 +9,8 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnNeighborUpdateArgs, blocks::plant::PlantBlockBase,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnNeighborUpdateArgs,
blocks::plant::PlantBlockBase,
};
#[pumpkin_block("minecraft:bamboo_sapling")]
@@ -24,71 +24,63 @@ impl BlockBehaviour for BambooSaplingBlock {
&& args.world.get_block_state(&above).is_air()
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
grow_bamboo(args.world, args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
{
grow_bamboo(args.world, args.position);
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
if args.block == &Block::BAMBOO_SAPLING
&& args.world.get_block(&args.position.up()) == &Block::BAMBOO
{
args.world
.set_block_state(
args.position,
Block::BAMBOO.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
args.world.set_block_state(
args.position,
Block::BAMBOO.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
}
})
}
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
return Block::AIR.default_state.id;
}
if args.direction == BlockDirection::Up
&& args.world.get_block(args.neighbor_position) == &Block::BAMBOO
{
return Block::BAMBOO.default_state.id;
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
return Block::AIR.default_state.id;
}
if args.direction == BlockDirection::Up
&& args.world.get_block(args.neighbor_position) == &Block::BAMBOO
{
return Block::BAMBOO.default_state.id;
}
args.state_id
}
fn random_tick<'a>(&'a self, args: crate::block::RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state_above = args.world.get_block_state(&args.position.up());
if !state_above.is_air() || rand::rng().random_range(0..3) > 0 {
return;
}
grow_bamboo(args.world, args.position).await;
})
fn random_tick(&self, args: crate::block::RandomTickArgs<'_>) {
let state_above = args.world.get_block_state(&args.position.up());
if !state_above.is_air() || rand::rng().random_range(0..3) > 0 {
return;
}
grow_bamboo(args.world, args.position);
}
}
async fn grow_bamboo(world: &std::sync::Arc<crate::world::World>, position: &BlockPos) {
fn grow_bamboo(world: &std::sync::Arc<crate::world::World>, position: &BlockPos) {
let mut props =
BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
props.leaves = BambooLeaves::Small;
world
.set_block_state(
&position.up(),
props.to_state_id(&Block::BAMBOO),
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
&position.up(),
props.to_state_id(&Block::BAMBOO),
BlockFlags::NOTIFY_ALL,
);
}
impl PlantBlockBase for BambooSaplingBlock {

View File

@@ -7,8 +7,8 @@ use crate::block::blocks::plant::big_dripleaf_stem::{
};
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnEntityStepArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnEntityStepArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::EntityBase;
use crate::entity::ai::pathfinder::node::Coordinate;
@@ -30,12 +30,12 @@ use rand::RngExt;
pub struct BigDripleafBlock;
impl BlockBehaviour for BigDripleafBlock {
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn on_entity_step(&self, args: OnEntityStepArgs<'_>) {
{
let props = BigDripleafLikeProperties::from_state_id(args.state.id, args.block);
if props.tilt == Tilt::None
&& can_entity_tilt(args.position, args.entity)
&& !block_receives_redstone_power(args.world, args.position).await
&& !block_receives_redstone_power(args.world, args.position)
{
set_tilt_and_schedule_tick(
args.state.id,
@@ -43,96 +43,81 @@ impl BlockBehaviour for BigDripleafBlock {
args.position,
Tilt::Unstable,
None,
)
.await;
);
}
})
}
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let state = args.world.get_block_state(args.position);
if block_receives_redstone_power(args.world, args.position).await {
reset_tilt(state.id, args.world, args.position).await;
} else {
let props =
BigDripleafLikeProperties::from_state_id(state.id, &Block::BIG_DRIPLEAF);
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let state = args.world.get_block_state(args.position);
let props = BigDripleafLikeProperties::from_state_id(state.id, &Block::BIG_DRIPLEAF);
if props.tilt == Tilt::Unstable {
set_tilt_and_schedule_tick(
state.id,
args.world,
args.position,
Tilt::Partial,
Some(Sound::BlockBigDripleafTiltDown),
)
.await;
} else if props.tilt == Tilt::Partial {
set_tilt_and_schedule_tick(
state.id,
args.world,
args.position,
Tilt::Full,
Some(Sound::BlockBigDripleafTiltDown),
)
.await;
} else if props.tilt == Tilt::Full {
reset_tilt(state.id, args.world, args.position).await;
}
}
})
if props.tilt == Tilt::Unstable {
set_tilt_and_schedule_tick(
state.id,
args.world,
args.position,
Tilt::Partial,
Some(Sound::BlockBigDripleafTiltDown),
);
} else if props.tilt == Tilt::Partial {
set_tilt_and_schedule_tick(
state.id,
args.world,
args.position,
Tilt::Full,
Some(Sound::BlockBigDripleafTiltDown),
);
} else if props.tilt == Tilt::Full {
reset_tilt(state.id, args.world, args.position);
}
}
//TODO: onProjectileHit
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let (support_block, support_block_state_id) =
args.world.get_block_and_state_id(&args.position.down());
let facing = if support_block == &Block::BIG_DRIPLEAF {
get_dripleaf_facing_dir(support_block_state_id)
} else {
args.player
.living_entity
.entity
.get_horizontal_facing()
.opposite()
};
let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let (support_block, support_block_state_id) =
args.world.get_block_and_state_id(&args.position.down());
let facing = if support_block == &Block::BIG_DRIPLEAF {
get_dripleaf_facing_dir(support_block_state_id)
} else {
args.player
.living_entity
.entity
.get_horizontal_facing()
.opposite()
};
let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
dripleaf_props.facing = facing;
dripleaf_props.waterlogged = args.replacing.water_source();
dripleaf_props.facing = facing;
dripleaf_props.waterlogged = args.replacing.water_source();
dripleaf_props.to_state_id(args.block)
})
dripleaf_props.to_state_id(args.block)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if block_receives_redstone_power(args.world, args.position).await {
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
{
if block_receives_redstone_power(args.world, args.position) {
let state_id = args.world.get_block_state_id(args.position);
reset_tilt(state_id, args.world, args.position).await;
reset_tilt(state_id, args.world, args.position);
}
})
}
}
/// if leaf is placed on top of another leaf, turn the lower one into a stem.
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn placed(&self, args: PlacedArgs<'_>) {
{
let support_pos = args.position.down();
let (support_block, support_state_id) = args.world.get_block_and_state_id(&support_pos);
if support_block == &Block::BIG_DRIPLEAF {
@@ -145,30 +130,27 @@ impl BlockBehaviour for BigDripleafBlock {
dripleaf_stem_props.facing = old_dripleaf_props.facing;
dripleaf_stem_props.waterlogged = old_dripleaf_props.waterlogged;
args.world
.set_block_state(
&support_pos,
dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM),
BlockFlags::empty(),
)
.await;
args.world.set_block_state(
&support_pos,
dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM),
BlockFlags::empty(),
);
}
})
}
}
/// if the leaf is broken, turn the stem below into a leaf.
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { handle_big_dripleaf_breaking(args.world, args.position).await })
fn broken(&self, args: BrokenArgs<'_>) {
handle_big_dripleaf_breaking(args.world, args.position);
}
}
async fn set_tilt_and_schedule_tick(
fn set_tilt_and_schedule_tick(
state_id: BlockStateId,
world: &Arc<World>,
pos: &BlockPos,
tilt: Tilt,
sound_wrapper: Option<Sound>,
) {
set_tilt(state_id, world, pos, tilt).await;
set_tilt(state_id, world, pos, tilt);
if let Some(tilt_sound) = sound_wrapper {
play_tilt_sound(world, pos, tilt_sound);
}
@@ -186,30 +168,30 @@ async fn set_tilt_and_schedule_tick(
);
}
}
fn play_tilt_sound(world: &Arc<World>, pos: &BlockPos, tilt_sound: Sound) {
let pitch = rand::rng().random_range(0.8f32..1.2f32);
let v = pos.as_vector3();
let position = Vector3::new(v.x as f64, v.y as f64, v.z as f64);
world.play_sound_fine(tilt_sound, SoundCategory::Blocks, &position, 1f32, pitch);
}
async fn reset_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos) {
set_tilt(state_id, world, pos, Tilt::None).await;
fn reset_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos) {
set_tilt(state_id, world, pos, Tilt::None);
let props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
if props.tilt != Tilt::None {
play_tilt_sound(world, pos, Sound::BlockBigDripleafTiltUp);
}
}
async fn set_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos, new_tilt: Tilt) {
fn set_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos, new_tilt: Tilt) {
let mut props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
props.tilt = new_tilt;
world
.set_block_state(
pos,
props.to_state_id(&Block::BIG_DRIPLEAF),
BlockFlags::NOTIFY_ALL,
)
.await;
//todo GameEvents?
world.set_block_state(
pos,
props.to_state_id(&Block::BIG_DRIPLEAF),
BlockFlags::NOTIFY_ALL,
);
}
fn play_tilt_sound(world: &Arc<World>, pos: &BlockPos, tilt_sound: Sound) {
let pitch = rand::rng().random_range(0.8f32..1.2f32);
let v = pos.as_vector3();
let position = Vector3::new(v.x as f64, v.y as f64, v.z as f64);
world.play_sound_fine(tilt_sound, SoundCategory::Blocks, &position, 1f32, pitch);
}
fn can_entity_tilt<T: EntityBase + ?Sized>(pos: &BlockPos, entity: &T) -> bool {
entity.get_entity().on_ground.load(Ordering::Relaxed)
@@ -229,8 +211,7 @@ impl PlantBlockBase for BigDripleafBlock {
let support_block = block_accessor.get_block(pos);
can_plant_dripleaf_on_top(support_block)
}
#[allow(clippy::unused_async_trait_impl)]
async fn get_state_for_neighbor_update(
fn get_state_for_neighbor_update(
&self,
block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos,

View File

@@ -2,9 +2,7 @@ use std::sync::Arc;
use crate::block::blocks::plant::PlantBlockBase;
use crate::block::blocks::plant::big_dripleaf::can_plant_dripleaf_on_top;
use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
};
use crate::block::{BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
use crate::world::World;
use pumpkin_data::Block;
use pumpkin_data::BlockStateId;
@@ -25,22 +23,19 @@ impl BlockBehaviour for BigDripleafStemBlock {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { handle_big_dripleaf_breaking(args.world, args.position).await })
fn broken(&self, args: BrokenArgs<'_>) {
handle_big_dripleaf_breaking(args.world, args.position);
}
}
impl PlantBlockBase for BigDripleafStemBlock {
@@ -49,8 +44,7 @@ impl PlantBlockBase for BigDripleafStemBlock {
can_plant_dripleaf_on_top(support_block)
}
#[allow(clippy::unused_async_trait_impl)]
async fn get_state_for_neighbor_update(
fn get_state_for_neighbor_update(
&self,
block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos,
@@ -69,7 +63,7 @@ impl PlantBlockBase for BigDripleafStemBlock {
block_state
}
}
pub async fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPos) {
pub fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPos) {
let support_pos = position.down();
let (support_block, support_state_id) = world.get_block_and_state_id(&support_pos);
if support_block == &Block::BIG_DRIPLEAF_STEM {
@@ -79,12 +73,10 @@ pub async fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPo
let mut dripleaf_props = BigDripleafLikeProperties::default(&Block::BIG_DRIPLEAF);
dripleaf_props.facing = dripleaf_stem_props.facing;
dripleaf_props.waterlogged = dripleaf_stem_props.waterlogged;
world
.set_block_state(
&support_pos,
dripleaf_props.to_state_id(&Block::BIG_DRIPLEAF),
BlockFlags::empty(),
)
.await;
world.set_block_state(
&support_pos,
dripleaf_props.to_state_id(&Block::BIG_DRIPLEAF),
BlockFlags::empty(),
);
}
}

View File

@@ -2,7 +2,7 @@ use pumpkin_data::BlockId;
use pumpkin_data::BlockStateId;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
blocks::plant::PlantBlockBase,
};
@@ -19,19 +19,16 @@ impl BlockBehaviour for BushBlock {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
}

View File

@@ -10,105 +10,85 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnScheduledTickArgs, RandomTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnScheduledTickArgs, RandomTickArgs,
};
#[pumpkin_block("minecraft:cactus")]
pub struct CactusBlock;
impl BlockBehaviour for CactusBlock {
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_place_at(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let block_up = args.position.up();
if args.world.get_block_state(&block_up).is_air() {
let state = args.world.get_block_state(args.position);
let mut props = CactusLikeProperties::from_state_id(state.id, args.block);
let age = props.age;
let mut i = 1;
while args.world.get_block(&args.position.down_height(i)) == &Block::CACTUS {
i += 1;
if 1 == 3 && age == 15 {
return;
}
}
if age == 8 && can_place_at(args.world.as_ref(), &block_up) {
let d = if i >= 3 { 0.25 } else { 0.1 };
if rand::rng().random_range(0.0..1.0) <= d {
args.world
.set_block_state(
&block_up,
Block::CACTUS_FLOWER.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
}
} else if age == 15 && i < 3 {
args.world
.set_block_state(
&block_up,
Block::CACTUS.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
let mut new_props = CactusLikeProperties::default(&Block::CACTUS);
new_props.age = 0;
args.world
.set_block_state(
args.position,
new_props.to_state_id(&Block::CACTUS),
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
)
.await;
args.world
.update_neighbor(args.position, &Block::CACTUS)
.await;
}
if age < 15 {
props.age = age + 1;
args.world
.set_block_state(
args.position,
props.to_state_id(&Block::CACTUS),
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
)
.await;
fn random_tick(&self, args: RandomTickArgs<'_>) {
let block_up = args.position.up();
if args.world.get_block_state(&block_up).is_air() {
let state = args.world.get_block_state(args.position);
let mut props = CactusLikeProperties::from_state_id(state.id, args.block);
let age = props.age;
let mut i = 1;
while args.world.get_block(&args.position.down_height(i)) == &Block::CACTUS {
i += 1;
if 1 == 3 && age == 15 {
return;
}
}
})
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
args.entity
.damage(args.entity, 1.0, DamageType::CACTUS)
.await;
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
if age == 8 && can_place_at(args.world.as_ref(), &block_up) {
let d = if i >= 3 { 0.25 } else { 0.1 };
if rand::rng().random_range(0.0..1.0) <= d {
args.world.set_block_state(
&block_up,
Block::CACTUS_FLOWER.default_state.id,
BlockFlags::NOTIFY_ALL,
);
}
} else if age == 15 && i < 3 {
args.world.set_block_state(
&block_up,
Block::CACTUS.default_state.id,
BlockFlags::NOTIFY_ALL,
);
let mut new_props = CactusLikeProperties::default(&Block::CACTUS);
new_props.age = 0;
args.world.set_block_state(
args.position,
new_props.to_state_id(&Block::CACTUS),
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
);
}
if age < 15 {
props.age = age + 1;
args.world.set_block_state(
args.position,
props.to_state_id(&Block::CACTUS),
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
);
}
}
}
args.state_id
})
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
{
args.entity.damage(args.entity, 1.0, DamageType::CACTUS);
}
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_place_at(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {

View File

@@ -1,5 +1,5 @@
use crate::block::blocks::plant::PlantBlockBase;
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
use pumpkin_data::BlockStateId;
use pumpkin_data::tag::{self, Taggable};
use pumpkin_data::{Block, BlockDirection, BlockState};
@@ -14,19 +14,16 @@ impl BlockBehaviour for CactusFlowerBlock {
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
}
@@ -43,8 +40,7 @@ impl PlantBlockBase for CactusFlowerBlock {
}
false
}
#[allow(clippy::unused_async_trait_impl)]
async fn get_state_for_neighbor_update(
fn get_state_for_neighbor_update(
&self,
block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos,

View File

@@ -17,8 +17,8 @@ use rand::RngExt;
use super::chorus_plant;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnScheduledTickArgs, RandomTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
RandomTickArgs,
},
world::World,
};
@@ -40,156 +40,144 @@ impl BlockBehaviour for ChorusFlowerBlock {
can_survive(args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if args.direction != BlockDirection::Up && !can_survive(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction != BlockDirection::Up && !can_survive(args.world, args.position) {
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
}
args.state_id
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if !can_survive(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
if !can_survive(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let above = args.position.up();
let max_y = args.world.dimension.min_y + args.world.dimension.height - 1;
if args.world.get_block(&above).default_state.is_air() && above.0.y <= max_y {
let state_id = args.world.get_block_state_id(args.position);
let state_props = ChorusFlowerLikeProperties::from_state_id(state_id, args.block);
let current_age = state_props.age;
if current_age < DEAD_AGE {
let mut grow_upwards = false;
let mut pillar_on_support_block = false;
let below_pos = args.position.down();
let (below_block, _) = args.world.get_block_and_state(&below_pos);
fn random_tick(&self, args: RandomTickArgs<'_>) {
let above = args.position.up();
let max_y = args.world.dimension.min_y + args.world.dimension.height - 1;
if args.world.get_block(&above).default_state.is_air() && above.0.y <= max_y {
let state_id = args.world.get_block_state_id(args.position);
let state_props = ChorusFlowerLikeProperties::from_state_id(state_id, args.block);
let current_age = state_props.age;
if current_age < DEAD_AGE {
let mut grow_upwards = false;
let mut pillar_on_support_block = false;
let below_pos = args.position.down();
let (below_block, _) = args.world.get_block_and_state(&below_pos);
if below_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
grow_upwards = true;
} else if below_block == &Block::CHORUS_PLANT {
let mut height = 1;
for _ in 0..4 {
let test_pos = args.position.offset(Vector3::new(0, -(height + 1), 0));
let (test_block, _) = args.world.get_block_and_state(&test_pos);
if test_block != &Block::CHORUS_PLANT {
if test_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER)
{
pillar_on_support_block = true;
}
break;
if below_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
grow_upwards = true;
} else if below_block == &Block::CHORUS_PLANT {
let mut height = 1;
for _ in 0..4 {
let test_pos = args.position.offset(Vector3::new(0, -(height + 1), 0));
let (test_block, _) = args.world.get_block_and_state(&test_pos);
if test_block != &Block::CHORUS_PLANT {
if test_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
pillar_on_support_block = true;
}
height += 1;
break;
}
let max_chance = if pillar_on_support_block { 5 } else { 4 };
if height < 2 || height <= rand::rng().random_range(0..max_chance) {
grow_upwards = true;
}
} else if below_block.default_state.is_air() {
grow_upwards = true;
height += 1;
}
let above_2 = args.position.offset(Vector3::new(0, 2, 0));
if grow_upwards
&& all_neighbors_empty(args.world.as_ref(), &above, None)
&& args.world.get_block(&above_2).default_state.is_air()
{
let max_chance = if pillar_on_support_block { 5 } else { 4 };
if height < 2 || height <= rand::rng().random_range(0..max_chance) {
grow_upwards = true;
}
} else if below_block.default_state.is_air() {
grow_upwards = true;
}
let above_2 = args.position.offset(Vector3::new(0, 2, 0));
if grow_upwards
&& all_neighbors_empty(args.world.as_ref(), &above, None)
&& args.world.get_block(&above_2).default_state.is_air()
{
let plant_state_id = chorus_plant::get_state_with_connections(
args.world.as_ref(),
&Block::CHORUS_PLANT,
args.position,
);
args.world.set_block_state(
args.position,
plant_state_id,
BlockFlags::NOTIFY_ALL,
);
place_grown_flower(args.world, &above, current_age);
} else if current_age < 4 {
let mut num_branch_attempts = rand::rng().random_range(0..4);
if pillar_on_support_block {
num_branch_attempts += 1;
}
let mut created_branch = false;
for _ in 0..num_branch_attempts {
let direction = HORIZONTAL_DIRECTIONS[rand::rng().random_range(0..4)];
let target = args.position.offset(direction.to_offset());
let target_below = target.down();
if args.world.get_block(&target).default_state.is_air()
&& args.world.get_block(&target_below).default_state.is_air()
&& all_neighbors_empty(
args.world.as_ref(),
&target,
Some(direction.opposite()),
)
{
place_grown_flower(args.world, &target, current_age + 1);
created_branch = true;
}
}
if created_branch {
let plant_state_id = chorus_plant::get_state_with_connections(
args.world.as_ref(),
&Block::CHORUS_PLANT,
args.position,
);
args.world
.set_block_state(args.position, plant_state_id, BlockFlags::NOTIFY_ALL)
.await;
place_grown_flower(args.world, &above, current_age).await;
} else if current_age < 4 {
let mut num_branch_attempts = rand::rng().random_range(0..4);
if pillar_on_support_block {
num_branch_attempts += 1;
}
let mut created_branch = false;
for _ in 0..num_branch_attempts {
let direction = HORIZONTAL_DIRECTIONS[rand::rng().random_range(0..4)];
let target = args.position.offset(direction.to_offset());
let target_below = target.down();
if args.world.get_block(&target).default_state.is_air()
&& args.world.get_block(&target_below).default_state.is_air()
&& all_neighbors_empty(
args.world.as_ref(),
&target,
Some(direction.opposite()),
)
{
place_grown_flower(args.world, &target, current_age + 1).await;
created_branch = true;
}
}
if created_branch {
let plant_state_id = chorus_plant::get_state_with_connections(
args.world.as_ref(),
&Block::CHORUS_PLANT,
args.position,
);
args.world
.set_block_state(
args.position,
plant_state_id,
BlockFlags::NOTIFY_ALL,
)
.await;
} else {
place_dead_flower(args.world, args.position).await;
}
args.world.set_block_state(
args.position,
plant_state_id,
BlockFlags::NOTIFY_ALL,
);
} else {
place_dead_flower(args.world, args.position).await;
place_dead_flower(args.world, args.position);
}
} else {
place_dead_flower(args.world, args.position);
}
}
})
}
}
}
pub async fn place_grown_flower(world: &Arc<World>, pos: &BlockPos, age: u8) {
pub fn place_grown_flower(world: &Arc<World>, pos: &BlockPos, age: u8) {
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
props.age = age;
world
.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
);
world.sync_world_event(WorldEvent::SoundChorusGrow, *pos, 0);
}
pub async fn place_dead_flower(world: &Arc<World>, pos: &BlockPos) {
pub fn place_dead_flower(world: &Arc<World>, pos: &BlockPos) {
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
props.age = DEAD_AGE;
world
.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
)
.await;
world.set_block_state(
pos,
props.to_state_id(&Block::CHORUS_FLOWER),
BlockFlags::NOTIFY_ALL,
);
world.sync_world_event(WorldEvent::SoundChorusDeath, *pos, 0);
}

View File

@@ -11,68 +11,59 @@ use pumpkin_world::{
};
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
};
#[pumpkin_block("minecraft:chorus_plant")]
pub struct ChorusPlantBlock;
impl BlockBehaviour for ChorusPlantBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
// Compute all 6 face connections immediately so the placed block visually connects to its neighbors.
get_state_with_connections(args.world, args.block, args.position)
})
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
// Compute all 6 face connections immediately so the placed block visually connects to its neighbors.
get_state_with_connections(args.world, args.block, args.position)
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_survive(args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
if !can_survive(args.world, args.position) {
// Schedule delayed destruction so the whole plant cascades down.
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
return args.state_id;
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if !can_survive(args.world, args.position) {
// Schedule delayed destruction so the whole plant cascades down.
args.world
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
return args.state_id;
}
// Update the single face connection for the direction that changed.
let neighbor_block = args.world.get_block(args.neighbor_position);
let connect = neighbor_block == &Block::CHORUS_PLANT
|| neighbor_block == &Block::CHORUS_FLOWER
|| (args.direction == BlockDirection::Down
&& neighbor_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_PLANT));
// Update the single face connection for the direction that changed.
let neighbor_block = args.world.get_block(args.neighbor_position);
let connect = neighbor_block == &Block::CHORUS_PLANT
|| neighbor_block == &Block::CHORUS_FLOWER
|| (args.direction == BlockDirection::Down
&& neighbor_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_PLANT));
let mut props =
BrownMushroomBlockLikeProperties::from_state_id(args.state_id, args.block);
match args.direction {
BlockDirection::Down => props.down = connect,
BlockDirection::Up => props.up = connect,
BlockDirection::North => props.north = connect,
BlockDirection::South => props.south = connect,
BlockDirection::East => props.east = connect,
BlockDirection::West => props.west = connect,
}
props.to_state_id(args.block)
})
let mut props = BrownMushroomBlockLikeProperties::from_state_id(args.state_id, args.block);
match args.direction {
BlockDirection::Down => props.down = connect,
BlockDirection::Up => props.up = connect,
BlockDirection::North => props.north = connect,
BlockDirection::South => props.south = connect,
BlockDirection::East => props.east = connect,
BlockDirection::West => props.west = connect,
}
props.to_state_id(args.block)
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// Destroy if unsupported; breaking propagates neighbor-update callbacks
// to connected chorus blocks, which schedule their own ticks.
if !can_survive(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty())
.await;
}
})
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
// Destroy if unsupported; breaking propagates neighbor-update callbacks
// to connected chorus blocks, which schedule their own ticks.
if !can_survive(args.world.as_ref(), args.position) {
args.world
.break_block(args.position, None, BlockFlags::empty());
}
}
}

View File

@@ -8,8 +8,8 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use crate::block::{
BlockBehaviour, BlockFuture, BonemealArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnPlaceArgs, RandomTickArgs,
BlockBehaviour, BonemealArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
RandomTickArgs,
};
use crate::entity::EntityBase;
@@ -53,57 +53,49 @@ impl BlockBehaviour for CocoaBlock {
false
}
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let mut props = CocoaProperties::default(args.block);
props.age = 0;
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = CocoaProperties::default(args.block);
props.age = 0;
let directions = args.player.get_entity().get_entity_facing_order();
for dir in directions {
if let Some(facing) = dir.to_horizontal_facing()
&& Self::can_survive(args.world, args.position, facing)
{
props.facing = facing;
return props.to_state_id(args.block);
}
}
Block::AIR.default_state.id
})
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = CocoaProperties::from_state_id(args.state_id, args.block);
if args.direction == props.facing.to_block_direction()
&& !Self::can_survive(args.world, args.position, props.facing)
let directions = args.player.get_entity().get_entity_facing_order();
for dir in directions {
if let Some(facing) = dir.to_horizontal_facing()
&& Self::can_survive(args.world, args.position, facing)
{
return Block::AIR.default_state.id;
props.facing = facing;
return props.to_state_id(args.block);
}
args.state_id
})
}
Block::AIR.default_state.id
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::random::<u8>().is_multiple_of(5) {
let state_id = args.world.get_block_state_id(args.position);
let mut props = CocoaProperties::from_state_id(state_id, args.block);
if props.age < MAX_AGE {
props.age += 1;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let props = CocoaProperties::from_state_id(args.state_id, args.block);
if args.direction == props.facing.to_block_direction()
&& !Self::can_survive(args.world, args.position, props.facing)
{
return Block::AIR.default_state.id;
}
args.state_id
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
if rand::random::<u8>().is_multiple_of(5) {
let state_id = args.world.get_block_state_id(args.position);
let mut props = CocoaProperties::from_state_id(state_id, args.block);
if props.age < MAX_AGE {
props.age += 1;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
}
})
}
}
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
@@ -115,20 +107,18 @@ impl BlockBehaviour for CocoaBlock {
true
}
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
{
let mut props = CocoaProperties::from_state_id(args.state_id, args.block);
if props.age < MAX_AGE {
props.age += 1;
args.world
.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
)
.await;
args.world.set_block_state(
args.position,
props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL,
);
}
})
}
}
fn rotate(

View File

@@ -6,9 +6,7 @@ use rand::RngExt;
use crate::block::blocks::plant::PlantBlockBase;
use crate::block::blocks::plant::crop::CropBlockBase;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
type BeetrootProperties = NetherWartLikeProperties;
@@ -20,37 +18,30 @@ impl BlockBehaviour for BeetrootBlock {
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::rng().random_range(0..3) == 0 {
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
if rand::rng().random_range(0..3) == 0 {
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
}
}
}

View File

@@ -3,9 +3,7 @@ use pumpkin_macros::pumpkin_block;
use crate::block::blocks::plant::PlantBlockBase;
use crate::block::blocks::plant::crop::CropBlockBase;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
#[pumpkin_block("minecraft:carrots")]
pub struct CarrotBlock;
@@ -15,35 +13,28 @@ impl BlockBehaviour for CarrotBlock {
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
}
}

View File

@@ -7,7 +7,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockAccessor;
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
blocks::plant::PlantBlockBase,
};
@@ -45,27 +45,24 @@ impl BlockBehaviour for AttachedStemBlock {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
let props = AttachedStemProperties::from_state_id(args.state_id, args.block);
if args.direction.to_horizontal_facing() == Some(props.facing)
&& args.neighbor_state_id != Self::get_gourd(args.block).default_state.id
{
let mut props = StemProperties::default(Self::get_stem(args.block));
props.age = 7;
return props.to_state_id(Self::get_stem(args.block));
}
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let props = AttachedStemProperties::from_state_id(args.state_id, args.block);
if args.direction.to_horizontal_facing() == Some(props.facing)
&& args.neighbor_state_id != Self::get_gourd(args.block).default_state.id
{
let mut props = StemProperties::default(Self::get_stem(args.block));
props.age = 7;
return props.to_state_id(Self::get_stem(args.block));
}
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
}

View File

@@ -1,6 +1,5 @@
use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
RandomTickArgs,
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
blocks::plant::{
PlantBlockBase,
crop::{CropBlockBase, get_available_moisture},
@@ -63,90 +62,76 @@ impl BlockBehaviour for StemBlock {
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
let (_, state) = args.world.get_block_and_state_id(args.position);
if StemProperties::from_state_id(state, args.block).age == 7 {
BlockBehaviour::random_tick(
self,
RandomTickArgs {
world: args.world,
block: args.block,
position: args.position,
},
)
.await;
}
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
let (_, state) = args.world.get_block_and_state_id(args.position);
if StemProperties::from_state_id(state, args.block).age == 7 {
BlockBehaviour::random_tick(
self,
RandomTickArgs {
world: args.world,
block: args.block,
position: args.position,
},
);
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
// TODO add light level check
let f: f32 = get_available_moisture(args.world, args.position, args.block).await;
if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 {
let (block, state) = args.world.get_block_and_state_id(args.position);
let props = StemProperties::from_state_id(state, block);
let age = i32::from(props.age);
if age < 7 {
args.world
.set_block_state(
args.position,
Self::state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
} else {
let dir = BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro(
Xoroshiro::from_seed(rand::rng().random()),
));
let plant_block_pos = args.position.offset(dir.to_offset());
let plant_block_state = args.world.get_block_state(&plant_block_pos);
let under_block: &Block = args.world.get_block(&plant_block_pos.down());
if plant_block_state.is_air()
&& (under_block == &Block::FARMLAND
|| under_block.has_tag(&tag::Block::MINECRAFT_DIRT))
{
let attached_stem = Self::get_attached_stem(dir, block);
let gourd = Self::get_gourd(block);
args.world
.set_block_state(
&plant_block_pos,
gourd.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
args.world
.set_block_state(
args.position,
attached_stem,
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
// TODO add light level check
let f: f32 = get_available_moisture(args.world, args.position, args.block);
if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 {
let (block, state) = args.world.get_block_and_state_id(args.position);
let props = StemProperties::from_state_id(state, block);
let age = i32::from(props.age);
if age < 7 {
args.world.set_block_state(
args.position,
Self::state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
);
} else {
let dir = BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro(
Xoroshiro::from_seed(rand::rng().random()),
));
let plant_block_pos = args.position.offset(dir.to_offset());
let plant_block_state = args.world.get_block_state(&plant_block_pos);
let under_block: &Block = args.world.get_block(&plant_block_pos.down());
if plant_block_state.is_air()
&& (under_block == &Block::FARMLAND
|| under_block.has_tag(&tag::Block::MINECRAFT_DIRT))
{
let attached_stem = Self::get_attached_stem(dir, block);
let gourd = Self::get_gourd(block);
args.world.set_block_state(
&plant_block_pos,
gourd.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS,
);
args.world.set_block_state(
args.position,
attached_stem,
BlockFlags::NOTIFY_NEIGHBORS,
);
}
}
})
}
}
}

View File

@@ -10,10 +10,7 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt;
use crate::{
block::blocks::plant::PlantBlockBase, plugin::api::events::block::block_grow::BlockGrowEvent,
world::World,
};
use crate::{block::blocks::plant::PlantBlockBase, world::World};
type CropProperties = WheatLikeProperties;
type FarmlandProperties = FarmlandLikeProperties;
@@ -57,44 +54,25 @@ trait CropBlockBase: PlantBlockBase {
self.get_age(state, block) < self.max_age()
}
async fn perform_bonemeal(&self, world: &Arc<World>, pos: &BlockPos) {
fn perform_bonemeal(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block);
let new_age = (age + self.bonemeal_age_increase()).min(self.max_age());
world
.set_block_state(
pos,
self.state_with_age(block, state, new_age),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world.set_block_state(
pos,
self.state_with_age(block, state, new_age),
BlockFlags::NOTIFY_LISTENERS,
);
}
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block);
if age < self.max_age() {
let f = get_available_moisture(world, pos, block).await;
let f = get_available_moisture(world, pos, block);
if rand::rng().random_range(0..=(25.0 / f).floor() as i64) == 0 {
let mut new_state_id = self.state_with_age(block, state, age + 1);
if let Some(server) = world.server.upgrade() {
let mut event = BlockGrowEvent::new(
world.clone(),
block,
state,
Block::from_state_id(new_state_id),
new_state_id,
*pos,
);
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return;
}
new_state_id = event.new_state_id;
}
world
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
.await;
let new_state_id = self.state_with_age(block, state, age + 1);
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
}
}
}
@@ -102,7 +80,7 @@ trait CropBlockBase: PlantBlockBase {
//TODO add impl for light level
}
pub async fn get_available_moisture(world: &Arc<World>, pos: &BlockPos, block: &Block) -> f32 {
pub fn get_available_moisture(world: &World, pos: &BlockPos, block: &Block) -> f32 {
let mut moisture = 1.0;
let down_pos = pos.down();

View File

@@ -12,7 +12,7 @@ use rand::RngExt;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
blocks::plant::{PlantBlockBase, crop::CropBlockBase},
},
world::World,
@@ -26,25 +26,20 @@ impl BlockBehaviour for NetherWartBlock {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
}
}
@@ -79,17 +74,15 @@ impl CropBlockBase for NetherWartBlock {
props.to_state_id(block)
}
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block);
if age < self.max_age() && rand::rng().random_range(0..10) == 0 {
world
.set_block_state(
pos,
self.state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
world.set_block_state(
pos,
self.state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
);
}
}
}

View File

@@ -3,9 +3,7 @@ use pumpkin_macros::pumpkin_block;
use crate::block::blocks::plant::PlantBlockBase;
use crate::block::blocks::plant::crop::CropBlockBase;
use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
};
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
#[pumpkin_block("minecraft:potatoes")]
pub struct PotatoBlock;
@@ -15,35 +13,28 @@ impl BlockBehaviour for PotatoBlock {
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
}
}

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use crate::{
block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
OnEntityCollisionArgs, RandomTickArgs, UseWithItemArgs,
blocks::plant::{PlantBlockBase, crop::CropBlockBase},
registry::BlockActionResult,
@@ -31,127 +31,101 @@ impl BlockBehaviour for SweetBerryBushBlock {
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
}
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
})
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
}
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block);
match props.age {
2 | 3 => {
let index = props.age;
props.age = 1;
let count: u8 = rand::rng().random_range((index - 1)..=(index));
for _ in 0..count {
args.world
.drop_stack(
args.position,
ItemStack::new(1, &Item::SWEET_BERRIES), //
)
.await;
}
args.world
.set_block_state(
args.position,
props.to_state_id(&Block::SWEET_BERRY_BUSH),
BlockFlags::NOTIFY_ALL,
)
.await;
BlockActionResult::SuccessServer
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let state_id = args.world.get_block_state_id(args.position);
let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block);
match props.age {
2 | 3 => {
let index = props.age;
props.age = 1;
let count: u8 = rand::rng().random_range((index - 1)..=(index));
for _ in 0..count {
args.world.drop_stack(
args.position,
ItemStack::new(1, &Item::SWEET_BERRIES), //
);
}
_ => BlockActionResult::Pass,
args.world.set_block_state(
args.position,
props.to_state_id(&Block::SWEET_BERRY_BUSH),
BlockFlags::NOTIFY_ALL,
);
BlockActionResult::SuccessServer
}
})
_ => BlockActionResult::Pass,
}
}
fn use_with_item<'a>(
&'a self,
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL {
BlockActionResult::Pass
} else {
BlockActionResult::PassToDefaultBlockAction
}
})
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
let state_id = args.world.get_block_state_id(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL {
BlockActionResult::Pass
} else {
BlockActionResult::PassToDefaultBlockAction
}
}
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
}
fn get_state_for_neighbor_update<'a>(
&'a self,
args: GetStateForNeighborUpdateArgs<'a>,
) -> BlockFuture<'a, BlockStateId> {
Box::pin(async move {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
.await
})
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
<Self as PlantBlockBase>::get_state_for_neighbor_update(
self,
args.world,
args.position,
args.state_id,
)
}
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let entity = args.entity.get_entity();
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
let entity = args.entity.get_entity();
let living_entity_opt = args.entity.get_living_entity();
let Some(living_entity) = living_entity_opt else {
return;
};
if entity.entity_type == &EntityType::FOX || entity.entity_type == &EntityType::BEE {
return;
}
entity
.slow_movement(args.state, Vector3::new(0.8, 0.75, 0.8))
.await;
let mov = if living_entity.is_player() {
living_entity.get_movement()
} else {
entity.last_pos.load() - entity.pos.load()
};
let living_entity_opt = args.entity.get_living_entity();
let Some(living_entity) = living_entity_opt else {
return;
};
if entity.entity_type == &EntityType::FOX || entity.entity_type == &EntityType::BEE {
return;
}
entity.slow_movement(args.state, Vector3::new(0.8, 0.75, 0.8));
let mov = if living_entity.is_player() {
living_entity.get_movement()
} else {
entity.last_pos.load() - entity.pos.load()
};
let state_id = args.world.get_block_state_id(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
if props.age == 0 {
return;
}
let state_id = args.world.get_block_state_id(args.position);
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
if props.age == 0 {
return;
}
if mov.horizontal_length_squared() <= 0.0
|| (mov.x.abs() < 0.003 && mov.z.abs() < 0.003)
{
return;
}
if mov.horizontal_length_squared() <= 0.0 || (mov.x.abs() < 0.003 && mov.z.abs() < 0.003) {
return;
}
args.entity
.damage(args.entity, 1.0, DamageType::SWEET_BERRY_BUSH)
.await;
})
args.entity
.damage(args.entity, 1.0, DamageType::SWEET_BERRY_BUSH);
}
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
if rand::rng().random_range(0..5) == 0 {
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
}
})
fn random_tick(&self, args: RandomTickArgs<'_>) {
if rand::rng().random_range(0..5) == 0 {
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
}
}
}
impl PlantBlockBase for SweetBerryBushBlock {
#[allow(clippy::unused_async_trait_impl)]
async fn get_state_for_neighbor_update(
fn get_state_for_neighbor_update(
&self,
block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos,
@@ -192,7 +166,7 @@ impl CropBlockBase for SweetBerryBushBlock {
props.to_state_id(block)
}
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
let (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block);
if age < self.max_age() {
@@ -201,13 +175,11 @@ impl CropBlockBase for SweetBerryBushBlock {
if state_above.is_full_cube() || state_above.is_solid() {
return;
}
world
.set_block_state(
pos,
self.state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
world.set_block_state(
pos,
self.state_with_age(block, state, age + 1),
BlockFlags::NOTIFY_NEIGHBORS,
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More