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 24442 additions and 28049 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::block::{BlockFuture, GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs}; use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{ use crate::block::{
registry::BlockActionResult, registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs}, {BlockBehaviour, NormalUseArgs},
@ -50,54 +50,47 @@ impl ScreenHandlerFactory for BarrelScreenFactory {
pub struct BarrelBlock; pub struct BarrelBlock;
impl BlockBehaviour for BarrelBlock { impl BlockBehaviour for BarrelBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = BarrelLikeProperties::default(args.block);
let mut props = BarrelLikeProperties::default(args.block); props.facing = args.player.get_entity().get_facing().opposite();
props.facing = args.player.get_entity().get_facing().opposite(); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
if let Some(block_entity) = args.world.get_block_entity(args.position) && let Some(inventory) = block_entity.get_inventory()
&& let Some(inventory) = block_entity.get_inventory() {
{ args.player.increment_stat(
args.player pumpkin_data::statistic::StatisticCategory::Custom,
.increment_stat( pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
pumpkin_data::statistic::StatisticCategory::Custom, 1,
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; .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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let barrel_block_entity = BarrelBlockEntity::new(*args.position);
let barrel_block_entity = BarrelBlockEntity::new(*args.position); args.world.add_block_entity(Arc::new(barrel_block_entity));
args.world.add_block_entity(Arc::new(barrel_block_entity));
})
} }
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, if let Some(block_entity) = args.world.get_block_entity(args.position)
args: GetComparatorOutputArgs<'a>, && let Some(inventory) = block_entity.get_inventory()
) -> BlockFuture<'a, Option<u8>> { {
Box::pin(async move { Some(crate::block::calculate_comparator_output(
if let Some(block_entity) = args.world.get_block_entity(args.position) inventory.as_ref(),
&& let Some(inventory) = block_entity.get_inventory() ))
{ } else {
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await) None
} 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::BlockStateId;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{
BlockProperties, MangroveRootsLikeProperties as BarrierLikeProperties, BlockProperties, MangroveRootsLikeProperties as BarrierLikeProperties,
@ -11,29 +11,25 @@ use pumpkin_world::tick::TickPriority;
pub struct BarrierBlock; pub struct BarrierBlock;
impl BlockBehaviour for BarrierBlock { impl BlockBehaviour for BarrierBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = BarrierLikeProperties::default(args.block);
let mut props = BarrierLikeProperties::default(args.block); props.waterlogged = args.replacing.water_source();
props.waterlogged = args.replacing.water_source(); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { let props = BarrierLikeProperties::from_state_id(args.state_id, args.block);
let props = BarrierLikeProperties::from_state_id(args.state_id, args.block); if props.waterlogged {
if props.waterlogged { args.world.schedule_fluid_tick(
args.world.schedule_fluid_tick( &Fluid::WATER,
&Fluid::WATER, *args.position,
*args.position, Fluid::WATER.flow_speed as u8,
Fluid::WATER.flow_speed as u8, TickPriority::Normal,
TickPriority::Normal, );
); }
} props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
} }

View File

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

View File

@ -13,7 +13,6 @@ use pumpkin_util::GameMode;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use crate::block::BlockFuture;
use crate::block::OnLandedUponArgs; use crate::block::OnLandedUponArgs;
use crate::block::UpdateEntityMovementAfterFallOnArgs; use crate::block::UpdateEntityMovementAfterFallOnArgs;
use crate::block::bounce_entity_after_fall; use crate::block::bounce_entity_after_fall;
@ -22,7 +21,7 @@ use crate::block::{
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, OnStateReplacedArgs, BlockBehaviour, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, OnStateReplacedArgs,
PlacedArgs, PlayerPlacedArgs, PlacedArgs, PlayerPlacedArgs,
}; };
use crate::entity::{Entity, EntityBase}; use crate::entity::{Entity, EntityBase, player::Player};
use crate::world::World; use crate::world::World;
type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties; type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties;
@ -84,36 +83,27 @@ impl BlockBehaviour for BedBlock {
false false
} }
fn on_landed_upon<'a>(&'a self, args: OnLandedUponArgs<'a>) -> BlockFuture<'a, ()> { fn on_landed_upon(&self, args: OnLandedUponArgs<'_>) {
Box::pin(async move { if let Some(living) = args.entity.get_living_entity() {
if let Some(living) = args.entity.get_living_entity() { living.handle_fall_damage(args.entity, args.fall_distance * 0.5, 1.0);
living }
.handle_fall_damage(args.entity, args.fall_distance * 0.5, 1.0)
.await;
}
})
} }
fn update_entity_movement_after_fall_on<'a>( fn update_entity_movement_after_fall_on(&self, args: UpdateEntityMovementAfterFallOnArgs<'_>) {
&'a self, bounce_entity_after_fall(args.entity, 0.66);
args: UpdateEntityMovementAfterFallOnArgs<'a>,
) -> BlockFuture<'a, ()> {
Box::pin(async move { bounce_entity_after_fall(args.entity, 0.66) })
} }
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut bed_props = BedProperties::default(args.block);
let mut bed_props = BedProperties::default(args.block);
bed_props.facing = args.player.get_entity().get_horizontal_facing(); bed_props.facing = args.player.get_entity().get_horizontal_facing();
bed_props.part = BedPart::Foot; 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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let bed_entity = BedBlockEntity::new(*args.position); let bed_entity = BedBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(bed_entity)); args.world.add_block_entity(Arc::new(bed_entity));
@ -122,283 +112,275 @@ impl BlockBehaviour for BedBlock {
bed_head_props.part = BedPart::Head; bed_head_props.part = BedPart::Head;
let bed_head_pos = args.position.offset(bed_head_props.facing.to_offset()); let bed_head_pos = args.position.offset(bed_head_props.facing.to_offset());
args.world args.world.set_block_state(
.set_block_state( &bed_head_pos,
&bed_head_pos, bed_head_props.to_state_id(args.block),
bed_head_props.to_state_id(args.block), BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK, );
)
.await;
let bed_head_entity = BedBlockEntity::new(bed_head_pos); let bed_head_entity = BedBlockEntity::new(bed_head_pos);
args.world.add_block_entity(Arc::new(bed_head_entity)); args.world.add_block_entity(Arc::new(bed_head_entity));
}) }
} }
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> { fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
Box::pin(async move { {
args.world.play_bedrock_level_sound( args.world.play_bedrock_level_sound(
"place", "place",
&args.position.to_centered_f64(), &args.position.to_centered_f64(),
i32::from(pumpkin_data::BlockState::to_be_network_id(args.state_id)), i32::from(pumpkin_data::BlockState::to_be_network_id(args.state_id)),
); );
}) }
} }
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { let bed_props = BedProperties::from_state_id(args.state.id, args.block);
let bed_props = BedProperties::from_state_id(args.state.id, args.block); let other_half_pos = if bed_props.part == BedPart::Head {
let other_half_pos = if bed_props.part == BedPart::Head { args.position
args.position .offset(bed_props.facing.opposite().to_offset())
.offset(bed_props.facing.opposite().to_offset()) } else {
} else { args.position.offset(bed_props.facing.to_offset())
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); let is_creative = args.player.gamemode.load() == GameMode::Creative;
if neighbor_state_id.to_block_id() != args.block.id { let flags = if bed_props.part == BedPart::Foot && !is_creative {
args.world.update_neighbors(&other_half_pos, None).await; // Breaking foot in survival -> allow head to drop
return; 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; args.world
let flags = if bed_props.part == BedPart::Foot && !is_creative { .break_block(&other_half_pos, Some(args.player.clone()), flags);
// 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;
})
} }
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> { fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
Box::pin(async move { if args.moved {
if args.moved { return;
return; }
}
// If the block is being replaced with air (i.e., broken), the `broken` callback // 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 // 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). // 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_state_id = args.world.get_block_state_id(args.position);
let new_block = Block::from_state_id(new_state_id); let new_block = Block::from_state_id(new_state_id);
if new_block == &Block::AIR { if new_block == &Block::AIR {
return; return;
} }
let bed_props = BedProperties::from_state_id(args.old_state_id, args.block); let bed_props = BedProperties::from_state_id(args.old_state_id, args.block);
let other_half_pos = if bed_props.part == BedPart::Head { let other_half_pos = if bed_props.part == BedPart::Head {
args.position args.position
.offset(bed_props.facing.opposite().to_offset()) .offset(bed_props.facing.opposite().to_offset())
} else { } else {
args.position.offset(bed_props.facing.to_offset()) args.position.offset(bed_props.facing.to_offset())
}; };
let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos); let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos);
if other_block == args.block { if other_block == args.block {
let other_props = BedProperties::from_state_id(other_state.id, other_block); let other_props = BedProperties::from_state_id(other_state.id, other_block);
if other_props.part != bed_props.part { if other_props.part != bed_props.part {
args.world args.world.break_block(
.break_block( &other_half_pos,
&other_half_pos, None,
None, BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS, );
)
.await;
}
} }
}) }
} }
#[expect(clippy::too_many_lines)] fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { let player = Arc::clone(args.player);
Box::pin(async move { let world = Arc::clone(args.world);
let state_id = args.world.get_block_state_id(args.position); let block_id = args.block.id;
let bed_props = BedProperties::from_state_id(state_id, args.block); let position = *args.position;
tokio::spawn(async move {
let (bed_head_pos, bed_foot_pos) = if bed_props.part == BedPart::Head { let block = Block::from_id(block_id);
( Self::use_bed(&world, &player, block, &position).await;
*args.position, });
args.position BlockActionResult::SuccessServer
.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
})
} }
} }
impl BedBlock { 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, occupied: bool,
world: &Arc<World>, world: &Arc<World>,
block: &Block, block: &Block,
@ -407,13 +389,11 @@ impl BedBlock {
) { ) {
let mut bed_props = BedProperties::from_state_id(state_id, block); let mut bed_props = BedProperties::from_state_id(state_id, block);
bed_props.occupied = occupied; bed_props.occupied = occupied;
world world.set_block_state(
.set_block_state( block_pos,
block_pos, bed_props.to_state_id(block),
bed_props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
)
.await;
let other_half_pos = if bed_props.part == BedPart::Head { let other_half_pos = if bed_props.part == BedPart::Head {
block_pos.offset(bed_props.facing.opposite().to_offset()) block_pos.offset(bed_props.facing.opposite().to_offset())
@ -425,19 +405,23 @@ impl BedBlock {
} else { } else {
BedPart::Head BedPart::Head
}; };
world world.set_block_state(
.set_block_state( &other_half_pos,
&other_half_pos, bed_props.to_state_id(block),
bed_props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
)
.await;
} }
} }
async fn can_sleep(world: &Arc<World>) -> bool { fn can_sleep(world: &Arc<World>) -> bool {
let time = world.level_time.lock().await; let time = world
let weather = world.weather.lock().await; .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 { if weather.thundering {
true 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::BlockId;
use pumpkin_data::block_properties::{BeeNestLikeProperties, BlockProperties}; use pumpkin_data::block_properties::{BeeNestLikeProperties, BlockProperties};
@ -11,14 +11,11 @@ impl BlockMetadata for BeehiveBlock {
} }
impl BlockBehaviour for BeehiveBlock { impl BlockBehaviour for BeehiveBlock {
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, {
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let props = BeeNestLikeProperties::from_state_id(state_id, args.block); let props = BeeNestLikeProperties::from_state_id(state_id, args.block);
Some(props.honey_level) Some(props.honey_level)
}) }
} }
} }

View File

@ -21,8 +21,8 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult, PlacedArgs, registry::BlockActionResult,
}, },
entity::experience_orb::ExperienceOrbEntity, entity::experience_orb::ExperienceOrbEntity,
}; };
@ -83,83 +83,74 @@ impl ScreenHandlerFactory for BlastingFurnaceScreenFactory {
pub struct BlastFurnaceBlock; pub struct BlastFurnaceBlock;
impl BlockBehaviour for BlastFurnaceBlock { impl BlockBehaviour for BlastFurnaceBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
if let Some(block_entity) = args.world.get_block_entity(args.position) && let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(inventory) = block_entity.clone().get_inventory() && let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate() && let Some(experience_container) = block_entity.to_experience_container()
&& let Some(experience_container) = block_entity.to_experience_container() {
{ args.player.increment_stat(
args.player pumpkin_data::statistic::StatisticCategory::Custom,
.increment_stat( pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
pumpkin_data::statistic::StatisticCategory::Custom, 1,
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32, );
1, let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
) inventory,
.await; property_delegate,
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new( experience_container,
inventory, );
property_delegate, let player = Arc::clone(args.player);
experience_container, let pos = *args.position;
); tokio::spawn(async move {
args.player player
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position)) .open_handled_screen(&blasting_furnace_screen_factory, Some(pos))
.await; .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> { fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
let mut props = FurnaceLikeProperties::default(args.block); && let Some(inventory) = block_entity.get_inventory()
props.facing = args {
.player Some(crate::block::calculate_comparator_output(
.living_entity inventory.as_ref(),
.entity ))
.get_horizontal_facing() } else {
.opposite(); None
}
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
}
})
} }
} }

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::block::{BlockFuture, GetComparatorOutputArgs, PlacedArgs}; use crate::block::{GetComparatorOutputArgs, PlacedArgs};
use crate::block::{ use crate::block::{
registry::BlockActionResult, registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs}, {BlockBehaviour, NormalUseArgs},
@ -49,55 +49,48 @@ impl ScreenHandlerFactory for BrewingScreenFactory {
pub struct BrewingStandBlock; pub struct BrewingStandBlock;
impl BlockBehaviour for BrewingStandBlock { impl BlockBehaviour for BrewingStandBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
if let Some(block_entity) = args.world.get_block_entity(args.position) && let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(inventory) = block_entity.clone().get_inventory() && let Some(pd) = block_entity.clone().to_property_delegate()
&& let Some(pd) = block_entity.clone().to_property_delegate() {
{ args.player.increment_stat(
args.player pumpkin_data::statistic::StatisticCategory::Custom,
.increment_stat( pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
pumpkin_data::statistic::StatisticCategory::Custom, 1,
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; .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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let be = BrewingStandBlockEntity::new(*args.position);
let be = BrewingStandBlockEntity::new(*args.position); args.world.add_block_entity(Arc::new(be));
args.world.add_block_entity(Arc::new(be));
})
} }
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, if let Some(block_entity) = args.world.get_block_entity(args.position)
args: GetComparatorOutputArgs<'a>, && let Some(inventory) = block_entity.get_inventory()
) -> BlockFuture<'a, Option<u8>> { {
Box::pin(async move { let mut bottles = 0u8;
if let Some(block_entity) = args.world.get_block_entity(args.position) // Bottle slots are 0, 1, 2 in brewing stands
&& let Some(inventory) = block_entity.get_inventory() for slot in 0..3 {
{ let stack = futures::executor::block_on(inventory.get_stack(slot));
let mut bottles = 0u8; if !stack.is_empty() {
// Bottle slots are 0, 1, 2 in brewing stands bottles += 1;
for slot in 0..3 {
let stack = inventory.get_stack(slot).await;
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 pumpkin_world::world::BlockFlags;
use crate::block::entities::brushable_block::BrushableBlockBlockEntity; use crate::block::entities::brushable_block::BrushableBlockBlockEntity;
use crate::block::{ use crate::block::{BlockBehaviour, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs};
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs,
};
pub struct BrushableBlock; pub struct BrushableBlock;
@ -38,7 +36,7 @@ impl BrushableBlock {
if *hits >= 4 { if *hits >= 4 {
let item = brush_be.item.lock().await.take(); let item = brush_be.item.lock().await.take();
if let Some(item_stack) = item { 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 { let target_block = if is_gravel {
@ -47,9 +45,7 @@ impl BrushableBlock {
&Block::SAND &Block::SAND
}; };
world world.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL);
.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL)
.await;
let sound = if is_gravel { let sound = if is_gravel {
Sound::ItemBrushBrushingGravelComplete Sound::ItemBrushBrushingGravelComplete
@ -60,9 +56,7 @@ impl BrushableBlock {
world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64()); world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64());
} else { } else {
props.dusted = (*hits as u8).min(3); props.dusted = (*hits as u8).min(3);
world world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
let sound = if is_gravel { let sound = if is_gravel {
Sound::ItemBrushBrushingGravel Sound::ItemBrushBrushingGravel
@ -77,28 +71,22 @@ impl BrushableBlock {
} }
impl BlockBehaviour for BrushableBlock { impl BlockBehaviour for BrushableBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let props = SuspiciousSandLikeProperties::default(args.block);
let props = SuspiciousSandLikeProperties::default(args.block); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let entity = BrushableBlockBlockEntity::new(*args.position);
let entity = BrushableBlockBlockEntity::new(*args.position); args.world.add_block_entity(Arc::new(entity));
args.world.add_block_entity(Arc::new(entity));
})
} }
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { if let Some(be) = args.world.get_block_entity(args.position)
if let Some(be) = args.world.get_block_entity(args.position) && let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>()
&& let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>() && let Some(contained) = brush_be.item.blocking_lock().take()
&& let Some(contained) = brush_be.item.lock().await.take() {
{ args.world.drop_stack(args.position, contained);
args.world.drop_stack(args.position, contained).await; }
}
})
} }
} }

View File

@ -10,7 +10,7 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs, BlockBehaviour, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs,
OnScheduledTickArgs, PlacedArgs, OnScheduledTickArgs, PlacedArgs,
}; };
use crate::world::World; use crate::world::World;
@ -161,8 +161,8 @@ fn bubble_column_velocity(
} }
impl BlockBehaviour for BubbleColumnBlock { impl BlockBehaviour for BubbleColumnBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
Box::pin(async move { {
if args.block != &Block::BUBBLE_COLUMN { if args.block != &Block::BUBBLE_COLUMN {
return; return;
} }
@ -179,19 +179,19 @@ impl BlockBehaviour for BubbleColumnBlock {
if let Some(player) = args.entity.get_player() { if let Some(player) = args.entity.get_player() {
player.breath_manager.reset(player); player.breath_manager.reset(player);
} }
}) }
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
if args.block == &Block::WATER && is_source_water(args.world, *args.position) { if args.block == &Block::WATER && is_source_water(args.world, *args.position) {
schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS); schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
} }
}) }
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { {
let state = args.world.get_block_state_id(args.position); let state = args.world.get_block_state_id(args.position);
if args.block == &Block::BUBBLE_COLUMN { if args.block == &Block::BUBBLE_COLUMN {
schedule_reconcile(args.world, *args.position, REMOVE_DELAY_TICKS); 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); schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
} }
}) }
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { let state = args.world.get_block_state_id(args.position);
let state = args.world.get_block_state_id(args.position); let block = Block::from_state_id(state);
let block = Block::from_state_id(state); if block != &Block::BUBBLE_COLUMN && block != &Block::WATER {
if block != &Block::BUBBLE_COLUMN && block != &Block::WATER { return;
return; }
}
let below_pos = args.position.down(); let below_pos = args.position.down();
let below_state = args.world.get_block_state_id(&below_pos); let below_state = args.world.get_block_state_id(&below_pos);
let below_block = Block::from_state_id(below_state); let below_block = Block::from_state_id(below_state);
match reconcile_action(block, state, below_block, below_state) { match reconcile_action(block, state, below_block, below_state) {
ReconcileAction::SetBubble(kind) => { ReconcileAction::SetBubble(kind) => {
let new_state = bubble_column_state(kind); let new_state = bubble_column_state(kind);
args.world args.world
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL) .set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL);
.await; schedule_reconcile(args.world, args.position.up(), CREATE_DELAY_TICKS);
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 => {}
} }
}) 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::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetComparatorOutputArgs, BlockBehaviour, CanPlaceAtArgs, GetComparatorOutputArgs, GetStateForNeighborUpdateArgs,
GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, UseWithItemArgs,
UseWithItemArgs, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
}, },
entity::player::Player, entity::player::Player,
world::World, world::World,
@ -21,13 +21,11 @@ use pumpkin_world::{
tick::TickPriority, tick::TickPriority,
world::{BlockAccessor, BlockFlags}, world::{BlockAccessor, BlockFlags},
}; };
use rand::{RngExt, rng};
#[pumpkin_block("minecraft:cake")] #[pumpkin_block("minecraft:cake")]
pub struct CakeBlock; pub struct CakeBlock;
impl CakeBlock { impl CakeBlock {
pub async fn consume_if_hungry( pub fn consume_if_hungry(
world: &Arc<World>, world: &Arc<World>,
player: &Player, player: &Player,
block: &Block, block: &Block,
@ -45,7 +43,7 @@ impl CakeBlock {
.hunger_manager .hunger_manager
.saturation .saturation
.store(player.hunger_manager.saturation.load() + 0.4); .store(player.hunger_manager.saturation.load() + 0.4);
player.send_health().await; player.send_health();
} }
GameMode::Creative | GameMode::Spectator => {} GameMode::Creative | GameMode::Spectator => {}
} }
@ -53,38 +51,30 @@ impl CakeBlock {
let mut properties = CakeLikeProperties::from_state_id(state_id, block); let mut properties = CakeLikeProperties::from_state_id(state_id, block);
match properties.bites { match properties.bites {
0..=5 => { 0..=5 => {
player player.increment_stat(
.increment_stat( pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::StatisticCategory::Custom, pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32, 1,
1, );
)
.await;
properties.bites += 1; properties.bites += 1;
world world.set_block_state(
.set_block_state( location,
location, properties.to_state_id(block),
properties.to_state_id(block), BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
)
.await;
BlockActionResult::Consume BlockActionResult::Consume
} }
6 => { 6 => {
player player.increment_stat(
.increment_stat( pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::StatisticCategory::Custom, pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32, 1,
1, );
) world.set_block_state(
.await; location,
world Block::AIR.default_state.id,
.set_block_state( BlockFlags::NOTIFY_ALL,
location, );
Block::AIR.default_state.id,
BlockFlags::NOTIFY_ALL,
)
.await;
BlockActionResult::Consume BlockActionResult::Consume
} }
_ => BlockActionResult::Pass, _ => BlockActionResult::Pass,
@ -93,113 +83,83 @@ impl CakeBlock {
} }
impl BlockBehaviour for CakeBlock { impl BlockBehaviour for CakeBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { if !can_place_at(args.world, args.position) {
if !can_place_at(args.world, args.position) { return Block::AIR.default_state.id;
return Block::AIR.default_state.id; }
} Block::CAKE.default_state.id
Block::CAKE.default_state.id
})
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position) can_place_at(args.block_accessor, args.position)
} }
fn use_with_item<'a>( fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
&'a self, let state_id = args.world.get_block_state_id(args.position);
args: UseWithItemArgs<'a>, let properties = CakeLikeProperties::from_state_id(state_id, args.block);
) -> BlockFuture<'a, BlockActionResult> { let item = args.item_stack.item;
Box::pin(async move { match item.id {
let state_id = args.world.get_block_state_id(args.position); id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
let properties = CakeLikeProperties::from_state_id(state_id, args.block); if properties.bites != 0 {
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
}
_ => {
return Self::consume_if_hungry( return Self::consume_if_hungry(
args.world, args.world,
args.player, args.player,
args.block, args.block,
args.position, args.position,
state_id, 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> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { let state_id = args.world.get_block_state_id(args.position);
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)
Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
.await
})
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { if !can_place_at(args.world.as_ref(), args.position) {
if !can_place_at(args.world.as_ref(), args.position) { args.world
args.world .break_block(args.position, None, BlockFlags::empty());
.break_block(args.position, None, BlockFlags::empty()) }
.await;
}
})
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if !can_place_at(args.world, args.position) {
if !can_place_at(args.world, args.position) { args.world
args.world .schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal); }
} args.state_id
args.state_id
})
} }
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, {
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let properties = CakeLikeProperties::from_state_id(state_id, args.block); let properties = CakeLikeProperties::from_state_id(state_id, args.block);
if properties.bites <= 6 { if properties.bites <= 6 {
@ -207,7 +167,7 @@ impl BlockBehaviour for CakeBlock {
} else { } else {
Some(0) Some(0)
} }
}) }
} }
} }

View File

@ -12,8 +12,8 @@ use pumpkin_world::tick::TickPriority;
use crate::block::entities::campfire::CampfireBlockEntity; use crate::block::entities::campfire::CampfireBlockEntity;
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs, BlockBehaviour, BlockIsReplacing, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
OnEntityCollisionArgs, OnPlaceArgs, PlacedArgs, OnPlaceArgs, PlacedArgs,
}, },
entity::EntityBase, entity::EntityBase,
}; };
@ -23,21 +23,24 @@ use std::sync::Arc;
pub struct CampfireBlock; pub struct CampfireBlock;
impl BlockBehaviour for CampfireBlock { impl BlockBehaviour for CampfireBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let entity = CampfireBlockEntity::new(*args.position); let entity = CampfireBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity)); args.world.add_block_entity(Arc::new(entity));
}) }
} }
// TODO: cooking food on campfire (CampfireBlockEntity) // TODO: cooking food on campfire (CampfireBlockEntity)
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
Box::pin(async move { {
if CampfireLikeProperties::from_state_id(args.state.id, args.block).lit if CampfireLikeProperties::from_state_id(args.state.id, args.block).lit
&& let Some(living_entity) = args.entity.get_living_entity() && let Some(living_entity) = args.entity.get_living_entity()
{ {
let has_frost_walker_enchantment = { 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
.equipment .equipment
.get(&EquipmentSlot::FEET) .get(&EquipmentSlot::FEET)
@ -47,7 +50,6 @@ impl BlockBehaviour for CampfireBlock {
}; };
let has_fire_res = living_entity let has_fire_res = living_entity
.get_effect(&StatusEffect::FIRE_RESISTANCE) .get_effect(&StatusEffect::FIRE_RESISTANCE)
.await
.is_some(); .is_some();
if has_frost_walker_enchantment || has_fire_res { 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 //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 1.0
}; };
args.entity args.entity
.damage(args.entity, damage_amount, DamageType::CAMPFIRE) .damage(args.entity, damage_amount, DamageType::CAMPFIRE);
.await;
} }
}) }
} }
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_));
let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_)); let mut props =
let mut props = CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block);
CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block); props.waterlogged = is_replacing_water;
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 = props.signal_fire =
is_signal_fire_base_block(args.world.get_block(&args.position.down())); is_signal_fire_base_block(args.world.get_block(args.neighbor_position));
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<'a>( props.to_state_id(args.block)
&'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)
})
} }
// TODO: onProjectileHit // TODO: onProjectileHit

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,9 +6,8 @@ use crate::command::CommandSender;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, NormalUseArgs, BlockBehaviour, BlockMetadata, CanPlaceAtArgs, NormalUseArgs, OnNeighborUpdateArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, registry::BlockActionResult,
registry::BlockActionResult,
}, },
server::Server, server::Server,
world::World, world::World,
@ -30,7 +29,7 @@ impl CommandBlock {
dir: Facing, dir: Facing,
) -> Option<(BlockPos, CommandBlockLikeProperties)> { ) -> Option<(BlockPos, CommandBlockLikeProperties)> {
let target_pos = pos.offset(dir.to_block_direction().to_offset()); 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 = [ let allowed_blocks = [
Block::COMMAND_BLOCK.name, Block::COMMAND_BLOCK.name,
@ -41,7 +40,6 @@ impl CommandBlock {
return None; return None;
} }
let state_id = world.get_block_state_id(&target_pos);
let props = CommandBlockLikeProperties::from_state_id(state_id, block); let props = CommandBlockLikeProperties::from_state_id(state_id, block);
Some((target_pos, props)) Some((target_pos, props))
@ -161,7 +159,7 @@ impl CommandBlock {
if !command_blocks_work { if !command_blocks_work {
return; 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 { if block.id != Block::CHAIN_COMMAND_BLOCK.id {
break; break;
@ -178,13 +176,16 @@ impl CommandBlock {
}; };
let powered = command_entity.powered.load(Ordering::Relaxed); let powered = command_entity.powered.load(Ordering::Relaxed);
let auto = command_entity.auto.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); let props = CommandBlockLikeProperties::from_state_id(state_id, block);
if powered || auto { if powered || auto {
let conditions_met = Self::conditions_met(&world, &pos, direction); let conditions_met = Self::conditions_met(&world, &pos, direction);
if conditions_met { 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 { let Some(entity) = world.get_block_entity(&pos) else {
warn!("Command block entity disappeared during execution"); warn!("Command block entity disappeared during execution");
break; break;
@ -220,16 +221,14 @@ impl BlockMetadata for CommandBlock {
} }
impl BlockBehaviour for CommandBlock { impl BlockBehaviour for CommandBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = CommandBlockLikeProperties::default(args.block);
let mut props = CommandBlockLikeProperties::default(args.block); props.facing = args.player.get_entity().get_facing().opposite();
props.facing = args.player.get_entity().get_facing().opposite(); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { {
if args.player.permission_lvl.load() < PermissionLvl::Two { if args.player.permission_lvl.load() < PermissionLvl::Two {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }
@ -238,11 +237,11 @@ impl BlockBehaviour for CommandBlock {
}; };
args.world.update_block_entity(&block_entity); args.world.update_block_entity(&block_entity);
BlockActionResult::SuccessServer BlockActionResult::SuccessServer
}) }
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { {
let command_blocks_work = let command_blocks_work =
{ args.world.level_info.load().game_rules.command_blocks_work }; { args.world.level_info.load().game_rules.command_blocks_work };
if !command_blocks_work { if !command_blocks_work {
@ -264,64 +263,68 @@ impl BlockBehaviour for CommandBlock {
args.block, args.block,
command_entity, command_entity,
args.position, 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, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { let command_blocks_work = { args.world.level_info.load().game_rules.command_blocks_work };
let command_blocks_work = if !command_blocks_work {
{ args.world.level_info.load().game_rules.command_blocks_work }; return;
if !command_blocks_work { }
return; let Some(block_entity) = args.world.get_block_entity(args.position) else {
} return;
let Some(block_entity) = args.world.get_block_entity(args.position) else { };
return; if block_entity.resource_location() != CommandBlockEntity::ID {
}; 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 { else {
warn!("Block entity at {} is not a command block", args.position);
return; return;
}; };
let Some(server) = args.world.server.upgrade() else { let command = command_entity
return; .command
}; .lock()
let props = CommandBlockLikeProperties::from_state_id( .unwrap_or_else(std::sync::PoisonError::into_inner)
args.world.get_block_state_id(args.position), .clone();
args.block, Self::execute(&server, world.clone(), entity_clone, &command).await;
);
Self::execute(
&server,
args.world.clone(),
block_entity.clone(),
&command_entity.command.lock().await,
)
.await;
Self::chain_execute( Self::chain_execute(
&server, &server,
args.world.clone(), world,
args.position position.offset(facing.to_block_direction().to_offset()),
.offset(props.facing.to_block_direction().to_offset()), facing,
props.facing,
) )
.await; .await;
});
let block = args.world.get_block(args.position); let block = args.world.get_block(args.position);
let is_auto = command_entity.auto.load(Ordering::Relaxed); let is_auto = command_entity.auto.load(Ordering::Relaxed);
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto; let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
if block == &Block::REPEATING_COMMAND_BLOCK && can_run { if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
args.world args.world
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal); .schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
} }
})
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@ -334,8 +337,8 @@ impl BlockBehaviour for CommandBlock {
false false
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let send_command_feedback = { let send_command_feedback = {
let game_rules = &args.world.level_info.load().game_rules; let game_rules = &args.world.level_info.load().game_rules;
game_rules.send_command_feedback game_rules.send_command_feedback
@ -347,14 +350,11 @@ impl BlockBehaviour for CommandBlock {
args.block.id == Block::CHAIN_COMMAND_BLOCK.id, args.block.id == Block::CHAIN_COMMAND_BLOCK.id,
); );
args.world.add_block_entity(Arc::new(entity)); args.world.add_block_entity(Arc::new(entity));
}) }
} }
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: crate::block::GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, {
args: crate::block::GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async {
let entity = args.world.get_block_entity(args.position); let entity = args.world.get_block_entity(args.position);
entity.map_or_else( 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) 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::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs, BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs,
UseWithItemArgs, registry::BlockActionResult, UseWithItemArgs, registry::BlockActionResult,
}, },
entity::{Entity, item::ItemEntity}, entity::{Entity, item::ItemEntity},
@ -27,32 +27,27 @@ use rand::RngExt;
pub struct ComposterBlock; pub struct ComposterBlock;
impl BlockBehaviour for ComposterBlock { impl BlockBehaviour for ComposterBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
if props.level == 8 { if props.level == 8 {
self.clear_composter(args.world, args.position, state_id, args.block) self.clear_composter(args.world, args.position, state_id, args.block);
.await;
} }
BlockActionResult::Pass BlockActionResult::Pass
}) }
} }
fn use_with_item<'a>( fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
&'a self, {
args: UseWithItemArgs<'a>,
) -> BlockFuture<'a, BlockActionResult> {
Box::pin(async move {
let state_id = args.world.get_block_state_id(args.position); let state_id = args.world.get_block_state_id(args.position);
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let level = props.level; let level = props.level;
// Check if the composter is full // Check if the composter is full
if level == 8 { if level == 8 {
self.clear_composter(args.world, args.position, state_id, args.block) self.clear_composter(args.world, args.position, state_id, args.block);
.await;
return BlockActionResult::Consume; return BlockActionResult::Consume;
} }
@ -77,48 +72,35 @@ impl BlockBehaviour for ComposterBlock {
state_id, state_id,
args.block, args.block,
level + 1, level + 1,
) );
.await;
args.world args.world
.sync_world_event(WorldEvent::ComposterFill, *args.position, 1); .sync_world_event(WorldEvent::ComposterFill, *args.position, 1);
} }
// Consume the item // Consume the item
BlockActionResult::Consume BlockActionResult::Consume
}) }
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { let state_id = args.world.get_block_state_id(args.position);
let state_id = args.world.get_block_state_id(args.position); let props = ComposterLikeProperties::from_state_id(state_id, args.block);
let props = ComposterLikeProperties::from_state_id(state_id, args.block); let level = props.level;
let level = props.level; if level == 7 {
if level == 7 { self.update_level_composter(args.world, args.position, state_id, args.block, level + 1);
self.update_level_composter( }
args.world,
args.position,
state_id,
args.block,
level + 1,
)
.await;
}
})
} }
fn get_comparator_output<'a>( fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
&'a self, {
args: GetComparatorOutputArgs<'a>,
) -> BlockFuture<'a, Option<u8>> {
Box::pin(async move {
let props = ComposterLikeProperties::from_state_id(args.state.id, args.block); let props = ComposterLikeProperties::from_state_id(args.state.id, args.block);
Some(props.level) Some(props.level)
}) }
} }
} }
impl ComposterBlock { impl ComposterBlock {
pub async fn update_level_composter( pub fn update_level_composter(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
location: &BlockPos, location: &BlockPos,
@ -128,23 +110,20 @@ impl ComposterBlock {
) { ) {
let mut props = ComposterLikeProperties::from_state_id(state_id, block); let mut props = ComposterLikeProperties::from_state_id(state_id, block);
props.level = level; props.level = level;
world world.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
if level == 7 { if level == 7 {
world.schedule_block_tick(block, *location, 20, TickPriority::Normal); world.schedule_block_tick(block, *location, 20, TickPriority::Normal);
} }
} }
pub async fn clear_composter( pub fn clear_composter(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
location: &BlockPos, location: &BlockPos,
state_id: BlockStateId, state_id: BlockStateId,
block: &Block, block: &Block,
) { ) {
self.update_level_composter(world, location, state_id, block, 0) self.update_level_composter(world, location, state_id, block, 0);
.await;
let item_position = { let item_position = {
let mut rng = rand::rng(); let mut rng = rand::rng();
@ -160,6 +139,6 @@ impl ComposterBlock {
ItemStack::new(1, &Item::BONE_MEAL), 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::entities::conduit::ConduitBlockEntity;
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs}; use crate::block::{BlockBehaviour, OnPlaceArgs, PlacedArgs};
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::block_properties::BlockProperties;
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
@ -9,20 +9,18 @@ use std::sync::Arc;
pub struct ConduitBlock; pub struct ConduitBlock;
impl BlockBehaviour for ConduitBlock { impl BlockBehaviour for ConduitBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props =
let mut props = pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block);
pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block); props.r#waterlogged = args.replacing.water_source();
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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let entity = ConduitBlockEntity::new(*args.position); let entity = ConduitBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity)); args.world.add_block_entity(Arc::new(entity));
}) }
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs, BlockBehaviour, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs,
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick}, blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
}, },
@ -47,102 +47,90 @@ pub type CoralWallFanLikeProperties = LadderLikeProperties;
pub type CoralFanLikeProperties = MangroveRootsLikeProperties; pub type CoralFanLikeProperties = MangroveRootsLikeProperties;
impl BlockBehaviour for CoralFanBlock { impl BlockBehaviour for CoralFanBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { if args.direction == BlockDirection::Down {
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);
}
}
let support_block = args.world.get_block_state(&args.position.down()); let support_block = args.world.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) { if support_block.is_center_solid(BlockDirection::Up) {
return get_default_coral_fan_state_id(args.block, args.replacing.water_source()); 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, ()> { if args.replacing == BlockIsReplacing::None {
Box::pin(async move { let face = args.direction.to_facing();
if !scan_for_water(args.world, args.position).await { let mut i = 0;
try_schedule_die_tick(args.block, args.world, args.position).await; while i < directions.len() && directions[i] != face {
i += 1;
} }
})
}
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { if i > 0 {
Box::pin(async move { directions.copy_within(0..i, 1);
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) { directions[0] = face;
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;
} }
}) } 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()); 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) { if support_block.is_center_solid(BlockDirection::Up) && !is_wall_fan(args.block) {
return true; return true;
@ -155,27 +143,25 @@ impl BlockBehaviour for CoralFanBlock {
false false
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if is_wall_fan(args.block) {
if is_wall_fan(args.block) { let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block); if props.facing.to_block_direction().opposite() == args.direction
if props.facing.to_block_direction().opposite() == args.direction && !can_place_at(args.world, args.position, props.facing.opposite())
&& !can_place_at(args.world, args.position, props.facing.opposite()) {
{ return BlockStateId::AIR;
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;
}
} }
} 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 pumpkin_world::world::BlockFlags;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, OnScheduledTickArgs, PlacedArgs,
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick}, blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
}; };
pub struct CoralPlantBlock; pub struct CoralPlantBlock;
@ -26,36 +26,30 @@ impl BlockMetadata for CoralPlantBlock {
pub type CoralPlantLikeProperties = MangroveRootsLikeProperties; pub type CoralPlantLikeProperties = MangroveRootsLikeProperties;
impl BlockBehaviour for CoralPlantBlock { impl BlockBehaviour for CoralPlantBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = CoralPlantLikeProperties::default(args.block);
let mut props = CoralPlantLikeProperties::default(args.block); props.waterlogged = args.replacing.water_source();
props.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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) { if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
try_schedule_die_tick(args.block, args.world, args.position).await; try_schedule_die_tick(args.block, args.world, args.position);
} }
}) }
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
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 current_state = args.world.get_block_state(args.position); let dead_block_state_id = {
let dead_block_state_id = { let props = CoralPlantLikeProperties::from_state_id(current_state.id, args.block);
let props = props.to_state_id(get_dead_type(args.block.id).unwrap_or_default().to_block())
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());
args.world }
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
.await;
}
})
} }
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()); let support_block = args.block_accessor.get_block_state(&args.position.down());
if support_block.is_center_solid(BlockDirection::Up) { if support_block.is_center_solid(BlockDirection::Up) {
return true; return true;
@ -63,19 +57,17 @@ impl BlockBehaviour for CoralPlantBlock {
false false
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if args.direction == BlockDirection::Down {
if args.direction == BlockDirection::Down { let support_block = args.world.get_block_state(&args.position.down());
let support_block = args.world.get_block_state(&args.position.down()); if !support_block.is_center_solid(BlockDirection::Up) {
if !support_block.is_center_solid(BlockDirection::Up) { return BlockStateId::AIR;
return BlockStateId::AIR;
}
} }
args.state_id }
}) args.state_id
} }
} }
const fn get_dead_type(id: BlockId) -> Option<BlockId> { 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_fan;
pub mod coral_plant; 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() { for direction in BlockDirection::all() {
let neighbor_pos = pos.offset(direction.to_offset()); let neighbor_pos = pos.offset(direction.to_offset());
let block = world.get_fluid(&neighbor_pos); 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_FAN
|| block == &Block::DEAD_TUBE_CORAL_WALL_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); let tick_delay = 60 + rand::rng().random_range(0..40);
world.schedule_block_tick( world.schedule_block_tick(
block, block,

View File

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

View File

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

View File

@ -10,125 +10,104 @@ use pumpkin_macros::pumpkin_block;
use crate::block::entities::decorated_pot::DecoratedPotBlockEntity; use crate::block::entities::decorated_pot::DecoratedPotBlockEntity;
use crate::block::registry::BlockActionResult; use crate::block::registry::BlockActionResult;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
PlacedArgs, UseWithItemArgs, UseWithItemArgs,
}; };
#[pumpkin_block("minecraft:decorated_pot")] #[pumpkin_block("minecraft:decorated_pot")]
pub struct DecoratedPotBlock; pub struct DecoratedPotBlock;
impl BlockBehaviour for DecoratedPotBlock { impl BlockBehaviour for DecoratedPotBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props =
let mut props = DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block);
DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block); props.facing = args
props.facing = args .player
.player .living_entity
.living_entity .entity
.entity .get_horizontal_facing()
.get_horizontal_facing() .opposite();
.opposite(); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let entity = DecoratedPotBlockEntity::new(*args.position);
let entity = DecoratedPotBlockEntity::new(*args.position); args.world.add_block_entity(Arc::new(entity));
args.world.add_block_entity(Arc::new(entity));
})
} }
fn use_with_item<'a>( fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
&'a self, if args.item_stack.item_count == 0 {
args: UseWithItemArgs<'a>, return self.normal_use(NormalUseArgs {
) -> BlockFuture<'a, BlockActionResult> { server: args.server,
Box::pin(async move { world: args.world,
if args.item_stack.item_count == 0 { block: args.block,
return self position: args.position,
.normal_use(NormalUseArgs { player: args.player,
server: args.server, hit: args.hit,
world: args.world, });
block: args.block, }
position: args.position,
player: args.player,
hit: args.hit,
})
.await;
}
if let Some(block_entity) = args.world.get_block_entity(args.position) if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(pot_entity) = block_entity && let Some(pot_entity) = block_entity
.as_any() .as_any()
.downcast_ref::<DecoratedPotBlockEntity>() .downcast_ref::<DecoratedPotBlockEntity>()
{ {
if pot_entity.try_insert_item(args.item_stack, 1).await { if pot_entity.try_insert_item(args.item_stack, 1) {
args.world.play_sound( args.world.play_sound(
Sound::BlockDecoratedPotInsert, Sound::BlockDecoratedPotInsert,
SoundCategory::Blocks, SoundCategory::Blocks,
&args.position.to_f64(), &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)
} else { } 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::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::CanPlaceAtArgs; use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs; use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs; use crate::block::OnPlaceArgs;
@ -17,40 +16,32 @@ use pumpkin_world::world::BlockFlags;
pub struct DirtPathBlock; pub struct DirtPathBlock;
impl BlockBehaviour for DirtPathBlock { impl BlockBehaviour for DirtPathBlock {
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { // TODO: push up entities
// 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 args.world
.set_block_state( .schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
args.position, }
Block::DIRT.default_state.id, args.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
})
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { 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::BlockDirection;
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
use pumpkin_data::block_properties::Axis; use pumpkin_data::block_properties::Axis;
@ -17,7 +16,6 @@ use pumpkin_world::world::BlockFlags;
use std::sync::Arc; use std::sync::Arc;
use crate::block::BlockBehaviour; use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::BrokenArgs; use crate::block::BrokenArgs;
use crate::block::CanPlaceAtArgs; use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs; use crate::block::GetStateForNeighborUpdateArgs;
@ -36,7 +34,7 @@ use pumpkin_util::GameMode;
type DoorProperties = pumpkin_data::block_properties::OakDoorLikeProperties; 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 (block, block_state) = world.get_block_and_state_id(block_pos);
let mut door_props = DoorProperties::from_state_id(block_state, block); let mut door_props = DoorProperties::from_state_id(block_state, block);
door_props.open = !door_props.open; door_props.open = !door_props.open;
@ -58,20 +56,16 @@ async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos)
*block_pos, *block_pos,
); );
world world.set_block_state(
.set_block_state( block_pos,
block_pos, door_props.to_state_id(block),
door_props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
) world.set_block_state(
.await; &other_pos,
world other_door_props.to_state_id(other_block),
.set_block_state( BlockFlags::NOTIFY_LISTENERS,
&other_pos, );
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
} }
fn can_open_door(block: &Block) -> bool { fn can_open_door(block: &Block) -> bool {
@ -103,7 +97,7 @@ fn get_sound(block: &Block, open: bool) -> Sound {
#[expect(clippy::pedantic)] #[expect(clippy::pedantic)]
#[inline] #[inline]
async fn get_hinge( fn get_hinge(
world: &World, world: &World,
pos: &BlockPos, pos: &BlockPos,
use_item: &SUseItemOn, use_item: &SUseItemOn,
@ -177,7 +171,7 @@ impl DoorBlock {
door_props.open 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); let (block, block_state) = world.get_block_and_state_id(block_pos);
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) { if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
return; return;
@ -198,46 +192,39 @@ impl DoorBlock {
world.play_block_sound(get_sound(block, open), SoundCategory::Blocks, *block_pos); world.play_block_sound(get_sound(block, open), SoundCategory::Blocks, *block_pos);
world world.set_block_state(
.set_block_state( block_pos,
block_pos, door_props.to_state_id(block),
door_props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
)
.await;
if other_block.id == block.id { if other_block.id == block.id {
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block); let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
other_door_props.open = open; other_door_props.open = open;
world world.set_block_state(
.set_block_state( &other_pos,
&other_pos, other_door_props.to_state_id(other_block),
other_door_props.to_state_id(other_block), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
)
.await;
} }
} }
} }
impl BlockBehaviour for DoorBlock { impl BlockBehaviour for DoorBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut door_props = DoorProperties::default(args.block);
let powered = block_receives_redstone_power(args.world, args.position).await let facing = args
|| block_receives_redstone_power(args.world, &args.position.up()).await; .player
.living_entity
let direction = args.player.get_entity().get_horizontal_facing(); .entity
let hinge = get_hinge(args.world, args.position, args.use_item_on, direction).await; .get_horizontal_facing()
.opposite();
let mut door_props = DoorProperties::default(args.block); door_props.facing = facing;
door_props.half = DoubleBlockHalf::Lower; door_props.half = DoubleBlockHalf::Lower;
door_props.facing = direction; door_props.hinge = get_hinge(args.world, args.position, args.use_item_on, facing);
door_props.hinge = hinge; door_props.open = false;
door_props.powered = powered; door_props.powered = false;
door_props.open = powered; door_props.to_state_id(args.block)
door_props.to_state_id(args.block)
})
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@ -248,65 +235,59 @@ impl BlockBehaviour for DoorBlock {
.replaceable() .replaceable()
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let mut door_props = DoorProperties::from_state_id(args.state_id, args.block); let mut door_props = DoorProperties::from_state_id(args.state_id, args.block);
door_props.half = DoubleBlockHalf::Upper; door_props.half = DoubleBlockHalf::Upper;
args.world args.world.set_block_state(
.set_block_state( &args.position.offset(BlockDirection::Up.to_offset()),
&args.position.offset(BlockDirection::Up.to_offset()), door_props.to_state_id(args.block),
door_props.to_state_id(args.block), BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK, );
) }
.await;
})
} }
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { {
if !can_open_door(args.block) { if !can_open_door(args.block) {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }
toggle_door(args.player, args.world, args.position).await; toggle_door(args.player, args.world, args.position);
BlockActionResult::Success BlockActionResult::Success
}) }
} }
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { let door_props = DoorProperties::from_state_id(args.state.id, args.block);
let door_props = DoorProperties::from_state_id(args.state.id, args.block); let other_half_pos = match door_props.half {
let other_half_pos = match door_props.half { DoubleBlockHalf::Upper => args.position.down(),
DoubleBlockHalf::Upper => args.position.down(), DoubleBlockHalf::Lower => args.position.up(),
DoubleBlockHalf::Lower => args.position.up(), };
};
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos); let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
if neighbor_state_id.to_block_id() != args.block.id { if neighbor_state_id.to_block_id() != args.block.id {
args.world.update_neighbors(&other_half_pos, None).await; args.world.update_neighbors(&other_half_pos, None);
return; // Neighbor is already gone or is a different block return; // Neighbor is already gone or is a different block
} }
let is_creative = args.player.gamemode.load() == GameMode::Creative; let is_creative = args.player.gamemode.load() == GameMode::Creative;
let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative { let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative {
BlockFlags::NOTIFY_ALL BlockFlags::NOTIFY_ALL
} else { } else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL
}; };
args.world args.world
.break_block(&other_half_pos, Some(args.player.clone()), flags) .break_block(&other_half_pos, Some(args.player.clone()), flags);
.await;
})
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { {
let block_state = args.world.get_block_state(args.position); let block_state = args.world.get_block_state(args.position);
let mut door_props = DoorProperties::from_state_id(block_state.id, args.block); let mut door_props = DoorProperties::from_state_id(block_state.id, args.block);
let other_half = match door_props.half { let other_half = match door_props.half {
DoubleBlockHalf::Upper => BlockDirection::Down, DoubleBlockHalf::Upper => BlockDirection::Down,
DoubleBlockHalf::Lower => BlockDirection::Up, DoubleBlockHalf::Lower => BlockDirection::Up,
@ -314,23 +295,28 @@ impl BlockBehaviour for DoorBlock {
let other_pos = args.position.offset(other_half.to_offset()); 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); let (other_block, other_state_id) = args.world.get_block_and_state_id(&other_pos);
if other_block.id != args.block.id { let powered = block_receives_redstone_power(args.world, args.position)
return; || block_receives_redstone_power(args.world, &other_pos);
}
let powered = block_receives_redstone_power(args.world, args.position).await if door_props.powered != powered {
|| block_receives_redstone_power(args.world, &other_pos).await; 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 = let mut other_door_props =
DoorProperties::from_state_id(other_state_id, other_block); 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.powered = powered;
door_props.open = door_props.powered; other_door_props.powered = powered;
other_door_props.open = other_door_props.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( args.world.play_block_sound(
get_sound(args.block, powered), get_sound(args.block, powered),
SoundCategory::Blocks, SoundCategory::Blocks,
@ -338,55 +324,48 @@ impl BlockBehaviour for DoorBlock {
); );
} }
args.world args.world.set_block_state(
.set_block_state( args.position,
args.position, door_props.to_state_id(args.block),
door_props.to_state_id(args.block), BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
) args.world.set_block_state(
.await; &other_pos,
args.world other_door_props.to_state_id(other_block),
.set_block_state( BlockFlags::NOTIFY_ALL,
&other_pos, );
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_ALL,
)
.await;
} }
}) }
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
let lv = DoorProperties::from_state_id(args.state_id, args.block).half; if args.direction.to_axis() != Axis::Y
if args.direction.to_axis() != Axis::Y || (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
|| (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; 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, ()> { fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
Box::pin(async move { {
if args.moved { if args.moved {
return; return;
} }
@ -403,14 +382,12 @@ impl BlockBehaviour for DoorBlock {
DoubleBlockHalf::Lower => args.position.up(), DoubleBlockHalf::Lower => args.position.up(),
}; };
args.world args.world.break_block(
.break_block( &other_half_pos,
&other_half_pos, None,
None, BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL,
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL, );
) }
.await;
})
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use crate::block::BlockBehaviour; use crate::block::BlockBehaviour;
use crate::block::BlockFuture;
use crate::block::OnEntityCollisionArgs; use crate::block::OnEntityCollisionArgs;
use crate::block::PlacedArgs; use crate::block::PlacedArgs;
use crate::block::entities::end_portal::EndPortalBlockEntity; use crate::block::entities::end_portal::EndPortalBlockEntity;
@ -12,36 +11,31 @@ use pumpkin_macros::pumpkin_block;
pub struct EndPortalBlock; pub struct EndPortalBlock;
impl BlockBehaviour for EndPortalBlock { impl BlockBehaviour for EndPortalBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
Box::pin(async move { let target_world =
let target_world = if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name {
if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name { args.server.get_world_from_dimension(&Dimension::OVERWORLD)
args.server.get_world_from_dimension(&Dimension::OVERWORLD) } else {
} else { args.server.get_world_from_dimension(&Dimension::THE_END)
args.server.get_world_from_dimension(&Dimension::THE_END) };
}; if Arc::ptr_eq(&target_world, args.world) {
if Arc::ptr_eq(&target_world, args.world) { return;
return; }
} tracing::info!(
tracing::info!( "End portal collision at {:?}, targeting world {:?}",
"End portal collision at {:?}, targeting world {:?}", args.position,
args.position, target_world.dimension.minecraft_name
target_world.dimension.minecraft_name );
); args.entity
args.entity .get_entity()
.get_entity() .try_use_portal(0, target_world, *args.position);
.try_use_portal(0, target_world, *args.position)
.await;
})
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let nbt = EndPortalBlockEntity::create_nbt(*args.position);
let nbt = EndPortalBlockEntity::create_nbt(*args.position); args.world.add_block_entity_nbt(*args.position, &nbt);
args.world.add_block_entity_nbt(*args.position, &nbt);
args.world args.world
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position))); .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 pumpkin_macros::pumpkin_block;
use crate::{ use crate::{
block::{BlockBehaviour, BlockFuture, OnPlaceArgs}, block::{BlockBehaviour, OnPlaceArgs},
entity::EntityBase, entity::EntityBase,
}; };
@ -13,13 +13,10 @@ type EndPortalFrameProperties = pumpkin_data::block_properties::EndPortalFrameLi
pub struct EndPortalFrameBlock; pub struct EndPortalFrameBlock;
impl BlockBehaviour for EndPortalFrameBlock { impl BlockBehaviour for EndPortalFrameBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut end_portal_frame_props = EndPortalFrameProperties::default(args.block);
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.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 crate::block::{BlockBehaviour, OnPlaceArgs};
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
@ -10,24 +9,22 @@ use pumpkin_macros::pumpkin_block;
pub struct EndRodBlock; pub struct EndRodBlock;
impl BlockBehaviour for EndRodBlock { impl BlockBehaviour for EndRodBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = EndRodLikeProperties::default(args.block);
let mut props = EndRodLikeProperties::default(args.block);
let blockstate = args let blockstate = args
.world .world
.get_block_state_id(&args.position.offset(args.direction.to_offset())); .get_block_state_id(&args.position.offset(args.direction.to_offset()));
if Block::from_state_id(blockstate).eq(args.block) if Block::from_state_id(blockstate).eq(args.block)
&& EndRodLikeProperties::from_state_id(blockstate, args.block).facing && EndRodLikeProperties::from_state_id(blockstate, args.block).facing
== args.direction.to_facing().opposite() == args.direction.to_facing().opposite()
{ {
props.facing = args.direction.to_facing(); props.facing = args.direction.to_facing();
} else { } else {
props.facing = args.direction.to_facing().opposite(); 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::entities::ender_chest::EnderChestBlockEntity;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, BlockBehaviour, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
registry::BlockActionResult, registry::BlockActionResult,
}; };
use crate::world::World; use crate::world::World;
@ -57,77 +57,69 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
pub struct EnderChestBlock; pub struct EnderChestBlock;
impl BlockBehaviour for EnderChestBlock { impl BlockBehaviour for EnderChestBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = LadderLikeProperties::default(args.block);
let mut props = LadderLikeProperties::default(args.block); props.facing = args
props.facing = args .player
.player .living_entity
.living_entity .entity
.entity .get_horizontal_facing()
.get_horizontal_facing() .opposite();
.opposite(); props.waterlogged = args.replacing.water_source();
props.waterlogged = args.replacing.water_source(); props.to_state_id(args.block)
props.to_state_id(args.block)
})
} }
fn on_synced_block_event<'a>( fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
&'a self, // On the server, we don't need to do more because the client is responsible for that.
args: OnSyncedBlockEventArgs<'a>, args.r#type == Self::LID_ANIMATION_EVENT_TYPE
) -> 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 normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { if is_chest_blocked(args.world, args.position) {
if is_chest_blocked(args.world, args.position) { return BlockActionResult::Success;
return BlockActionResult::Success; }
}
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) { let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
be be
} else { } else {
let be = Arc::new(EnderChestBlockEntity::new(*args.position)); let be = Arc::new(EnderChestBlockEntity::new(*args.position));
args.world.add_block_entity(be.clone()); args.world.add_block_entity(be.clone());
be be
}; };
if let Some(block_entity) = block_entity if let Some(block_entity) = block_entity
.as_any() .as_any()
.downcast_ref::<EnderChestBlockEntity>() .downcast_ref::<EnderChestBlockEntity>()
{ {
let inventory = args.player.ender_chest_inventory(); args.player.increment_stat(
args.player pumpkin_data::statistic::StatisticCategory::Custom,
.increment_stat( pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
pumpkin_data::statistic::StatisticCategory::Custom, 1,
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32, );
1, let player = Arc::clone(args.player);
) let pos = *args.position;
.await; let tracker = block_entity.get_tracker();
args.player tokio::spawn(async move {
let inventory = player.ender_chest_inventory();
player
.open_handled_screen( .open_handled_screen(
&EnderChestScreenFactory { &EnderChestScreenFactory {
inventory: inventory.clone(), inventory: inventory.clone(),
tracker: Some(block_entity.get_tracker()), tracker: Some(tracker),
}, },
Some(*args.position), Some(pos),
) )
.await; .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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { let block_entity = EnderChestBlockEntity::new(*args.position);
let block_entity = EnderChestBlockEntity::new(*args.position); args.world.add_block_entity(Arc::new(block_entity));
args.world.add_block_entity(Arc::new(block_entity));
})
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::GetStateForNeighborUpdateArgs; use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs; use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
@ -22,23 +21,19 @@ use crate::world::World;
pub struct FenceBlock; pub struct FenceBlock;
impl BlockBehaviour for FenceBlock { impl BlockBehaviour for FenceBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut fence_props = FenceProperties::default(args.block);
let mut fence_props = FenceProperties::default(args.block); fence_props.waterlogged = args.replacing.water_source();
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>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { let fence_props = FenceProperties::from_state_id(args.state_id, args.block);
let fence_props = FenceProperties::from_state_id(args.state_id, args.block); compute_fence_state(fence_props, args.world, args.block, args.position)
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::blocks::tnt::TNTBlock;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
OnEntityCollisionArgs, OnScheduledTickArgs, PlacedArgs, OnEntityCollisionArgs, OnScheduledTickArgs, PlacedArgs,
}; };
use crate::world::World; 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 block = world.get_block(pos);
let odds = Self::get_burn_odds(block); let odds = Self::get_burn_odds(block);
if rand::rng().random_range(0..chance) < odds { 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); let mut fire_props = FireProperties::from_state_id(state_id, &Block::FIRE);
fire_props.age = new_age; fire_props.age = new_age;
let new_state_id = fire_props.to_state_id(&Block::FIRE); let new_state_id = fire_props.to_state_id(&Block::FIRE);
world world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
.await;
} else { } else {
let mut burn_event = crate::plugin::block::block_burn::BlockBurnEvent { world.set_block_state(
igniting_block: &Block::FIRE, pos,
block: old_block, Block::AIR.default_state.id,
cancelled: false, BlockFlags::NOTIFY_NEIGHBORS,
}; );
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;
} }
if old_block == &Block::TNT { if old_block == &Block::TNT {
TNTBlock::prime(world, pos).await; TNTBlock::prime(world, pos);
} }
} }
} }
} }
impl BlockBehaviour for FireBlock { impl BlockBehaviour for FireBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { if args.old_state_id == args.state_id {
if args.old_state_id == args.state_id { // Already a fire
// Already a fire return;
return; }
}
let dimension = &args.world.dimension; 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 // 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) if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
&& let Some(portal) = && let Some(portal) =
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X) NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X)
{ {
portal.create(args.world).await; portal.create(args.world);
return; return;
} }
args.world.schedule_block_tick( args.world.schedule_block_tick(
args.block, args.block,
*args.position, *args.position,
Self::get_fire_tick_delay() as u8, Self::get_fire_tick_delay() as u8,
TickPriority::Normal, TickPriority::Normal,
); );
})
} }
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
FireBlockBase::apply_fire_collision(args, false) FireBlockBase::apply_fire_collision(&args, false);
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if self.can_place_at(CanPlaceAtArgs {
if self.can_place_at(CanPlaceAtArgs { server: None,
server: None, world: Some(args.world),
world: Some(args.world), block_accessor: args.world,
block_accessor: args.world, block: &Block::FIRE,
block: &Block::FIRE, state: Block::FIRE.default_state,
state: Block::FIRE.default_state, position: args.position,
position: args.position, direction: None,
direction: None, player: None,
player: None, use_item_on: None,
use_item_on: None, }) {
}) { self.get_state_for_position(args.world, args.block, args.position)
let old_fire_props = FireProperties::from_state_id(args.state_id, &Block::FIRE); } else {
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);
}
Block::AIR.default_state.id Block::AIR.default_state.id
}) }
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
@ -259,247 +236,223 @@ impl BlockBehaviour for FireBlock {
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { let (world, block, pos) = (args.world, args.block, args.position);
let (world, block, pos) = (args.world, args.block, args.position);
// Schedule next tick first // Schedule next tick first
world.schedule_block_tick( world.schedule_block_tick(
block, block,
*pos, *pos,
Self::get_fire_tick_delay() as u8, Self::get_fire_tick_delay() as u8,
TickPriority::Normal, 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 let block_state = world.get_block_state(pos);
if !self.can_place_at(CanPlaceAtArgs { let block_below = world.get_block(&pos.down());
server: None,
world: Some(world), // Check for infiniburn blocks (depending on dimension)
block_accessor: world.as_ref(), let infiniburn = match world.dimension.id {
block, id if id == Dimension::OVERWORLD.id => {
state: block.default_state, block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD)
position: pos, }
direction: None, id if id == Dimension::THE_NETHER.id => {
player: None, block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER)
use_item_on: None, }
}) { id if id == Dimension::THE_END.id => {
world block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END)
.set_block_state( }
_ => 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, pos,
Block::AIR.default_state.id, Block::AIR.default_state.id,
BlockFlags::NOTIFY_NEIGHBORS, 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; return;
} }
}
let block_state = world.get_block_state(pos); // Burn adjacent blocks
let block_below = world.get_block(&pos.down()); 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) self.try_spreading_fire(
let infiniburn = match world.dimension.id { world,
id if id == Dimension::OVERWORLD.id => { &pos.offset(BlockDirection::East.to_offset()),
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD) 300 + extra,
} new_age,
id if id == Dimension::THE_NETHER.id => { );
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER) self.try_spreading_fire(
} world,
id if id == Dimension::THE_END.id => { &pos.offset(BlockDirection::West.to_offset()),
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END) 300 + extra,
} new_age,
_ => false, );
}; 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); // Respect the `fire_spread_radius_around_player` gamerule.
let age = fire_props.age; // -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 // Try to spread fire to nearby air blocks
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) { let difficulty = world.level_info.load().difficulty as i32;
let rain_chance = 0.2 + (age as f32) * 0.03; for xx in -1..=1 {
if rand::random::<f32>() < rain_chance { for zz in -1..=1 {
world for yy in -1..=4 {
.set_block_state( if xx != 0 || yy != 0 || zz != 0 {
pos, let offset_pos = pos.offset(Vector3::new(xx, yy, zz));
Block::AIR.default_state.id, let ignite_odds = self.get_burn_chance(world, &offset_pos);
BlockFlags::NOTIFY_NEIGHBORS,
)
.await;
return;
}
}
// Increment age if ignite_odds > 0 {
let random = (rand::rng().random_range(0..3) / 2) as u8; // Skip if spreding is disabled or if there are no players nearby
let new_age = (age + random).min(15); if spread_radius == 0 {
if new_age != age { continue;
fire_props.age = new_age; }
let new_state_id = fire_props.to_state_id(&Block::FIRE); if spread_radius != -1 {
world let center = offset_pos.to_centered_f64();
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS) if world
.await; .get_closest_player(center, spread_radius as f64)
} .is_none()
{
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 {
continue; 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 // Calculate spread rate based on height
let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 }; let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 };
// Calculate odds of spreading // Calculate odds of spreading
let mut odds = let mut odds =
(ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30); (ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30);
// Reduce spread odds in certain biomes // Reduce spread odds in certain biomes
if Self::is_increased_burnout_biome(world, &offset_pos) { if Self::is_increased_burnout_biome(world, &offset_pos) {
odds /= 2; // Fire spreads 50% slower odds /= 2; // Fire spreads 50% slower
} }
if odds > 0 if odds > 0
&& rand::rng().random_range(0..rate) <= odds && rand::rng().random_range(0..rate) <= odds
&& !Self::is_near_rain(world.as_ref(), &offset_pos) && !Self::is_near_rain(world.as_ref(), &offset_pos)
{ {
let spread_age = (new_age + rand::rng().random_range(0..5) / 4) let spread_age =
.min(15) (new_age + rand::rng().random_range(0..5) / 4).min(15) as u8;
as u8; let fire_state_id =
let fire_state_id = self.get_state_for_position( self.get_state_for_position(world.as_ref(), block, &offset_pos);
world.as_ref(), let mut new_fire_props =
block, FireProperties::from_state_id(fire_state_id, &Block::FIRE);
&offset_pos, new_fire_props.age = spread_age;
);
let mut new_fire_props =
FireProperties::from_state_id(fire_state_id, &Block::FIRE);
new_fire_props.age = spread_age;
world world.set_block_state(
.set_block_state( &offset_pos,
&offset_pos, new_fire_props.to_state_id(&Block::FIRE),
new_fire_props.to_state_id(&Block::FIRE), BlockFlags::NOTIFY_NEIGHBORS,
BlockFlags::NOTIFY_NEIGHBORS, );
)
.await;
}
} }
} }
} }
} }
} }
}) }
} }
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { {
FireBlockBase::broken(args.world, *args.position); FireBlockBase::broken(args.world, *args.position);
}) }
} }
} }

View File

@ -11,7 +11,7 @@ use rand::RngExt;
use soul_fire::SoulFireBlock; use soul_fire::SoulFireBlock;
use crate::block::blocks::fire::fire::FireBlock; 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::entity::EntityBase;
use crate::world::World; use crate::world::World;
use crate::world::portal::nether::NetherPortal; use crate::world::portal::nether::NetherPortal;
@ -117,46 +117,34 @@ impl FireBlockBase {
} }
/// Shared fire collision behavior used by `fire` and `soul_fire`. /// Shared fire collision behavior used by `fire` and `soul_fire`.
#[must_use] pub fn apply_fire_collision(args: &OnEntityCollisionArgs<'_>, extra_damage_for_living: bool) {
#[allow(clippy::needless_pass_by_value)] let base_entity = args.entity.get_entity();
pub fn apply_fire_collision( if !base_entity.entity_type.fire_immune && !base_entity.fire_immune.load(Ordering::Relaxed)
args: OnEntityCollisionArgs<'_>, {
extra_damage_for_living: bool, let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
) -> 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);
// Timer logic // Timer logic
if ticks < 0 { if ticks < 0 {
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed); base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
} else if base_entity.entity_type == &EntityType::PLAYER { } else if base_entity.entity_type == &EntityType::PLAYER {
let rnd_ticks = rand::rng().random_range(1..3); let rnd_ticks = rand::rng().random_range(1..3);
base_entity base_entity
.fire_ticks .fire_ticks
.store(ticks + rnd_ticks, Ordering::Relaxed); .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;
}
} }
})
// 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) { 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_data::{Block, tag};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use crate::block::{ use crate::block::{BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
};
use super::FireBlockBase; use super::FireBlockBase;
use crate::block::OnEntityCollisionArgs; use crate::block::OnEntityCollisionArgs;
@ -21,30 +19,28 @@ impl SoulFireBlock {
} }
impl BlockBehaviour for SoulFireBlock { impl BlockBehaviour for SoulFireBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
FireBlockBase::apply_fire_collision(args, true) FireBlockBase::apply_fire_collision(&args, true);
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if !Self::is_soul_base(args.world.get_block(&args.position.down())) {
if !Self::is_soul_base(args.world.get_block(&args.position.down())) { return Block::AIR.default_state.id;
return Block::AIR.default_state.id; }
}
args.state_id args.state_id
})
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
Self::is_soul_base(args.block_accessor.get_block(&args.position.down())) Self::is_soul_base(args.block_accessor.get_block(&args.position.down()))
} }
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { {
FireBlockBase::broken(args.world, *args.position); FireBlockBase::broken(args.world, *args.position);
}) }
} }
} }

View File

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

View File

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

View File

@ -20,8 +20,8 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
block::{ block::{
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
OnPlaceArgs, PlacedArgs, registry::BlockActionResult, PlacedArgs, registry::BlockActionResult,
}, },
entity::experience_orb::ExperienceOrbEntity, entity::experience_orb::ExperienceOrbEntity,
}; };
@ -82,79 +82,70 @@ impl ScreenHandlerFactory for FurnaceScreenFactory {
pub struct FurnaceBlock; pub struct FurnaceBlock;
impl BlockBehaviour for FurnaceBlock { impl BlockBehaviour for FurnaceBlock {
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
if let Some(block_entity) = args.world.get_block_entity(args.position) && let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(inventory) = block_entity.clone().get_inventory() && let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate() && let Some(experience_container) = block_entity.to_experience_container()
&& let Some(experience_container) = block_entity.to_experience_container() {
{ args.player.increment_stat(
args.player pumpkin_data::statistic::StatisticCategory::Custom,
.increment_stat( pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
pumpkin_data::statistic::StatisticCategory::Custom, 1,
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32, );
1, let furnace_screen_factory =
) FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
.await; let player = Arc::clone(args.player);
let furnace_screen_factory = let pos = *args.position;
FurnaceScreenFactory::new(inventory, property_delegate, experience_container); tokio::spawn(async move {
args.player player
.open_handled_screen(&furnace_screen_factory, Some(*args.position)) .open_handled_screen(&furnace_screen_factory, Some(pos))
.await; .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> { fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
Box::pin(async move { if let Some(block_entity) = args.world.get_block_entity(args.position)
let mut props = FurnaceLikeProperties::default(args.block); && let Some(inventory) = block_entity.get_inventory()
props.facing = args {
.player Some(crate::block::calculate_comparator_output(
.living_entity inventory.as_ref(),
.entity ))
.get_horizontal_facing() } else {
.opposite(); None
}
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
}
})
} }
} }

View File

@ -1,4 +1,3 @@
use crate::block::BlockFuture;
use crate::block::GetStateForNeighborUpdateArgs; use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs; use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
@ -20,23 +19,19 @@ use crate::world::World;
pub struct GlassPaneBlock; pub struct GlassPaneBlock;
impl BlockBehaviour for GlassPaneBlock { impl BlockBehaviour for GlassPaneBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut pane_props = GlassPaneProperties::default(args.block);
let mut pane_props = GlassPaneProperties::default(args.block); pane_props.waterlogged = args.replacing.water_source();
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>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block);
let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block); compute_pane_state(pane_props, args.world, args.block, args.position)
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::BlockStateId;
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties}; use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
@ -7,16 +7,14 @@ use pumpkin_macros::pumpkin_block_from_tag;
pub struct GlazedTerracottaBlock; pub struct GlazedTerracottaBlock;
impl BlockBehaviour for GlazedTerracottaBlock { impl BlockBehaviour for GlazedTerracottaBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut prop = WallTorchLikeProperties::default(args.block);
let mut prop = WallTorchLikeProperties::default(args.block); prop.facing = args
prop.facing = args .player
.player .living_entity
.living_entity .entity
.entity .get_horizontal_facing()
.get_horizontal_facing() .opposite();
.opposite(); prop.to_state_id(args.block)
prop.to_state_id(args.block)
})
} }
} }

View File

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

View File

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

View File

@ -1,18 +1,14 @@
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use crate::block::{BlockBehaviour, BlockFuture, OnLandedUponArgs}; use crate::block::{BlockBehaviour, OnLandedUponArgs};
#[pumpkin_block("minecraft:hay_block")] #[pumpkin_block("minecraft:hay_block")]
pub struct HayBlock; pub struct HayBlock;
impl BlockBehaviour for HayBlock { impl BlockBehaviour for HayBlock {
fn on_landed_upon<'a>(&'a self, args: OnLandedUponArgs<'a>) -> BlockFuture<'a, ()> { fn on_landed_upon(&self, args: OnLandedUponArgs<'_>) {
Box::pin(async move { if let Some(living) = args.entity.get_living_entity() {
if let Some(living) = args.entity.get_living_entity() { living.handle_fall_damage(args.entity, args.fall_distance, 0.2);
living }
.handle_fall_damage(args.entity, args.fall_distance, 0.2)
.await;
}
})
} }
} }

View File

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

View File

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

View File

@ -4,16 +4,16 @@ use pumpkin_data::entity::EntityType;
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use crate::block::BlockBehaviour;
use crate::block::BrokenArgs; use crate::block::BrokenArgs;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::entity::Entity; use crate::entity::Entity;
#[pumpkin_block_from_tag("c:cobblestones/infested")] #[pumpkin_block_from_tag("c:cobblestones/infested")]
pub struct InfestedBlock; pub struct InfestedBlock;
impl BlockBehaviour for InfestedBlock { impl BlockBehaviour for InfestedBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async { {
// TODO: ugly fix, use onStacksDropped // TODO: ugly fix, use onStacksDropped
if args.player.gamemode.load() == GameMode::Creative { if args.player.gamemode.load() == GameMode::Creative {
return; return;
@ -24,7 +24,7 @@ impl BlockBehaviour for InfestedBlock {
&EntityType::SILVERFISH, &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::GetStateForNeighborUpdateArgs;
use crate::block::OnPlaceArgs; use crate::block::OnPlaceArgs;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
@ -20,23 +19,19 @@ use crate::world::World;
pub struct IronBarsBlock; pub struct IronBarsBlock;
impl BlockBehaviour for IronBarsBlock { impl BlockBehaviour for IronBarsBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut bars_props = IronBarsProperties::default(args.block);
let mut bars_props = IronBarsProperties::default(args.block); bars_props.waterlogged = args.replacing.water_source();
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>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block);
let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block); compute_bars_state(bars_props, args.world, args.block, args.position)
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::entities::jigsaw_block::JigsawBlockEntity;
use crate::block::registry::BlockActionResult; 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 crate::entity::EntityBase;
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{
BlockProperties, HorizontalFacing, JigsawLikeProperties, Orientation, BlockProperties, HorizontalFacing, JigsawLikeProperties, Orientation,
@ -100,24 +100,21 @@ impl JigsawBlock {
} }
impl BlockBehaviour for JigsawBlock { impl BlockBehaviour for JigsawBlock {
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let mut props = JigsawLikeProperties::default(args.block);
let mut props = JigsawLikeProperties::default(args.block); let front = args.direction;
let front = args.direction; let top = if front == BlockDirection::Up || front == BlockDirection::Down {
let top = if front == BlockDirection::Up || front == BlockDirection::Down { horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing()).opposite()
horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing()) } else {
.opposite() BlockDirection::Up
} else { };
BlockDirection::Up
};
props.r#orientation = Self::from_front_top(front, top); props.r#orientation = Self::from_front_top(front, top);
props.to_state_id(args.block) props.to_state_id(args.block)
})
} }
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { {
if args.player.permission_lvl.load() < PermissionLvl::Two { if args.player.permission_lvl.load() < PermissionLvl::Two {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }
@ -129,14 +126,14 @@ impl BlockBehaviour for JigsawBlock {
}; };
args.world.update_block_entity(&block_entity); args.world.update_block_entity(&block_entity);
BlockActionResult::SuccessServer BlockActionResult::SuccessServer
}) }
} }
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let entity = JigsawBlockEntity::new(*args.position); let entity = JigsawBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(entity)); args.world.add_block_entity(Arc::new(entity));
}) }
} }
fn mirror( fn mirror(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -1,6 +1,6 @@
use crate::block::registry::BlockActionResult; use crate::block::registry::BlockActionResult;
use crate::block::{ use crate::block::{
BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
UseWithItemArgs, UseWithItemArgs,
}; };
use pumpkin_data::BlockStateId; use pumpkin_data::BlockStateId;
@ -25,7 +25,7 @@ use super::redstone::block_receives_redstone_power;
pub struct NoteBlock; pub struct NoteBlock;
impl 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() { 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( let mut event = crate::plugin::api::events::block::note_play::NotePlayEvent::new(
*pos, *pos,
@ -33,12 +33,12 @@ impl NoteBlock {
props.note, props.note,
); );
if let Some(server) = world.server.upgrade() { 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 { if event.cancelled {
return; 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 { fn get_note_pitch(note: u16) -> f32 {
@ -70,116 +70,92 @@ impl NoteBlock {
} }
impl BlockBehaviour for NoteBlock { impl BlockBehaviour for NoteBlock {
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { let block_state = args.world.get_block_state(args.position);
let block_state = args.world.get_block_state(args.position); let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block); let powered = block_receives_redstone_power(args.world, args.position);
let powered = block_receives_redstone_power(args.world, args.position).await; // check if powered state changed
// check if powered state changed if note_props.powered != powered {
if note_props.powered != powered { if powered {
if powered { Self::play_note(&note_props, args.world, args.position);
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;
} }
}) note_props.powered = powered;
} args.world.set_block_state(
args.position,
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { note_props.to_state_id(args.block),
Box::pin(async move { BlockFlags::NOTIFY_ALL,
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,
); );
true }
})
} }
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
Box::pin(async move { let block_state = args.world.get_block_state(args.position);
Self::get_state_with_instrument( 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.world,
args.position, args.position,
Block::NOTE_BLOCK.default_state.id, args.state_id,
args.block, args.block,
) );
}) }
} args.state_id
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
})
} }
} }

View File

@ -7,7 +7,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use rand::RngExt; use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, BonemealArgs, RandomTickArgs}; use crate::block::{BlockBehaviour, BonemealArgs, RandomTickArgs};
use crate::world::World; use crate::world::World;
#[pumpkin_block_from_tag("minecraft:nylium")] #[pumpkin_block_from_tag("minecraft:nylium")]
@ -25,18 +25,14 @@ impl NyliumBlock {
} }
impl BlockBehaviour for NyliumBlock { impl BlockBehaviour for NyliumBlock {
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> { fn random_tick(&self, args: RandomTickArgs<'_>) {
Box::pin(async move { if !Self::can_be_nylium(args.world, args.position) {
if !Self::can_be_nylium(args.world, args.position) { args.world.set_block_state(
args.world args.position,
.set_block_state( Block::NETHERRACK.default_state.id,
args.position, BlockFlags::NOTIFY_ALL,
Block::NETHERRACK.default_state.id, );
BlockFlags::NOTIFY_ALL, }
)
.await;
}
})
} }
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool { fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
@ -50,26 +46,24 @@ impl BlockBehaviour for NyliumBlock {
true true
} }
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> { fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
Box::pin(async move { let world = args.world;
let world = args.world; let block = args.block;
let block = args.block; let above_pos = args.position.up();
let above_pos = args.position.up();
if block == &Block::CRIMSON_NYLIUM { if block == &Block::CRIMSON_NYLIUM {
place_crimson_vegetation(world, &above_pos).await; place_crimson_vegetation(world, &above_pos);
} else if block == &Block::WARPED_NYLIUM { } else if block == &Block::WARPED_NYLIUM {
place_warped_vegetation(world, &above_pos).await; place_warped_vegetation(world, &above_pos);
place_nether_sprouts(world, &above_pos).await; place_nether_sprouts(world, &above_pos);
if rand::rng().random_range(0..8) == 0 { if rand::rng().random_range(0..8) == 0 {
place_twisting_vines(world, &above_pos).await; 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 { for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3); 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); 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; continue;
} }
world world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
} }
} }
async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) { fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 { for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3); 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); 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; continue;
} }
world world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
} }
} }
async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) { fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 { for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3); 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); 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; continue;
} }
world world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
.await;
} }
} }
async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) { fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
for _ in 0..9 { for _ in 0..9 {
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3); 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); 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(); || !world.get_block_state(&current_pos.up()).is_air();
if is_top { if is_top {
world world.set_block_state(
.set_block_state( &current_pos,
&current_pos, Block::TWISTING_VINES.default_state.id,
Block::TWISTING_VINES.default_state.id, BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
)
.await;
break; break;
} }
world world.set_block_state(
.set_block_state( &current_pos,
&current_pos, Block::TWISTING_VINES_PLANT.default_state.id,
Block::TWISTING_VINES_PLANT.default_state.id, BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
)
.await;
current_pos = current_pos.up(); 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.moved_blocks.clear();
self.broken_blocks.clear(); self.broken_blocks.clear();
let (block, block_state) = self.world.get_block_and_state(&self.pos_to); let (block, block_state) = self.world.get_block_and_state(&self.pos_to);
@ -64,15 +64,13 @@ impl<'a> PistonHandler<'a> {
} }
return false; 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; return false;
} }
for i in 0..self.moved_blocks.len() { for i in 0..self.moved_blocks.len() {
let block_pos = self.moved_blocks[i]; let block_pos = self.moved_blocks[i];
let block = self.world.get_block(&block_pos); let block = self.world.get_block(&block_pos);
if Self::is_block_sticky(block) if Self::is_block_sticky(block) && !self.try_move_adjacent_block(block, &block_pos) {
&& !self.try_move_adjacent_block(block, &block_pos).await
{
return false; return false;
} }
} }
@ -98,7 +96,7 @@ impl<'a> PistonHandler<'a> {
|| (!self.retracted && pos == self.pos_from.offset(self.piston_direction.to_offset())) || (!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); let (mut block, block_state) = self.world.get_block_and_state(&pos);
if block_state.is_air() { if block_state.is_air() {
return true; return true;
@ -154,7 +152,7 @@ impl<'a> PistonHandler<'a> {
let block_pos3 = self.moved_blocks[m]; let block_pos3 = self.moved_blocks[m];
let block = self.world.get_block(&block_pos3); let block = self.world.get_block(&block_pos3);
if Self::is_block_sticky(block) 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; return false;
} }
@ -204,7 +202,7 @@ impl<'a> PistonHandler<'a> {
self.moved_blocks.extend(list3); 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() { for direction in BlockDirection::all() {
if direction.to_axis() == self.motion_direction.to_axis() { if direction.to_axis() == self.motion_direction.to_axis() {
continue; continue;
@ -212,7 +210,7 @@ impl<'a> PistonHandler<'a> {
let block_pos = pos.offset(direction.to_offset()); let block_pos = pos.offset(direction.to_offset());
let block_state2 = self.world.get_block(&block_pos); let block_state2 = self.world.get_block(&block_pos);
if Self::is_adjacent_block_stuck(block_state2, block) if Self::is_adjacent_block_stuck(block_state2, block)
&& !self.try_move(block_pos, direction).await && !self.try_move(block_pos, direction)
{ {
return false; return false;
} }

View File

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

View File

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

View File

@ -3,8 +3,8 @@ use pumpkin_data::{Block, FacingExt};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use crate::block::BlockBehaviour;
use crate::block::blocks::piston::piston::try_move; use crate::block::blocks::piston::piston::try_move;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::block::{BrokenArgs, OnNeighborUpdateArgs}; use crate::block::{BrokenArgs, OnNeighborUpdateArgs};
use super::piston::PistonProps; use super::piston::PistonProps;
@ -15,50 +15,43 @@ pub(crate) type PistonHeadProperties = pumpkin_data::block_properties::PistonHea
pub struct PistonHeadBlock; pub struct PistonHeadBlock;
impl BlockBehaviour for PistonHeadBlock { impl BlockBehaviour for PistonHeadBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { fn broken(&self, args: BrokenArgs<'_>) {
Box::pin(async move { let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD);
let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD); let pos = args
let pos = args .position
.position .offset(props.facing.opposite().to_block_direction().to_offset());
.offset(props.facing.opposite().to_block_direction().to_offset()); let (new_block, new_state) = args.world.get_block_and_state_id(&pos);
let (new_block, new_state) = args.world.get_block_and_state_id(&pos); if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block {
if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block { let props = PistonProps::from_state_id(new_state, new_block);
let props = PistonProps::from_state_id(new_state, new_block); if props.extended {
if props.extended { // TODO: use player
// TODO: use player args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
args.world
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
.await;
}
} }
}) }
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { let head_state_id = args.world.get_block_state_id(args.position);
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);
let head_props = if head_props.facing != Facing::Up {
PistonHeadProperties::from_state_id(head_state_id, &Block::PISTON_HEAD); return;
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 pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt; use rand::RngExt;
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, blocks::plant::PlantBlockBase}; use crate::block::{BlockBehaviour, CanPlaceAtArgs, blocks::plant::PlantBlockBase};
use crate::block::{ use crate::block::{
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs,
}; };
@ -37,105 +37,89 @@ impl BlockBehaviour for BambooBlock {
&& args.world.get_block_state(&top.up()).is_air() && args.world.get_block_state(&top.up()).is_air()
} }
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> { fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
Box::pin(async move { bone_meal(args.world, args.position);
bone_meal(Arc::clone(args.world), args.position).await;
})
} }
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let (block_below, state_id_below) =
let (block_below, state_id_below) = args.world.get_block_and_state_id(&args.position.down());
args.world.get_block_and_state_id(&args.position.down());
if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) { if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
let mut props = BambooLikeProperties::from_state_id( let mut props =
Block::BAMBOO.default_state.id, BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
&Block::BAMBOO, if block_below == &Block::BAMBOO_SAPLING {
); return Block::BAMBOO.default_state.id;
if block_below == &Block::BAMBOO_SAPLING { } else if block_below == &Block::BAMBOO {
return Block::BAMBOO.default_state.id; let props_below = BambooLikeProperties::from_state_id(state_id_below, block_below);
} else if block_below == &Block::BAMBOO { if props_below.age > 0 {
let props_below = props.age = 1;
BambooLikeProperties::from_state_id(state_id_below, block_below); }
if props_below.age > 0 { } else {
props.age = 1; let (block_above, state_id_above) =
} args.world.get_block_and_state_id(&args.position.up());
} else { if block_above == &Block::BAMBOO {
let (block_above, state_id_above) = let props_above =
args.world.get_block_and_state_id(&args.position.up()); BambooLikeProperties::from_state_id(state_id_above, block_above);
if block_above == &Block::BAMBOO { props.age = props_above.age;
let props_above = } else {
BambooLikeProperties::from_state_id(state_id_above, block_above); return Block::BAMBOO_SAPLING.default_state.id;
props.age = props_above.age;
} else {
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 { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position) <Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) { args.world
args.world .break_block(args.position, None, BlockFlags::empty());
.break_block(args.position, None, BlockFlags::empty()) } else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING {
.await; args.world.set_block_state(
} else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING { &args.position.down(),
args.world Block::BAMBOO.default_state.id,
.set_block_state( BlockFlags::empty(),
&args.position.down(), );
Block::BAMBOO.default_state.id, }
BlockFlags::empty(),
)
.await;
}
})
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) { args.world
args.world .schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
.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 { args.state_id
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
})
} }
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> { fn random_tick(&self, args: RandomTickArgs<'_>) {
Box::pin(async move { if rand::rng().random_range(0..=3) == 0 {
if rand::rng().random_range(0..=3) == 0 { update_leaves_and_grow(args.world, args.position);
update_leaves_and_grow(args.world.clone(), args.position).await; }
}
})
} }
} }
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 above_pos = position.up();
let below_pos = position.down(); let below_pos = position.down();
let two_below_pos = position.down_height(2); let two_below_pos = position.down_height(2);
@ -152,7 +136,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
return; return;
} }
let bamboo_count = count_bamboo_below(&world, position); let bamboo_count = count_bamboo_below(world, position);
if bamboo_count >= 16 { if bamboo_count >= 16 {
return; 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); BambooLikeProperties::from_state_id(state_id_two_below, block_two_below);
props_two_below.leaves = BambooLeaves::None; props_two_below.leaves = BambooLeaves::None;
world world.set_block_state(
.set_block_state( &below_pos,
&below_pos, props_below.to_state_id(block_below),
props_below.to_state_id(block_below), BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
) world.set_block_state(
.await; &two_below_pos,
world props_two_below.to_state_id(block_two_below),
.set_block_state( BlockFlags::NOTIFY_ALL,
&two_below_pos, );
props_two_below.to_state_id(block_two_below),
BlockFlags::NOTIFY_ALL,
)
.await;
} }
} }
@ -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), !((bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15),
); );
world world.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
.await;
} }
fn count_bamboo_below(world: &World, pos: &BlockPos) -> usize { fn count_bamboo_below(world: &World, pos: &BlockPos) -> usize {
@ -236,12 +214,12 @@ fn count_bamboo_above(world: &World, pos: &BlockPos) -> usize {
bamboo_count bamboo_count
} }
async fn bone_meal(world: Arc<World>, position: &BlockPos) { fn bone_meal(world: &Arc<World>, position: &BlockPos) {
let bamboo_below = count_bamboo_below(&world, position); let bamboo_below = count_bamboo_below(world, position);
let growth_amount = rand::rng().random_range(1..=2); 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; 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 // `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; 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 rand::RngExt;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnNeighborUpdateArgs,
OnNeighborUpdateArgs, blocks::plant::PlantBlockBase, blocks::plant::PlantBlockBase,
}; };
#[pumpkin_block("minecraft:bamboo_sapling")] #[pumpkin_block("minecraft:bamboo_sapling")]
@ -24,71 +24,63 @@ impl BlockBehaviour for BambooSaplingBlock {
&& args.world.get_block_state(&above).is_air() && args.world.get_block_state(&above).is_air()
} }
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> { fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
Box::pin(async move { {
grow_bamboo(args.world, args.position).await; grow_bamboo(args.world, args.position);
}) }
} }
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position) <Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { {
if args.block == &Block::BAMBOO_SAPLING if args.block == &Block::BAMBOO_SAPLING
&& args.world.get_block(&args.position.up()) == &Block::BAMBOO && args.world.get_block(&args.position.up()) == &Block::BAMBOO
{ {
args.world args.world.set_block_state(
.set_block_state( args.position,
args.position, Block::BAMBOO.default_state.id,
Block::BAMBOO.default_state.id, BlockFlags::NOTIFY_NEIGHBORS,
BlockFlags::NOTIFY_NEIGHBORS, );
)
.await;
} }
}) }
} }
fn get_state_for_neighbor_update<'a>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) { return Block::AIR.default_state.id;
return Block::AIR.default_state.id; }
} if args.direction == BlockDirection::Up
if args.direction == BlockDirection::Up && args.world.get_block(args.neighbor_position) == &Block::BAMBOO
&& args.world.get_block(args.neighbor_position) == &Block::BAMBOO {
{ return Block::BAMBOO.default_state.id;
return Block::BAMBOO.default_state.id; }
} args.state_id
args.state_id
})
} }
fn random_tick<'a>(&'a self, args: crate::block::RandomTickArgs<'a>) -> BlockFuture<'a, ()> { fn random_tick(&self, args: crate::block::RandomTickArgs<'_>) {
Box::pin(async move { let state_above = args.world.get_block_state(&args.position.up());
let state_above = args.world.get_block_state(&args.position.up()); if !state_above.is_air() || rand::rng().random_range(0..3) > 0 {
if !state_above.is_air() || rand::rng().random_range(0..3) > 0 { return;
return; }
} grow_bamboo(args.world, args.position);
grow_bamboo(args.world, args.position).await;
})
} }
} }
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 = let mut props =
BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO); BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
props.leaves = BambooLeaves::Small; props.leaves = BambooLeaves::Small;
world world.set_block_state(
.set_block_state( &position.up(),
&position.up(), props.to_state_id(&Block::BAMBOO),
props.to_state_id(&Block::BAMBOO), BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
)
.await;
} }
impl PlantBlockBase for BambooSaplingBlock { 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::blocks::redstone::block_receives_redstone_power;
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnEntityStepArgs,
OnEntityStepArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
}; };
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::ai::pathfinder::node::Coordinate; use crate::entity::ai::pathfinder::node::Coordinate;
@ -30,12 +30,12 @@ use rand::RngExt;
pub struct BigDripleafBlock; pub struct BigDripleafBlock;
impl BlockBehaviour for BigDripleafBlock { impl BlockBehaviour for BigDripleafBlock {
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_step(&self, args: OnEntityStepArgs<'_>) {
Box::pin(async move { {
let props = BigDripleafLikeProperties::from_state_id(args.state.id, args.block); let props = BigDripleafLikeProperties::from_state_id(args.state.id, args.block);
if props.tilt == Tilt::None if props.tilt == Tilt::None
&& can_entity_tilt(args.position, args.entity) && 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( set_tilt_and_schedule_tick(
args.state.id, args.state.id,
@ -43,96 +43,81 @@ impl BlockBehaviour for BigDripleafBlock {
args.position, args.position,
Tilt::Unstable, Tilt::Unstable,
None, None,
) );
.await;
} }
}) }
} }
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
Box::pin(async move { let state = args.world.get_block_state(args.position);
let state = args.world.get_block_state(args.position); let props = BigDripleafLikeProperties::from_state_id(state.id, &Block::BIG_DRIPLEAF);
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);
if props.tilt == Tilt::Unstable { if props.tilt == Tilt::Unstable {
set_tilt_and_schedule_tick( set_tilt_and_schedule_tick(
state.id, state.id,
args.world, args.world,
args.position, args.position,
Tilt::Partial, Tilt::Partial,
Some(Sound::BlockBigDripleafTiltDown), Some(Sound::BlockBigDripleafTiltDown),
) );
.await; } else if props.tilt == Tilt::Partial {
} else if props.tilt == Tilt::Partial { set_tilt_and_schedule_tick(
set_tilt_and_schedule_tick( state.id,
state.id, args.world,
args.world, args.position,
args.position, Tilt::Full,
Tilt::Full, Some(Sound::BlockBigDripleafTiltDown),
Some(Sound::BlockBigDripleafTiltDown), );
) } else if props.tilt == Tilt::Full {
.await; reset_tilt(state.id, args.world, args.position);
} else if props.tilt == Tilt::Full { }
reset_tilt(state.id, args.world, args.position).await;
}
}
})
} }
//TODO: onProjectileHit //TODO: onProjectileHit
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool { fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position) <Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
} }
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
Box::pin(async move { let (support_block, support_block_state_id) =
let (support_block, support_block_state_id) = args.world.get_block_and_state_id(&args.position.down());
args.world.get_block_and_state_id(&args.position.down()); let facing = if support_block == &Block::BIG_DRIPLEAF {
let facing = if support_block == &Block::BIG_DRIPLEAF { get_dripleaf_facing_dir(support_block_state_id)
get_dripleaf_facing_dir(support_block_state_id) } else {
} else { args.player
args.player .living_entity
.living_entity .entity
.entity .get_horizontal_facing()
.get_horizontal_facing() .opposite()
.opposite() };
}; let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
dripleaf_props.facing = facing; dripleaf_props.facing = facing;
dripleaf_props.waterlogged = args.replacing.water_source(); 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>( fn get_state_for_neighbor_update(
&'a self, &self,
args: GetStateForNeighborUpdateArgs<'a>, args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockFuture<'a, BlockStateId> { ) -> BlockStateId {
Box::pin(async move { <Self as PlantBlockBase>::get_state_for_neighbor_update(
<Self as PlantBlockBase>::get_state_for_neighbor_update( self,
self, args.world,
args.world, args.position,
args.position, args.state_id,
args.state_id, )
)
.await
})
} }
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> { fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
Box::pin(async move { {
if block_receives_redstone_power(args.world, args.position).await { if block_receives_redstone_power(args.world, args.position) {
let state_id = args.world.get_block_state_id(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. /// 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, ()> { fn placed(&self, args: PlacedArgs<'_>) {
Box::pin(async move { {
let support_pos = args.position.down(); let support_pos = args.position.down();
let (support_block, support_state_id) = args.world.get_block_and_state_id(&support_pos); let (support_block, support_state_id) = args.world.get_block_and_state_id(&support_pos);
if support_block == &Block::BIG_DRIPLEAF { 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.facing = old_dripleaf_props.facing;
dripleaf_stem_props.waterlogged = old_dripleaf_props.waterlogged; dripleaf_stem_props.waterlogged = old_dripleaf_props.waterlogged;
args.world args.world.set_block_state(
.set_block_state( &support_pos,
&support_pos, dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM),
dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM), BlockFlags::empty(),
BlockFlags::empty(), );
)
.await;
} }
}) }
} }
/// if the leaf is broken, turn the stem below into a leaf. fn broken(&self, args: BrokenArgs<'_>) {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> { handle_big_dripleaf_breaking(args.world, args.position);
Box::pin(async move { handle_big_dripleaf_breaking(args.world, args.position).await })
} }
} }
async fn set_tilt_and_schedule_tick( fn set_tilt_and_schedule_tick(
state_id: BlockStateId, state_id: BlockStateId,
world: &Arc<World>, world: &Arc<World>,
pos: &BlockPos, pos: &BlockPos,
tilt: Tilt, tilt: Tilt,
sound_wrapper: Option<Sound>, 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 { if let Some(tilt_sound) = sound_wrapper {
play_tilt_sound(world, pos, tilt_sound); 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); fn reset_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos) {
let v = pos.as_vector3(); set_tilt(state_id, world, pos, Tilt::None);
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;
let props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF); let props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
if props.tilt != Tilt::None { if props.tilt != Tilt::None {
play_tilt_sound(world, pos, Sound::BlockBigDripleafTiltUp); 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); let mut props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
props.tilt = new_tilt; props.tilt = new_tilt;
world world.set_block_state(
.set_block_state( pos,
pos, props.to_state_id(&Block::BIG_DRIPLEAF),
props.to_state_id(&Block::BIG_DRIPLEAF), BlockFlags::NOTIFY_ALL,
BlockFlags::NOTIFY_ALL, );
) }
.await;
//todo GameEvents? 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 { fn can_entity_tilt<T: EntityBase + ?Sized>(pos: &BlockPos, entity: &T) -> bool {
entity.get_entity().on_ground.load(Ordering::Relaxed) entity.get_entity().on_ground.load(Ordering::Relaxed)
@ -229,8 +211,7 @@ impl PlantBlockBase for BigDripleafBlock {
let support_block = block_accessor.get_block(pos); let support_block = block_accessor.get_block(pos);
can_plant_dripleaf_on_top(support_block) can_plant_dripleaf_on_top(support_block)
} }
#[allow(clippy::unused_async_trait_impl)] fn get_state_for_neighbor_update(
async fn get_state_for_neighbor_update(
&self, &self,
block_accessor: &dyn BlockAccessor, block_accessor: &dyn BlockAccessor,
block_pos: &BlockPos, block_pos: &BlockPos,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,10 +10,7 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_world::world::{BlockAccessor, BlockFlags}; use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rand::RngExt; use rand::RngExt;
use crate::{ use crate::{block::blocks::plant::PlantBlockBase, world::World};
block::blocks::plant::PlantBlockBase, plugin::api::events::block::block_grow::BlockGrowEvent,
world::World,
};
type CropProperties = WheatLikeProperties; type CropProperties = WheatLikeProperties;
type FarmlandProperties = FarmlandLikeProperties; type FarmlandProperties = FarmlandLikeProperties;
@ -57,44 +54,25 @@ trait CropBlockBase: PlantBlockBase {
self.get_age(state, block) < self.max_age() 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 (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block); let age = self.get_age(state, block);
let new_age = (age + self.bonemeal_age_increase()).min(self.max_age()); let new_age = (age + self.bonemeal_age_increase()).min(self.max_age());
world world.set_block_state(
.set_block_state( pos,
pos, self.state_with_age(block, state, new_age),
self.state_with_age(block, state, new_age), BlockFlags::NOTIFY_LISTENERS,
BlockFlags::NOTIFY_LISTENERS, );
)
.await;
} }
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 (block, state) = world.get_block_and_state_id(pos);
let age = self.get_age(state, block); let age = self.get_age(state, block);
if age < self.max_age() { 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 { 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); let new_state_id = self.state_with_age(block, state, age + 1);
if let Some(server) = world.server.upgrade() { world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
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;
} }
} }
} }
@ -102,7 +80,7 @@ trait CropBlockBase: PlantBlockBase {
//TODO add impl for light level //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 mut moisture = 1.0;
let down_pos = pos.down(); let down_pos = pos.down();

View File

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

View File

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

View File

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

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