mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
refactor: from async to sync Part 1
This commit is contained in:
@@ -556,7 +556,7 @@ mod tests {
|
||||
},
|
||||
};
|
||||
use pumpkin_world::inventory::SimpleInventory;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
entity_equipment::EntityEquipment,
|
||||
@@ -911,7 +911,7 @@ mod tests {
|
||||
player_inventory
|
||||
.main_inventory
|
||||
.write()
|
||||
.await
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.fill_with(|| ItemStack::new(64, &Item::COBBLESTONE));
|
||||
merchant_inventory
|
||||
.set_stack(0, ItemStack::new(9, &Item::EMERALD))
|
||||
|
||||
@@ -19,9 +19,9 @@ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use tracing::warn;
|
||||
|
||||
/// The player's inventory.
|
||||
@@ -99,27 +99,36 @@ impl PlayerInventory {
|
||||
/// Gets the item in the currently selected hotbar slot.
|
||||
///
|
||||
/// This is the item the player is currently holding in their main hand.
|
||||
pub async fn held_item(&self) -> ItemStack {
|
||||
let inv = self.main_inventory.read().await;
|
||||
pub fn held_item(&self) -> ItemStack {
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv[self.get_selected_slot() as usize].clone()
|
||||
}
|
||||
|
||||
/// Sets the item in the currently selected hotbar slot.
|
||||
pub async fn set_held_item(&self, stack: ItemStack) {
|
||||
pub fn set_held_item(&self, stack: ItemStack) {
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv[selected] = stack;
|
||||
}
|
||||
|
||||
/// Sets the item in the specified hand.
|
||||
pub async fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) {
|
||||
pub fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) {
|
||||
match hand {
|
||||
Hand::Right => self.set_held_item(stack).await,
|
||||
Hand::Right => self.set_held_item(stack),
|
||||
Hand::Left => {
|
||||
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
|
||||
return;
|
||||
};
|
||||
self.entity_equipment.lock().await.put(slot, stack);
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.put(slot, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,34 +137,43 @@ impl PlayerInventory {
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `hand` - Which hand to get the item from
|
||||
pub async fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
|
||||
pub fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
|
||||
match hand {
|
||||
Hand::Left => self.off_hand_item().await,
|
||||
Hand::Right => self.held_item().await,
|
||||
Hand::Left => self.off_hand_item(),
|
||||
Hand::Right => self.held_item(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the item in the off-hand.
|
||||
///
|
||||
/// Mojang name: `getOffHandStack`
|
||||
pub async fn off_hand_item(&self) -> ItemStack {
|
||||
pub fn off_hand_item(&self) -> ItemStack {
|
||||
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
|
||||
return ItemStack::EMPTY.clone();
|
||||
};
|
||||
self.entity_equipment.lock().await.get(slot)
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(slot)
|
||||
}
|
||||
|
||||
/// Swaps the items between main hand and off-hand.
|
||||
///
|
||||
/// # Returns
|
||||
/// The new main hand item and new off-hand item.
|
||||
pub async fn swap_item(&self) -> (ItemStack, ItemStack) {
|
||||
pub fn swap_item(&self) -> (ItemStack, ItemStack) {
|
||||
let Some(slot) = self.equipment_slots.get(&Self::OFF_HAND_SLOT) else {
|
||||
return (ItemStack::EMPTY.clone(), ItemStack::EMPTY.clone());
|
||||
};
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
let mut equipment = self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut main_inv = self.main_inventory.write().await;
|
||||
let mut main_inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let main_hand_item = main_inv[selected].clone();
|
||||
let new_main = equipment.put(slot, main_hand_item.clone());
|
||||
main_inv[selected] = new_main.clone();
|
||||
@@ -169,27 +187,30 @@ impl PlayerInventory {
|
||||
}
|
||||
|
||||
/// Adds a stack to any available slot, prioritizing stacking with existing items.
|
||||
async fn add_stack(&self, stack: ItemStack) -> usize {
|
||||
let mut slot_index = self.get_occupied_slot_with_room_for_stack(&stack).await;
|
||||
fn add_stack(&self, stack: ItemStack) -> usize {
|
||||
let mut slot_index = self.get_occupied_slot_with_room_for_stack(&stack);
|
||||
|
||||
if slot_index == -1 {
|
||||
slot_index = self.get_empty_slot().await;
|
||||
slot_index = self.get_empty_slot();
|
||||
}
|
||||
|
||||
if slot_index == -1 {
|
||||
stack.item_count as usize
|
||||
} else {
|
||||
self.add_stack_to_slot(slot_index as usize, stack).await
|
||||
self.add_stack_to_slot(slot_index as usize, stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a stack to a specific slot.
|
||||
///
|
||||
/// Returns the number of items that couldn't fit.
|
||||
async fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize {
|
||||
fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize {
|
||||
if slot >= Self::MAIN_SIZE {
|
||||
if let Some(slot_type) = self.equipment_slots.get(&slot) {
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
let mut equipment = self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let current = equipment.get(slot_type);
|
||||
if current.is_empty() {
|
||||
equipment.put(slot_type, stack);
|
||||
@@ -199,7 +220,10 @@ impl PlayerInventory {
|
||||
return stack.item_count as usize;
|
||||
}
|
||||
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let mut stack_count = stack.item_count;
|
||||
let self_stack = &mut inv[slot];
|
||||
|
||||
@@ -221,8 +245,11 @@ impl PlayerInventory {
|
||||
///
|
||||
/// # Returns
|
||||
/// The slot index or -1 if inventory is full.
|
||||
async fn get_empty_slot(&self) -> i16 {
|
||||
let inv = self.main_inventory.read().await;
|
||||
fn get_empty_slot(&self) -> i16 {
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
for (i, stack) in inv.iter().enumerate() {
|
||||
if stack.is_empty() {
|
||||
return i as i16;
|
||||
@@ -242,14 +269,17 @@ impl PlayerInventory {
|
||||
/// Finds a slot with the same item type that has room for more items.
|
||||
///
|
||||
/// Checks selected slot, off-hand, then other slots.
|
||||
async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
|
||||
fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let inv = self.main_inventory.read().await;
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if Self::can_stack_add_more(&inv[selected], stack) {
|
||||
return selected as i16;
|
||||
}
|
||||
|
||||
let off_hand = self.off_hand_item().await;
|
||||
let off_hand = self.off_hand_item();
|
||||
if Self::can_stack_add_more(&off_hand, stack) {
|
||||
return Self::OFF_HAND_SLOT as i16;
|
||||
}
|
||||
@@ -270,8 +300,8 @@ impl PlayerInventory {
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if any items were inserted, `false` otherwise.
|
||||
pub async fn insert_stack_anywhere(&self, stack: &mut ItemStack) -> bool {
|
||||
self.insert_stack(-1, stack).await
|
||||
pub fn insert_stack_anywhere(&self, stack: &mut ItemStack) -> bool {
|
||||
self.insert_stack(-1, stack)
|
||||
}
|
||||
|
||||
/// Inserts a stack into a specific slot or any slot.
|
||||
@@ -282,7 +312,7 @@ impl PlayerInventory {
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if any items were inserted, `false` otherwise.
|
||||
pub async fn insert_stack(&self, slot: i16, stack: &mut ItemStack) -> bool {
|
||||
pub fn insert_stack(&self, slot: i16, stack: &mut ItemStack) -> bool {
|
||||
if stack.is_empty() {
|
||||
return false;
|
||||
}
|
||||
@@ -292,9 +322,9 @@ impl PlayerInventory {
|
||||
loop {
|
||||
i = stack.item_count;
|
||||
if slot == -1 {
|
||||
stack.set_count(self.add_stack(stack.clone()).await as u8);
|
||||
stack.set_count(self.add_stack(stack.clone()) as u8);
|
||||
} else {
|
||||
stack.set_count(self.add_stack_to_slot(slot as usize, stack.clone()).await as u8);
|
||||
stack.set_count(self.add_stack_to_slot(slot as usize, stack.clone()) as u8);
|
||||
}
|
||||
|
||||
if stack.is_empty() || stack.item_count >= i {
|
||||
@@ -309,8 +339,11 @@ impl PlayerInventory {
|
||||
///
|
||||
/// # Returns
|
||||
/// The slot index or -1 if not found.
|
||||
pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
|
||||
let inv = self.main_inventory.read().await;
|
||||
pub fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
for (i, item) in inv.iter().enumerate() {
|
||||
if !item.is_empty() && item.are_items_and_components_equal(stack) {
|
||||
return i as i16;
|
||||
@@ -322,8 +355,11 @@ impl PlayerInventory {
|
||||
/// Finds an empty hotbar slot to swap an item to.
|
||||
///
|
||||
/// First looks for empty slots, then slots without enchantments.
|
||||
async fn get_swappable_hotbar_slot(&self) -> usize {
|
||||
let inv = self.main_inventory.read().await;
|
||||
fn get_swappable_hotbar_slot(&self) -> usize {
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let selected_slot = self.get_selected_slot() as usize;
|
||||
for i in 0..Self::HOTBAR_SIZE {
|
||||
let check_index = (i + selected_slot) % 9;
|
||||
@@ -338,11 +374,14 @@ impl PlayerInventory {
|
||||
/// Swaps an item stack with an item on the hotbar.
|
||||
///
|
||||
/// Finds an empty hotbar slot and places the stack there.
|
||||
pub async fn swap_stack_with_hotbar(&self, stack: ItemStack) {
|
||||
let swappable = self.get_swappable_hotbar_slot().await;
|
||||
pub fn swap_stack_with_hotbar(&self, stack: ItemStack) {
|
||||
let swappable = self.get_swappable_hotbar_slot();
|
||||
self.set_selected_slot(swappable as u8);
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
if let Some(empty_slot) = inv.iter().position(ItemStack::is_empty)
|
||||
&& !inv[selected].is_empty()
|
||||
@@ -354,11 +393,14 @@ impl PlayerInventory {
|
||||
}
|
||||
|
||||
/// Swaps the items at two slot indices.
|
||||
pub async fn swap_slot_with_hotbar(&self, slot: usize) {
|
||||
let swappable = self.get_swappable_hotbar_slot().await;
|
||||
pub fn swap_slot_with_hotbar(&self, slot: usize) {
|
||||
let swappable = self.get_swappable_hotbar_slot();
|
||||
self.set_selected_slot(swappable as u8);
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv.swap(selected, slot);
|
||||
}
|
||||
|
||||
@@ -376,9 +418,9 @@ impl PlayerInventory {
|
||||
pub async fn offer(&self, stack: ItemStack, notify_client: bool, player: &dyn InventoryPlayer) {
|
||||
let mut stack = stack;
|
||||
while !stack.is_empty() {
|
||||
let mut room_for_stack = self.get_occupied_slot_with_room_for_stack(&stack).await;
|
||||
let mut room_for_stack = self.get_occupied_slot_with_room_for_stack(&stack);
|
||||
if room_for_stack == -1 {
|
||||
room_for_stack = self.get_empty_slot().await;
|
||||
room_for_stack = self.get_empty_slot();
|
||||
}
|
||||
|
||||
if room_for_stack == -1 {
|
||||
@@ -388,11 +430,7 @@ impl PlayerInventory {
|
||||
|
||||
let items_fit = stack.get_max_stack_size()
|
||||
- self.get_stack(room_for_stack as usize).await.item_count;
|
||||
if self
|
||||
.insert_stack(room_for_stack, &mut stack.split(items_fit))
|
||||
.await
|
||||
&& notify_client
|
||||
{
|
||||
if self.insert_stack(room_for_stack, &mut stack.split(items_fit)) && notify_client {
|
||||
player
|
||||
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
|
||||
i32::from(room_for_stack).into(),
|
||||
@@ -407,9 +445,15 @@ impl PlayerInventory {
|
||||
impl Clearable for PlayerInventory {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.entity_equipment.lock().await.clear();
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clear();
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -421,13 +465,20 @@ impl Inventory for PlayerInventory {
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
let inv = self.main_inventory.read().await;
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if inv.iter().any(|s| !s.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for slot in self.equipment_slots.values() {
|
||||
let eq_item = self.entity_equipment.lock().await.get(slot);
|
||||
let eq_item = self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(slot);
|
||||
if !eq_item.is_empty() {
|
||||
return false;
|
||||
}
|
||||
@@ -440,10 +491,16 @@ impl Inventory for PlayerInventory {
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let inv = self.main_inventory.read().await;
|
||||
let inv = self
|
||||
.main_inventory
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv[slot].clone()
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment.lock().await.get(slot)
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.get(slot)
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
@@ -453,12 +510,15 @@ impl Inventory for PlayerInventory {
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.put(slot, ItemStack::EMPTY.clone())
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
@@ -469,14 +529,20 @@ impl Inventory for PlayerInventory {
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if !inv[slot].is_empty() && amount > 0 {
|
||||
inv[slot].split(amount)
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
let mut equipment = self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let mut stack = equipment.get(slot);
|
||||
|
||||
if !stack.is_empty() && amount > 0 {
|
||||
@@ -495,10 +561,16 @@ impl Inventory for PlayerInventory {
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut inv = self
|
||||
.main_inventory
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
inv[slot] = stack;
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment.lock().await.put(slot, stack);
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.put(slot, stack);
|
||||
} else {
|
||||
warn!("Failed to get Equipment Slot at {slot}");
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ use pumpkin_data::{
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockAccessor;
|
||||
|
||||
use crate::{
|
||||
block::{BlockFuture, GetStateForNeighborUpdateArgs},
|
||||
entity::player::Player,
|
||||
};
|
||||
use crate::{block::GetStateForNeighborUpdateArgs, entity::player::Player};
|
||||
|
||||
pub trait WallMountedBlock: Send + Sync {
|
||||
fn get_direction(&self, state_id: BlockStateId, block: &Block) -> BlockDirection;
|
||||
@@ -49,10 +46,10 @@ pub trait WallMountedBlock: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at<'a>(
|
||||
&'a self,
|
||||
world: &'a dyn BlockAccessor,
|
||||
pos: &'a BlockPos,
|
||||
fn can_place_at(
|
||||
&self,
|
||||
world: &dyn BlockAccessor,
|
||||
pos: &BlockPos,
|
||||
direction: BlockDirection,
|
||||
) -> bool {
|
||||
let block_pos = pos.offset(direction.to_offset());
|
||||
@@ -60,18 +57,16 @@ pub trait WallMountedBlock: Send + Sync {
|
||||
block_state.is_side_solid(direction.opposite())
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if self.get_direction(args.state_id, args.block).opposite() == args.direction
|
||||
&& !self.can_place_at(args.world, args.position, args.direction)
|
||||
{
|
||||
Block::AIR.default_state.id
|
||||
} else {
|
||||
args.state_id
|
||||
}
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if self.get_direction(args.state_id, args.block).opposite() == args.direction
|
||||
&& !self.can_place_at(args.world, args.position, args.direction)
|
||||
{
|
||||
Block::AIR.default_state.id
|
||||
} else {
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use pumpkin_world::world::BlockFlags;
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnPlaceArgs, RandomTickArgs, blocks::abstract_wall_mounting::WallMountedBlock,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
RandomTickArgs, blocks::abstract_wall_mounting::WallMountedBlock,
|
||||
};
|
||||
|
||||
const ALL_DIRECTIONS: [BlockDirection; 6] = [
|
||||
@@ -35,16 +35,12 @@ impl BlockMetadata for AmethystBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for AmethystBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = AmethystClusterLikeProperties::from_state_id(
|
||||
args.block.default_state.id,
|
||||
args.block,
|
||||
);
|
||||
props.facing = args.direction.to_facing();
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
AmethystClusterLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.facing = args.direction.to_facing();
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -56,11 +52,11 @@ impl BlockBehaviour for AmethystBlock {
|
||||
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, direction)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move { WallMountedBlock::get_state_for_neighbor_update(self, args).await })
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
WallMountedBlock::get_state_for_neighbor_update(self, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,56 +71,53 @@ impl WallMountedBlock for AmethystBlock {
|
||||
pub struct BuddingAmethystBlock;
|
||||
|
||||
impl BlockBehaviour for BuddingAmethystBlock {
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if rand::rng().random_range(0..5) == 0 {
|
||||
let grow_direction = {
|
||||
let mut rng = rand::rng();
|
||||
ALL_DIRECTIONS[rng.random_range(0..ALL_DIRECTIONS.len())]
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if rand::rng().random_range(0..5) == 0 {
|
||||
let grow_direction = {
|
||||
let mut rng = rand::rng();
|
||||
ALL_DIRECTIONS[rng.random_range(0..ALL_DIRECTIONS.len())]
|
||||
};
|
||||
let grow_pos = args.position.offset(grow_direction.to_offset());
|
||||
let (relative_block, relative_state) = args.world.get_block_and_state(&grow_pos);
|
||||
let relative_state_id = relative_state.id;
|
||||
|
||||
let next_stage_and_water =
|
||||
if can_cluster_grow_at_state(relative_block, relative_state_id) {
|
||||
Some((&Block::SMALL_AMETHYST_BUD, relative_block == &Block::WATER))
|
||||
} else if relative_block == &Block::SMALL_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::SMALL_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::MEDIUM_AMETHYST_BUD, props.waterlogged))
|
||||
} else if relative_block == &Block::MEDIUM_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::MEDIUM_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::LARGE_AMETHYST_BUD, props.waterlogged))
|
||||
} else if relative_block == &Block::LARGE_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::LARGE_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::AMETHYST_CLUSTER, props.waterlogged))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let grow_pos = args.position.offset(grow_direction.to_offset());
|
||||
let (relative_block, relative_state) = args.world.get_block_and_state(&grow_pos);
|
||||
let relative_state_id = relative_state.id;
|
||||
|
||||
let next_stage_and_water =
|
||||
if can_cluster_grow_at_state(relative_block, relative_state_id) {
|
||||
Some((&Block::SMALL_AMETHYST_BUD, relative_block == &Block::WATER))
|
||||
} else if relative_block == &Block::SMALL_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::SMALL_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::MEDIUM_AMETHYST_BUD, props.waterlogged))
|
||||
} else if relative_block == &Block::MEDIUM_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::MEDIUM_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::LARGE_AMETHYST_BUD, props.waterlogged))
|
||||
} else if relative_block == &Block::LARGE_AMETHYST_BUD {
|
||||
let props = AmethystClusterLikeProperties::from_state_id(
|
||||
relative_state_id,
|
||||
&Block::LARGE_AMETHYST_BUD,
|
||||
);
|
||||
(props.facing == grow_direction.to_facing())
|
||||
.then_some((&Block::AMETHYST_CLUSTER, props.waterlogged))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((next_stage, waterlogged)) = next_stage_and_water {
|
||||
let mut target_props = AmethystClusterLikeProperties::default(next_stage);
|
||||
target_props.facing = grow_direction.to_facing();
|
||||
target_props.waterlogged = waterlogged;
|
||||
let target_state_id = target_props.to_state_id(next_stage);
|
||||
args.world
|
||||
.set_block_state(&grow_pos, target_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
}
|
||||
if let Some((next_stage, waterlogged)) = next_stage_and_water {
|
||||
let mut target_props = AmethystClusterLikeProperties::default(next_stage);
|
||||
target_props.facing = grow_direction.to_facing();
|
||||
target_props.waterlogged = waterlogged;
|
||||
let target_state_id = target_props.to_state_id(next_stage);
|
||||
args.world
|
||||
.set_block_state(&grow_pos, target_state_id, BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::block::blocks::falling::FallingBlock;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
PlacedArgs,
|
||||
};
|
||||
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -23,58 +23,50 @@ use tokio::sync::Mutex;
|
||||
pub struct AnvilBlock;
|
||||
|
||||
impl BlockBehaviour for AnvilBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&AnvilScreenFactory, Some(*args.position))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&AnvilScreenFactory, Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::placed(&FallingBlock, args).await;
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
FallingBlock::placed(&FallingBlock, args);
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let dir = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.rotate_clockwise();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let dir = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.rotate_clockwise();
|
||||
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
|
||||
props.facing = dir;
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.facing = dir;
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args);
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(
|
||||
async move { FallingBlock::get_state_for_neighbor_update(&FallingBlock, args).await },
|
||||
)
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
FallingBlock::get_state_for_neighbor_update(&FallingBlock, args)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
PlacedArgs,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -17,46 +16,39 @@ use std::sync::Arc;
|
||||
pub struct BannerBlock;
|
||||
|
||||
impl BlockBehaviour for BannerBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let entity = BannerBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = WhiteBannerLikeProperties::default(args.block);
|
||||
props.rotation = args.player.get_entity().get_flipped_rotation_16();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = WhiteBannerLikeProperties::default(args.block);
|
||||
props.rotation = args.player.get_entity().get_flipped_rotation_16();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: crate::block::CanPlaceAtArgs<'_>) -> bool {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::{BlockFuture, GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
|
||||
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
|
||||
use crate::block::{
|
||||
registry::BlockActionResult,
|
||||
{BlockBehaviour, NormalUseArgs},
|
||||
@@ -50,54 +50,47 @@ impl ScreenHandlerFactory for BarrelScreenFactory {
|
||||
pub struct BarrelBlock;
|
||||
|
||||
impl BlockBehaviour for BarrelBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = BarrelLikeProperties::default(args.block);
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = BarrelLikeProperties::default(args.block);
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
|
||||
1,
|
||||
)
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&BarrelScreenFactory(inventory), Some(pos))
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&BarrelScreenFactory(inventory), Some(*args.position))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(barrel_block_entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(barrel_block_entity));
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
inventory.as_ref(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs};
|
||||
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::{
|
||||
BlockProperties, MangroveRootsLikeProperties as BarrierLikeProperties,
|
||||
@@ -11,29 +11,25 @@ use pumpkin_world::tick::TickPriority;
|
||||
pub struct BarrierBlock;
|
||||
|
||||
impl BlockBehaviour for BarrierBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = BarrierLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = BarrierLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = BarrierLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let props = BarrierLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs};
|
||||
|
||||
// Create the factory just like ChestScreenFactory
|
||||
struct BeaconScreenFactory(Arc<dyn Inventory>);
|
||||
@@ -48,29 +48,29 @@ impl ScreenHandlerFactory for BeaconScreenFactory {
|
||||
pub struct BeaconBlock;
|
||||
|
||||
impl BlockBehaviour for BeaconBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let block_entity = args.world.get_block_entity(args.position);
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let block_entity = args.world.get_block_entity(args.position);
|
||||
|
||||
// Extract the inventory from the entity
|
||||
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
|
||||
return BlockActionResult::Fail;
|
||||
};
|
||||
// Extract the inventory from the entity
|
||||
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
|
||||
return BlockActionResult::Fail;
|
||||
};
|
||||
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBeacon as i32,
|
||||
1,
|
||||
)
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBeacon as i32,
|
||||
1,
|
||||
);
|
||||
|
||||
// Open the screen using the factory
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&BeaconScreenFactory(inventory), Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
// Open the screen using the factory
|
||||
args.player
|
||||
.open_handled_screen(&BeaconScreenFactory(inventory), Some(*args.position))
|
||||
.await;
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ use pumpkin_util::GameMode;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::OnLandedUponArgs;
|
||||
use crate::block::UpdateEntityMovementAfterFallOnArgs;
|
||||
use crate::block::bounce_entity_after_fall;
|
||||
@@ -22,7 +21,7 @@ use crate::block::{
|
||||
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, OnStateReplacedArgs,
|
||||
PlacedArgs, PlayerPlacedArgs,
|
||||
};
|
||||
use crate::entity::{Entity, EntityBase};
|
||||
use crate::entity::{Entity, EntityBase, player::Player};
|
||||
use crate::world::World;
|
||||
|
||||
type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties;
|
||||
@@ -84,36 +83,27 @@ impl BlockBehaviour for BedBlock {
|
||||
false
|
||||
}
|
||||
|
||||
fn on_landed_upon<'a>(&'a self, args: OnLandedUponArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(living) = args.entity.get_living_entity() {
|
||||
living
|
||||
.handle_fall_damage(args.entity, args.fall_distance * 0.5, 1.0)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_landed_upon(&self, args: OnLandedUponArgs<'_>) {
|
||||
if let Some(living) = args.entity.get_living_entity() {
|
||||
living.handle_fall_damage(args.entity, args.fall_distance * 0.5, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_entity_movement_after_fall_on<'a>(
|
||||
&'a self,
|
||||
args: UpdateEntityMovementAfterFallOnArgs<'a>,
|
||||
) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { bounce_entity_after_fall(args.entity, 0.66) })
|
||||
fn update_entity_movement_after_fall_on(&self, args: UpdateEntityMovementAfterFallOnArgs<'_>) {
|
||||
bounce_entity_after_fall(args.entity, 0.66);
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut bed_props = BedProperties::default(args.block);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut bed_props = BedProperties::default(args.block);
|
||||
|
||||
bed_props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
bed_props.part = BedPart::Foot;
|
||||
bed_props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
bed_props.part = BedPart::Foot;
|
||||
|
||||
bed_props.to_state_id(args.block)
|
||||
})
|
||||
bed_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let bed_entity = BedBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(bed_entity));
|
||||
|
||||
@@ -122,283 +112,275 @@ impl BlockBehaviour for BedBlock {
|
||||
bed_head_props.part = BedPart::Head;
|
||||
|
||||
let bed_head_pos = args.position.offset(bed_head_props.facing.to_offset());
|
||||
args.world
|
||||
.set_block_state(
|
||||
&bed_head_pos,
|
||||
bed_head_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&bed_head_pos,
|
||||
bed_head_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
|
||||
);
|
||||
|
||||
let bed_head_entity = BedBlockEntity::new(bed_head_pos);
|
||||
args.world.add_block_entity(Arc::new(bed_head_entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
||||
{
|
||||
args.world.play_bedrock_level_sound(
|
||||
"place",
|
||||
&args.position.to_centered_f64(),
|
||||
i32::from(pumpkin_data::BlockState::to_be_network_id(args.state_id)),
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let bed_props = BedProperties::from_state_id(args.state.id, args.block);
|
||||
let other_half_pos = if bed_props.part == BedPart::Head {
|
||||
args.position
|
||||
.offset(bed_props.facing.opposite().to_offset())
|
||||
} else {
|
||||
args.position.offset(bed_props.facing.to_offset())
|
||||
};
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
let bed_props = BedProperties::from_state_id(args.state.id, args.block);
|
||||
let other_half_pos = if bed_props.part == BedPart::Head {
|
||||
args.position
|
||||
.offset(bed_props.facing.opposite().to_offset())
|
||||
} else {
|
||||
args.position.offset(bed_props.facing.to_offset())
|
||||
};
|
||||
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
|
||||
if neighbor_state_id.to_block_id() != args.block.id {
|
||||
args.world.update_neighbors(&other_half_pos, None);
|
||||
return;
|
||||
}
|
||||
|
||||
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
|
||||
if neighbor_state_id.to_block_id() != args.block.id {
|
||||
args.world.update_neighbors(&other_half_pos, None).await;
|
||||
return;
|
||||
}
|
||||
let is_creative = args.player.gamemode.load() == GameMode::Creative;
|
||||
let flags = if bed_props.part == BedPart::Foot && !is_creative {
|
||||
// Breaking foot in survival -> allow head to drop
|
||||
BlockFlags::NOTIFY_NEIGHBORS
|
||||
} else {
|
||||
// Breaking head OR creative mode -> skip drops
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
||||
};
|
||||
|
||||
let is_creative = args.player.gamemode.load() == GameMode::Creative;
|
||||
let flags = if bed_props.part == BedPart::Foot && !is_creative {
|
||||
// Breaking foot in survival -> allow head to drop
|
||||
BlockFlags::NOTIFY_NEIGHBORS
|
||||
} else {
|
||||
// Breaking head OR creative mode -> skip drops
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
||||
};
|
||||
|
||||
args.world
|
||||
.break_block(&other_half_pos, Some(args.player.clone()), flags)
|
||||
.await;
|
||||
})
|
||||
args.world
|
||||
.break_block(&other_half_pos, Some(args.player.clone()), flags);
|
||||
}
|
||||
|
||||
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if args.moved {
|
||||
return;
|
||||
}
|
||||
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||
if args.moved {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the block is being replaced with air (i.e., broken), the `broken` callback
|
||||
// will handle breaking the other half with the correct drop flags. Only handle it here
|
||||
// if the block is being replaced with something else (e.g., piston movement).
|
||||
let new_state_id = args.world.get_block_state_id(args.position);
|
||||
let new_block = Block::from_state_id(new_state_id);
|
||||
if new_block == &Block::AIR {
|
||||
return;
|
||||
}
|
||||
// If the block is being replaced with air (i.e., broken), the `broken` callback
|
||||
// will handle breaking the other half with the correct drop flags. Only handle it here
|
||||
// if the block is being replaced with something else (e.g., piston movement).
|
||||
let new_state_id = args.world.get_block_state_id(args.position);
|
||||
let new_block = Block::from_state_id(new_state_id);
|
||||
if new_block == &Block::AIR {
|
||||
return;
|
||||
}
|
||||
|
||||
let bed_props = BedProperties::from_state_id(args.old_state_id, args.block);
|
||||
let other_half_pos = if bed_props.part == BedPart::Head {
|
||||
args.position
|
||||
.offset(bed_props.facing.opposite().to_offset())
|
||||
} else {
|
||||
args.position.offset(bed_props.facing.to_offset())
|
||||
};
|
||||
let bed_props = BedProperties::from_state_id(args.old_state_id, args.block);
|
||||
let other_half_pos = if bed_props.part == BedPart::Head {
|
||||
args.position
|
||||
.offset(bed_props.facing.opposite().to_offset())
|
||||
} else {
|
||||
args.position.offset(bed_props.facing.to_offset())
|
||||
};
|
||||
|
||||
let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos);
|
||||
if other_block == args.block {
|
||||
let other_props = BedProperties::from_state_id(other_state.id, other_block);
|
||||
if other_props.part != bed_props.part {
|
||||
args.world
|
||||
.break_block(
|
||||
&other_half_pos,
|
||||
None,
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let (other_block, other_state) = args.world.get_block_and_state(&other_half_pos);
|
||||
if other_block == args.block {
|
||||
let other_props = BedProperties::from_state_id(other_state.id, other_block);
|
||||
if other_props.part != bed_props.part {
|
||||
args.world.break_block(
|
||||
&other_half_pos,
|
||||
None,
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let bed_props = BedProperties::from_state_id(state_id, args.block);
|
||||
|
||||
let (bed_head_pos, bed_foot_pos) = if bed_props.part == BedPart::Head {
|
||||
(
|
||||
*args.position,
|
||||
args.position
|
||||
.offset(bed_props.facing.opposite().to_offset()),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
args.position.offset(bed_props.facing.to_offset()),
|
||||
*args.position,
|
||||
)
|
||||
};
|
||||
|
||||
// Explode if not in the overworld
|
||||
if args.world.dimension != Dimension::OVERWORLD {
|
||||
args.world
|
||||
.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
args.world
|
||||
.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
|
||||
args.world
|
||||
.explode(
|
||||
bed_head_pos.to_centered_f64(),
|
||||
5.0,
|
||||
crate::world::ExplosionInteraction::Block,
|
||||
)
|
||||
.await;
|
||||
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure the bed is not obstructed
|
||||
if args.world.get_block_state(&bed_head_pos.up()).is_solid()
|
||||
|| args.world.get_block_state(&bed_foot_pos.up()).is_solid()
|
||||
{
|
||||
args.player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_OBSTRUCTED,
|
||||
translation::bedrock::TILE_BED_OBSTRUCTED
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure the bed is not occupied
|
||||
if bed_props.occupied {
|
||||
// TODO: Wake up villager
|
||||
|
||||
args.player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_OCCUPIED,
|
||||
translation::bedrock::TILE_BED_OCCUPIED
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure player is close enough
|
||||
if !args
|
||||
.player
|
||||
.position()
|
||||
.is_within_bounds(bed_head_pos.to_f64(), 3.0, 3.0, 3.0)
|
||||
&& !args
|
||||
.player
|
||||
.position()
|
||||
.is_within_bounds(bed_foot_pos.to_f64(), 3.0, 3.0, 3.0)
|
||||
{
|
||||
args.player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_TOO_FAR_AWAY,
|
||||
translation::bedrock::TILE_BED_TOOFAR
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Set respawn point
|
||||
if args
|
||||
.player
|
||||
.set_respawn_point(
|
||||
args.world.dimension.clone(),
|
||||
bed_head_pos,
|
||||
args.player.get_entity().yaw.load(),
|
||||
args.player.get_entity().pitch.load(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
args.player
|
||||
.send_system_message(&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
|
||||
translation::bedrock::TILE_BED_RESPAWNSET
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Make sure the time and weather allows sleep
|
||||
if !can_sleep(args.world).await {
|
||||
args.player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
|
||||
translation::bedrock::TILE_BED_NOSLEEP
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure there are no monsters nearby
|
||||
for entity in args.world.entities.load().iter() {
|
||||
if !entity_prevents_sleep(entity.get_entity()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pos = entity.get_entity().pos.load();
|
||||
if pos.is_within_bounds(bed_head_pos.to_f64(), 8.0, 5.0, 8.0)
|
||||
|| pos.is_within_bounds(bed_foot_pos.to_f64(), 8.0, 5.0, 8.0)
|
||||
{
|
||||
args.player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_NOT_SAFE,
|
||||
translation::bedrock::TILE_BED_NOTSAFE
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
let mut event =
|
||||
crate::plugin::api::events::player::player_bed::PlayerBedEnterEvent::new(
|
||||
args.player.clone(),
|
||||
bed_head_pos,
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
}
|
||||
|
||||
args.player.sleep(bed_head_pos);
|
||||
args.player
|
||||
.trigger_advancement(
|
||||
crate::entity::player::advancement::trigger::AdvancementTrigger::SleptInBed,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::SleepInBed as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
Self::set_occupied(true, args.world, args.block, args.position, state_id).await;
|
||||
|
||||
BlockActionResult::SuccessServer
|
||||
})
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let player = Arc::clone(args.player);
|
||||
let world = Arc::clone(args.world);
|
||||
let block_id = args.block.id;
|
||||
let position = *args.position;
|
||||
tokio::spawn(async move {
|
||||
let block = Block::from_id(block_id);
|
||||
Self::use_bed(&world, &player, block, &position).await;
|
||||
});
|
||||
BlockActionResult::SuccessServer
|
||||
}
|
||||
}
|
||||
|
||||
impl BedBlock {
|
||||
pub async fn set_occupied(
|
||||
#[expect(clippy::too_many_lines)]
|
||||
async fn use_bed(
|
||||
world: &Arc<World>,
|
||||
player: &Arc<Player>,
|
||||
block: &Block,
|
||||
position: &BlockPos,
|
||||
) -> BlockActionResult {
|
||||
let state_id = world.get_block_state_id(position);
|
||||
let bed_props = BedProperties::from_state_id(state_id, block);
|
||||
|
||||
let (bed_head_pos, bed_foot_pos) = if bed_props.part == BedPart::Head {
|
||||
(
|
||||
*position,
|
||||
position.offset(bed_props.facing.opposite().to_offset()),
|
||||
)
|
||||
} else {
|
||||
(position.offset(bed_props.facing.to_offset()), *position)
|
||||
};
|
||||
|
||||
// Explode if not in the overworld
|
||||
if world.dimension != Dimension::OVERWORLD {
|
||||
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
|
||||
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
|
||||
|
||||
world
|
||||
.explode(
|
||||
bed_head_pos.to_centered_f64(),
|
||||
5.0,
|
||||
crate::world::ExplosionInteraction::Block,
|
||||
)
|
||||
.await;
|
||||
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure the bed is not obstructed
|
||||
if world.get_block_state(&bed_head_pos.up()).is_solid()
|
||||
|| world.get_block_state(&bed_foot_pos.up()).is_solid()
|
||||
{
|
||||
player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_OBSTRUCTED,
|
||||
translation::bedrock::TILE_BED_OBSTRUCTED
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure the bed is not occupied
|
||||
if bed_props.occupied {
|
||||
// TODO: Wake up villager
|
||||
|
||||
player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_OCCUPIED,
|
||||
translation::bedrock::TILE_BED_OCCUPIED
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure player is close enough
|
||||
if !player
|
||||
.position()
|
||||
.is_within_bounds(bed_head_pos.to_f64(), 3.0, 3.0, 3.0)
|
||||
&& !player
|
||||
.position()
|
||||
.is_within_bounds(bed_foot_pos.to_f64(), 3.0, 3.0, 3.0)
|
||||
{
|
||||
player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_TOO_FAR_AWAY,
|
||||
translation::bedrock::TILE_BED_TOOFAR
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Set respawn point
|
||||
if player
|
||||
.set_respawn_point(
|
||||
world.dimension.clone(),
|
||||
bed_head_pos,
|
||||
player.get_entity().yaw.load(),
|
||||
player.get_entity().pitch.load(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
player
|
||||
.send_system_message(&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_SET_SPAWN,
|
||||
translation::bedrock::TILE_BED_RESPAWNSET
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Make sure the time and weather allows sleep
|
||||
if !can_sleep(world) {
|
||||
player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_NO_SLEEP,
|
||||
translation::bedrock::TILE_BED_NOSLEEP
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
|
||||
// Make sure there are no monsters nearby
|
||||
for entity in world.entities.load().iter() {
|
||||
if !entity_prevents_sleep(entity.get_entity()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pos = entity.get_entity().pos.load();
|
||||
if pos.is_within_bounds(bed_head_pos.to_f64(), 8.0, 5.0, 8.0)
|
||||
|| pos.is_within_bounds(bed_foot_pos.to_f64(), 8.0, 5.0, 8.0)
|
||||
{
|
||||
player
|
||||
.send_system_message_raw(
|
||||
&pumpkin_macros::translate_cross!(
|
||||
translation::java::BLOCK_MINECRAFT_BED_NOT_SAFE,
|
||||
translation::bedrock::TILE_BED_NOTSAFE
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event =
|
||||
crate::plugin::api::events::player::player_bed::PlayerBedEnterEvent::new(
|
||||
player.clone(),
|
||||
bed_head_pos,
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return BlockActionResult::SuccessServer;
|
||||
}
|
||||
}
|
||||
|
||||
player.sleep(bed_head_pos);
|
||||
player.trigger_advancement(
|
||||
crate::entity::player::advancement::trigger::AdvancementTrigger::SleptInBed,
|
||||
);
|
||||
player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::SleepInBed as i32,
|
||||
1,
|
||||
);
|
||||
Self::set_occupied(true, world, block, position, state_id);
|
||||
|
||||
BlockActionResult::SuccessServer
|
||||
}
|
||||
}
|
||||
|
||||
impl BedBlock {
|
||||
pub fn set_occupied(
|
||||
occupied: bool,
|
||||
world: &Arc<World>,
|
||||
block: &Block,
|
||||
@@ -407,13 +389,11 @@ impl BedBlock {
|
||||
) {
|
||||
let mut bed_props = BedProperties::from_state_id(state_id, block);
|
||||
bed_props.occupied = occupied;
|
||||
world
|
||||
.set_block_state(
|
||||
block_pos,
|
||||
bed_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
block_pos,
|
||||
bed_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
|
||||
let other_half_pos = if bed_props.part == BedPart::Head {
|
||||
block_pos.offset(bed_props.facing.opposite().to_offset())
|
||||
@@ -425,19 +405,23 @@ impl BedBlock {
|
||||
} else {
|
||||
BedPart::Head
|
||||
};
|
||||
world
|
||||
.set_block_state(
|
||||
&other_half_pos,
|
||||
bed_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&other_half_pos,
|
||||
bed_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn can_sleep(world: &Arc<World>) -> bool {
|
||||
let time = world.level_time.lock().await;
|
||||
let weather = world.weather.lock().await;
|
||||
fn can_sleep(world: &Arc<World>) -> bool {
|
||||
let time = world
|
||||
.level_time
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let weather = world
|
||||
.weather
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
if weather.thundering {
|
||||
true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs};
|
||||
use crate::block::{BlockBehaviour, BlockMetadata, GetComparatorOutputArgs};
|
||||
use pumpkin_data::BlockId;
|
||||
use pumpkin_data::block_properties::{BeeNestLikeProperties, BlockProperties};
|
||||
|
||||
@@ -11,14 +11,11 @@ impl BlockMetadata for BeehiveBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for BeehiveBlock {
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = BeeNestLikeProperties::from_state_id(state_id, args.block);
|
||||
Some(props.honey_level)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
|
||||
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
|
||||
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
|
||||
PlacedArgs, registry::BlockActionResult,
|
||||
},
|
||||
entity::experience_orb::ExperienceOrbEntity,
|
||||
};
|
||||
@@ -83,83 +83,74 @@ impl ScreenHandlerFactory for BlastingFurnaceScreenFactory {
|
||||
pub struct BlastFurnaceBlock;
|
||||
|
||||
impl BlockBehaviour for BlastFurnaceBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
|
||||
inventory,
|
||||
property_delegate,
|
||||
experience_container,
|
||||
);
|
||||
args.player
|
||||
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
|
||||
1,
|
||||
);
|
||||
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
|
||||
inventory,
|
||||
property_delegate,
|
||||
experience_container,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&blasting_furnace_screen_factory, Some(pos))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
crate::block::registry::BlockActionResult::Consume
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = FurnaceLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let blasting_furnace_block_entity = BlastingFurnaceBlockEntity::new(*args.position);
|
||||
args.world
|
||||
.add_block_entity(Arc::new(blasting_furnace_block_entity));
|
||||
}
|
||||
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
// Extract and drop accumulated XP as orbs before removing the block entity
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
let xp = experience_container.extract_experience();
|
||||
if xp > 0 {
|
||||
let pos = args.position.to_f64();
|
||||
ExperienceOrbEntity::spawn(args.world, pos, xp as u32);
|
||||
}
|
||||
crate::block::registry::BlockActionResult::Consume
|
||||
})
|
||||
}
|
||||
args.world.remove_block_entity(args.position);
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = FurnaceLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let blasting_furnace_block_entity = BlastingFurnaceBlockEntity::new(*args.position);
|
||||
args.world
|
||||
.add_block_entity(Arc::new(blasting_furnace_block_entity));
|
||||
})
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Extract and drop accumulated XP as orbs before removing the block entity
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
let xp = experience_container.extract_experience();
|
||||
if xp > 0 {
|
||||
let pos = args.position.to_f64();
|
||||
ExperienceOrbEntity::spawn(args.world, pos, xp as u32).await;
|
||||
}
|
||||
}
|
||||
args.world.remove_block_entity(args.position);
|
||||
})
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
inventory.as_ref(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::{BlockFuture, GetComparatorOutputArgs, PlacedArgs};
|
||||
use crate::block::{GetComparatorOutputArgs, PlacedArgs};
|
||||
use crate::block::{
|
||||
registry::BlockActionResult,
|
||||
{BlockBehaviour, NormalUseArgs},
|
||||
@@ -49,55 +49,48 @@ impl ScreenHandlerFactory for BrewingScreenFactory {
|
||||
pub struct BrewingStandBlock;
|
||||
|
||||
impl BlockBehaviour for BrewingStandBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(pd) = block_entity.clone().to_property_delegate()
|
||||
{
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
|
||||
1,
|
||||
)
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(pd) = block_entity.clone().to_property_delegate()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(pos))
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(*args.position))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let be = BrewingStandBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(be));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let be = BrewingStandBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(be));
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
let mut bottles = 0u8;
|
||||
// Bottle slots are 0, 1, 2 in brewing stands
|
||||
for slot in 0..3 {
|
||||
let stack = inventory.get_stack(slot).await;
|
||||
if !stack.is_empty() {
|
||||
bottles += 1;
|
||||
}
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
let mut bottles = 0u8;
|
||||
// Bottle slots are 0, 1, 2 in brewing stands
|
||||
for slot in 0..3 {
|
||||
let stack = futures::executor::block_on(inventory.get_stack(slot));
|
||||
if !stack.is_empty() {
|
||||
bottles += 1;
|
||||
}
|
||||
Some(bottles)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
Some(bottles)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ use pumpkin_data::{Block, BlockId, BlockStateId};
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::entities::brushable_block::BrushableBlockBlockEntity;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, BlockMetadata, BrokenArgs, OnPlaceArgs, PlacedArgs};
|
||||
|
||||
pub struct BrushableBlock;
|
||||
|
||||
@@ -38,7 +36,7 @@ impl BrushableBlock {
|
||||
if *hits >= 4 {
|
||||
let item = brush_be.item.lock().await.take();
|
||||
if let Some(item_stack) = item {
|
||||
world.drop_stack(pos, item_stack).await;
|
||||
world.drop_stack(pos, item_stack);
|
||||
}
|
||||
|
||||
let target_block = if is_gravel {
|
||||
@@ -47,9 +45,7 @@ impl BrushableBlock {
|
||||
&Block::SAND
|
||||
};
|
||||
|
||||
world
|
||||
.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(pos, target_block.default_state.id, BlockFlags::NOTIFY_ALL);
|
||||
|
||||
let sound = if is_gravel {
|
||||
Sound::ItemBrushBrushingGravelComplete
|
||||
@@ -60,9 +56,7 @@ impl BrushableBlock {
|
||||
world.play_sound(sound, SoundCategory::Blocks, &pos.to_f64());
|
||||
} else {
|
||||
props.dusted = (*hits as u8).min(3);
|
||||
world
|
||||
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
|
||||
let sound = if is_gravel {
|
||||
Sound::ItemBrushBrushingGravel
|
||||
@@ -77,28 +71,22 @@ impl BrushableBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for BrushableBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = SuspiciousSandLikeProperties::default(args.block);
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let props = SuspiciousSandLikeProperties::default(args.block);
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = BrushableBlockBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let entity = BrushableBlockBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(be) = args.world.get_block_entity(args.position)
|
||||
&& let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>()
|
||||
&& let Some(contained) = brush_be.item.lock().await.take()
|
||||
{
|
||||
args.world.drop_stack(args.position, contained).await;
|
||||
}
|
||||
})
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
if let Some(be) = args.world.get_block_entity(args.position)
|
||||
&& let Some(brush_be) = be.as_any().downcast_ref::<BrushableBlockBlockEntity>()
|
||||
&& let Some(contained) = brush_be.item.blocking_lock().take()
|
||||
{
|
||||
args.world.drop_stack(args.position, contained);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use pumpkin_world::tick::TickPriority;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs,
|
||||
BlockBehaviour, BlockMetadata, OnEntityCollisionArgs, OnNeighborUpdateArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
use crate::world::World;
|
||||
@@ -161,8 +161,8 @@ fn bubble_column_velocity(
|
||||
}
|
||||
|
||||
impl BlockBehaviour for BubbleColumnBlock {
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
{
|
||||
if args.block != &Block::BUBBLE_COLUMN {
|
||||
return;
|
||||
}
|
||||
@@ -179,19 +179,19 @@ impl BlockBehaviour for BubbleColumnBlock {
|
||||
if let Some(player) = args.entity.get_player() {
|
||||
player.breath_manager.reset(player);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
if args.block == &Block::WATER && is_source_water(args.world, *args.position) {
|
||||
schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
let state = args.world.get_block_state_id(args.position);
|
||||
if args.block == &Block::BUBBLE_COLUMN {
|
||||
schedule_reconcile(args.world, *args.position, REMOVE_DELAY_TICKS);
|
||||
@@ -202,42 +202,37 @@ impl BlockBehaviour for BubbleColumnBlock {
|
||||
{
|
||||
schedule_reconcile(args.world, *args.position, CREATE_DELAY_TICKS);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state = args.world.get_block_state_id(args.position);
|
||||
let block = Block::from_state_id(state);
|
||||
if block != &Block::BUBBLE_COLUMN && block != &Block::WATER {
|
||||
return;
|
||||
}
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let state = args.world.get_block_state_id(args.position);
|
||||
let block = Block::from_state_id(state);
|
||||
if block != &Block::BUBBLE_COLUMN && block != &Block::WATER {
|
||||
return;
|
||||
}
|
||||
|
||||
let below_pos = args.position.down();
|
||||
let below_state = args.world.get_block_state_id(&below_pos);
|
||||
let below_block = Block::from_state_id(below_state);
|
||||
let below_pos = args.position.down();
|
||||
let below_state = args.world.get_block_state_id(&below_pos);
|
||||
let below_block = Block::from_state_id(below_state);
|
||||
|
||||
match reconcile_action(block, state, below_block, below_state) {
|
||||
ReconcileAction::SetBubble(kind) => {
|
||||
let new_state = bubble_column_state(kind);
|
||||
args.world
|
||||
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
schedule_reconcile(args.world, args.position.up(), CREATE_DELAY_TICKS);
|
||||
}
|
||||
ReconcileAction::RestoreWater => {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
source_water_state(),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
schedule_reconcile(args.world, args.position.up(), REMOVE_DELAY_TICKS);
|
||||
}
|
||||
ReconcileAction::Stop => {}
|
||||
match reconcile_action(block, state, below_block, below_state) {
|
||||
ReconcileAction::SetBubble(kind) => {
|
||||
let new_state = bubble_column_state(kind);
|
||||
args.world
|
||||
.set_block_state(args.position, new_state, BlockFlags::NOTIFY_ALL);
|
||||
schedule_reconcile(args.world, args.position.up(), CREATE_DELAY_TICKS);
|
||||
}
|
||||
})
|
||||
ReconcileAction::RestoreWater => {
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
source_water_state(),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
schedule_reconcile(args.world, args.position.up(), REMOVE_DELAY_TICKS);
|
||||
}
|
||||
ReconcileAction::Stop => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetComparatorOutputArgs,
|
||||
GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
UseWithItemArgs, blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetComparatorOutputArgs, GetStateForNeighborUpdateArgs,
|
||||
NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, UseWithItemArgs,
|
||||
blocks::candle_cakes::cake_from_candle, registry::BlockActionResult,
|
||||
},
|
||||
entity::player::Player,
|
||||
world::World,
|
||||
@@ -21,13 +21,11 @@ use pumpkin_world::{
|
||||
tick::TickPriority,
|
||||
world::{BlockAccessor, BlockFlags},
|
||||
};
|
||||
use rand::{RngExt, rng};
|
||||
|
||||
#[pumpkin_block("minecraft:cake")]
|
||||
pub struct CakeBlock;
|
||||
|
||||
impl CakeBlock {
|
||||
pub async fn consume_if_hungry(
|
||||
pub fn consume_if_hungry(
|
||||
world: &Arc<World>,
|
||||
player: &Player,
|
||||
block: &Block,
|
||||
@@ -45,7 +43,7 @@ impl CakeBlock {
|
||||
.hunger_manager
|
||||
.saturation
|
||||
.store(player.hunger_manager.saturation.load() + 0.4);
|
||||
player.send_health().await;
|
||||
player.send_health();
|
||||
}
|
||||
GameMode::Creative | GameMode::Spectator => {}
|
||||
}
|
||||
@@ -53,38 +51,30 @@ impl CakeBlock {
|
||||
let mut properties = CakeLikeProperties::from_state_id(state_id, block);
|
||||
match properties.bites {
|
||||
0..=5 => {
|
||||
player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
|
||||
1,
|
||||
);
|
||||
properties.bites += 1;
|
||||
world
|
||||
.set_block_state(
|
||||
location,
|
||||
properties.to_state_id(block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
location,
|
||||
properties.to_state_id(block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
6 => {
|
||||
player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.set_block_state(
|
||||
location,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::EatCakeSlice as i32,
|
||||
1,
|
||||
);
|
||||
world.set_block_state(
|
||||
location,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
_ => BlockActionResult::Pass,
|
||||
@@ -93,113 +83,83 @@ impl CakeBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CakeBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
Block::CAKE.default_state.id
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
Block::CAKE.default_state.id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
|
||||
let item = args.item_stack.item;
|
||||
match item.id {
|
||||
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
|
||||
if properties.bites != 0 {
|
||||
return Self::consume_if_hungry(
|
||||
args.world,
|
||||
args.player,
|
||||
args.block,
|
||||
args.position,
|
||||
state_id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if args.player.gamemode.load() != GameMode::Creative {
|
||||
args.item_stack.decrement(1);
|
||||
}
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
cake_from_candle(item).default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
let seed: f64 = rng().random();
|
||||
args.player
|
||||
.play_sound(
|
||||
Sound::BlockCakeAddCandle as u16,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
1.0,
|
||||
1.0,
|
||||
seed,
|
||||
)
|
||||
.await;
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
_ => {
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
|
||||
let item = args.item_stack.item;
|
||||
match item.id {
|
||||
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
|
||||
if properties.bites != 0 {
|
||||
return Self::consume_if_hungry(
|
||||
args.world,
|
||||
args.player,
|
||||
args.block,
|
||||
args.position,
|
||||
state_id,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
|
||||
if args.player.gamemode.load() != GameMode::Creative {
|
||||
args.item_stack.decrement(1);
|
||||
}
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
cake_from_candle(item).default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
args.world.play_sound(
|
||||
Sound::BlockCakeAddCandle,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
})
|
||||
_ => Self::consume_if_hungry(
|
||||
args.world,
|
||||
args.player,
|
||||
args.block,
|
||||
args.position,
|
||||
state_id,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
|
||||
.await
|
||||
})
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
Self::consume_if_hungry(args.world, args.player, args.block, args.position, state_id)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
|
||||
if properties.bites <= 6 {
|
||||
@@ -207,7 +167,7 @@ impl BlockBehaviour for CakeBlock {
|
||||
} else {
|
||||
Some(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ use pumpkin_world::tick::TickPriority;
|
||||
use crate::block::entities::campfire::CampfireBlockEntity;
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockIsReplacing, GetStateForNeighborUpdateArgs,
|
||||
OnEntityCollisionArgs, OnPlaceArgs, PlacedArgs,
|
||||
BlockBehaviour, BlockIsReplacing, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
|
||||
OnPlaceArgs, PlacedArgs,
|
||||
},
|
||||
entity::EntityBase,
|
||||
};
|
||||
@@ -23,21 +23,24 @@ use std::sync::Arc;
|
||||
pub struct CampfireBlock;
|
||||
|
||||
impl BlockBehaviour for CampfireBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let entity = CampfireBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cooking food on campfire (CampfireBlockEntity)
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
{
|
||||
if CampfireLikeProperties::from_state_id(args.state.id, args.block).lit
|
||||
&& let Some(living_entity) = args.entity.get_living_entity()
|
||||
{
|
||||
let has_frost_walker_enchantment = {
|
||||
let equipment = living_entity.entity_equipment.lock().await;
|
||||
let equipment = living_entity
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::FEET)
|
||||
@@ -47,7 +50,6 @@ impl BlockBehaviour for CampfireBlock {
|
||||
};
|
||||
let has_fire_res = living_entity
|
||||
.get_effect(&StatusEffect::FIRE_RESISTANCE)
|
||||
.await
|
||||
.is_some();
|
||||
if has_frost_walker_enchantment || has_fire_res {
|
||||
//campfire burning doesn't work if entity's boots has frost walker enchantment or entity has fire resistance. source: https://minecraft.wiki/w/Campfire#Damage
|
||||
@@ -59,49 +61,43 @@ impl BlockBehaviour for CampfireBlock {
|
||||
1.0
|
||||
};
|
||||
args.entity
|
||||
.damage(args.entity, damage_amount, DamageType::CAMPFIRE)
|
||||
.await;
|
||||
.damage(args.entity, damage_amount, DamageType::CAMPFIRE);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_));
|
||||
let mut props =
|
||||
CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.waterlogged = is_replacing_water;
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let is_replacing_water = matches!(args.replacing, BlockIsReplacing::Water(_));
|
||||
let mut props =
|
||||
CampfireLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.waterlogged = is_replacing_water;
|
||||
props.signal_fire = is_signal_fire_base_block(args.world.get_block(&args.position.down()));
|
||||
props.lit = !is_replacing_water;
|
||||
props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let mut props = CampfireLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
props.lit = false;
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
|
||||
if args.direction == BlockDirection::Down {
|
||||
props.signal_fire =
|
||||
is_signal_fire_base_block(args.world.get_block(&args.position.down()));
|
||||
props.lit = !is_replacing_water;
|
||||
props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
}
|
||||
is_signal_fire_base_block(args.world.get_block(args.neighbor_position));
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = CampfireLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
props.lit = false;
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
|
||||
if args.direction == BlockDirection::Down {
|
||||
props.signal_fire =
|
||||
is_signal_fire_base_block(args.world.get_block(args.neighbor_position));
|
||||
}
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
// TODO: onProjectileHit
|
||||
|
||||
@@ -10,8 +10,8 @@ use pumpkin_world::{
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs,
|
||||
OnScheduledTickArgs, UseWithItemArgs, blocks::cake::CakeBlock, registry::BlockActionResult,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnScheduledTickArgs,
|
||||
UseWithItemArgs, blocks::cake::CakeBlock, registry::BlockActionResult,
|
||||
},
|
||||
entity::player::Player,
|
||||
world::World,
|
||||
@@ -55,7 +55,7 @@ pub fn candle_from_cake(block: &Block) -> &'static Item {
|
||||
pub struct CandleCakeBlock;
|
||||
|
||||
impl CandleCakeBlock {
|
||||
async fn consume_and_drop_candle(
|
||||
fn consume_and_drop_candle(
|
||||
block: &Block,
|
||||
player: &Player,
|
||||
location: &BlockPos,
|
||||
@@ -75,65 +75,51 @@ impl CandleCakeBlock {
|
||||
|
||||
let item_stack = ItemStack::new(1, candle_item);
|
||||
|
||||
world.drop_stack(location, item_stack).await;
|
||||
world.drop_stack(location, item_stack);
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
location,
|
||||
Block::CAKE.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
location,
|
||||
Block::CAKE.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
|
||||
let (block, state) = world.get_block_and_state_id(location);
|
||||
|
||||
CakeBlock::consume_if_hungry(world, player, block, location, state).await
|
||||
CakeBlock::consume_if_hungry(world, player, block, location, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CandleCakeBlock {
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item_id = args.item_stack.item.id;
|
||||
match item_id {
|
||||
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
|
||||
BlockActionResult::Pass
|
||||
} // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL
|
||||
_ => BlockActionResult::PassToDefaultBlockAction,
|
||||
}
|
||||
})
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let item_id = args.item_stack.item.id;
|
||||
match item_id {
|
||||
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
|
||||
BlockActionResult::Pass
|
||||
} // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL
|
||||
_ => BlockActionResult::PassToDefaultBlockAction,
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
Self::consume_and_drop_candle(args.block, args.player, args.position, args.world).await
|
||||
})
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
Self::consume_and_drop_candle(args.block, args.player, args.position, args.world)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use pumpkin_world::tick::TickPriority;
|
||||
use pumpkin_world::world::BlockAccessor;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::{BlockFuture, GetStateForNeighborUpdateArgs, OnScheduledTickArgs};
|
||||
use crate::block::{GetStateForNeighborUpdateArgs, OnScheduledTickArgs};
|
||||
use crate::{
|
||||
block::{
|
||||
BlockIsReplacing,
|
||||
@@ -27,29 +27,24 @@ use crate::{
|
||||
pub struct CandleBlock;
|
||||
|
||||
impl BlockBehaviour for CandleBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.player.get_entity().pose.load() != EntityPose::Crouching
|
||||
&& let BlockIsReplacing::Itself(state_id) = args.replacing
|
||||
{
|
||||
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
|
||||
if properties.candles < 4 {
|
||||
properties.candles += 1;
|
||||
}
|
||||
return properties.to_state_id(args.block);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
if args.player.get_entity().pose.load() != EntityPose::Crouching
|
||||
&& let BlockIsReplacing::Itself(state_id) = args.replacing
|
||||
{
|
||||
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
|
||||
if properties.candles < 4 {
|
||||
properties.candles += 1;
|
||||
}
|
||||
return properties.to_state_id(args.block);
|
||||
}
|
||||
|
||||
let mut properties = CandleLikeProperties::default(args.block);
|
||||
properties.waterlogged = args.replacing.water_source();
|
||||
properties.to_state_id(args.block)
|
||||
})
|
||||
let mut properties = CandleLikeProperties::default(args.block);
|
||||
properties.waterlogged = args.replacing.water_source();
|
||||
properties.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let mut properties = CandleLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
@@ -67,13 +62,11 @@ impl BlockBehaviour for CandleBlock {
|
||||
|
||||
properties.lit = was_lit;
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
@@ -84,22 +77,20 @@ impl BlockBehaviour for CandleBlock {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
|
||||
BlockActionResult::Consume
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut properties = CandleLikeProperties::from_state_id(state_id, args.block);
|
||||
|
||||
@@ -107,16 +98,14 @@ impl BlockBehaviour for CandleBlock {
|
||||
properties.lit = false;
|
||||
}
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
properties.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
|
||||
BlockActionResult::Consume
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -130,27 +119,22 @@ impl BlockBehaviour for CandleBlock {
|
||||
&& args.block.id == b.id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
@@ -16,27 +16,22 @@ impl BlockBehaviour for CarpetBlock {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,27 +43,22 @@ impl BlockBehaviour for MossCarpetBlock {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,27 +70,22 @@ impl BlockBehaviour for PaleMossCarpetBlock {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs};
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_inventory::cartography_table_screen_handler::CartographyTableScreenHandler;
|
||||
@@ -16,21 +16,21 @@ use tokio::sync::Mutex;
|
||||
pub struct CartographyTableBlock;
|
||||
|
||||
impl BlockBehaviour for CartographyTableBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&CartographyTableScreenFactory, Some(*args.position))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&CartographyTableScreenFactory, Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_data::{
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::{
|
||||
block::{BlockBehaviour, BlockFuture, BlockMetadata, OnPlaceArgs, PlacedArgs},
|
||||
block::{BlockBehaviour, BlockMetadata, OnPlaceArgs, PlacedArgs},
|
||||
entity::{
|
||||
Entity,
|
||||
passive::{iron_golem::IronGolemEntity, snow_golem::SnowGolemEntity},
|
||||
@@ -23,35 +23,31 @@ impl BlockMetadata for CarvedPumpkinBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CarvedPumpkinBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
// Mojang uses some BlockPattern magic, way too complex tbh
|
||||
Box::pin(async {
|
||||
{
|
||||
let down_pos = args.position.down();
|
||||
let upper = args.world.get_block(&down_pos);
|
||||
let lower = args.world.get_block(&down_pos.down());
|
||||
if upper == &Block::SNOW_BLOCK && lower == &Block::SNOW_BLOCK {
|
||||
for i in 0..3 {
|
||||
let pos = args.position.down_height(i);
|
||||
args.world
|
||||
.set_block_state(
|
||||
&pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
args.world.sync_world_event(
|
||||
WorldEvent::ParticlesDestroyBlock,
|
||||
pos,
|
||||
@@ -64,7 +60,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
|
||||
&EntityType::SNOW_GOLEM,
|
||||
);
|
||||
let golem = SnowGolemEntity::new(entity);
|
||||
args.world.spawn_entity(golem).await;
|
||||
args.world.spawn_entity(golem);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,13 +76,11 @@ impl BlockBehaviour for CarvedPumpkinBlock {
|
||||
let pattern = [*args.position, down_pos, down_pos.down(), arm1, arm2];
|
||||
|
||||
for p in pattern {
|
||||
args.world
|
||||
.set_block_state(
|
||||
&p,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&p,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
args.world.sync_world_event(
|
||||
WorldEvent::ParticlesDestroyBlock,
|
||||
p,
|
||||
@@ -100,11 +94,11 @@ impl BlockBehaviour for CarvedPumpkinBlock {
|
||||
&EntityType::IRON_GOLEM,
|
||||
);
|
||||
let golem = IronGolemEntity::new(entity);
|
||||
args.world.spawn_entity(golem).await;
|
||||
args.world.spawn_entity(golem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, GetComparatorOutputArgs, UseWithItemArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, BlockMetadata, GetComparatorOutputArgs, UseWithItemArgs};
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockId;
|
||||
use pumpkin_data::block_properties::{BlockProperties, WaterCauldronLikeProperties};
|
||||
@@ -24,7 +24,7 @@ impl BlockMetadata for CauldronBlock {
|
||||
}
|
||||
}
|
||||
|
||||
async fn fire_cauldron_change(
|
||||
fn fire_cauldron_change(
|
||||
world: &std::sync::Arc<crate::world::World>,
|
||||
pos: pumpkin_util::math::position::BlockPos,
|
||||
old_level: i32,
|
||||
@@ -42,210 +42,208 @@ async fn fire_cauldron_change(
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
}
|
||||
!event.cancelled
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CauldronBlock {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item_id = args.item_stack.item.id;
|
||||
let block_id = args.block.id;
|
||||
let gamemode = args.player.gamemode.load();
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let item_id = args.item_stack.item.id;
|
||||
let block_id = args.block.id;
|
||||
let gamemode = args.player.gamemode.load();
|
||||
|
||||
// Filling empty cauldron with buckets
|
||||
if block_id == BlockId::CAULDRON {
|
||||
if item_id == Item::WATER_BUCKET.id {
|
||||
if !fire_cauldron_change(
|
||||
args.world,
|
||||
*args.position,
|
||||
0,
|
||||
3,
|
||||
crate::plugin::block::cauldron_level_change::CauldronChangeReason::BucketEmpty,
|
||||
Some(args.player.clone()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
// Filling empty cauldron with buckets
|
||||
if block_id == BlockId::CAULDRON {
|
||||
if item_id == Item::WATER_BUCKET.id {
|
||||
if !fire_cauldron_change(
|
||||
args.world,
|
||||
*args.position,
|
||||
0,
|
||||
3,
|
||||
crate::plugin::block::cauldron_level_change::CauldronChangeReason::BucketEmpty,
|
||||
Some(Arc::clone(args.player) as Arc<dyn crate::entity::EntityBase>),
|
||||
) {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::LAVA_BUCKET.id {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::LAVA_CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyLava,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::LAVA_BUCKET.id {
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::LAVA_CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyLava,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POWDER_SNOW_BUCKET.id {
|
||||
let state_id = Block::POWDER_SNOW_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::POWDER_SNOW_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyPowderSnow,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POWDER_SNOW_BUCKET.id {
|
||||
let state_id = Block::POWDER_SNOW_CAULDRON
|
||||
.from_properties(&[("level", "3")])
|
||||
.to_state_id(&Block::POWDER_SNOW_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
|
||||
args.world.play_sound(
|
||||
Sound::ItemBucketEmptyPowderSnow,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), args.player.as_ref())
|
||||
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POTION.id {
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "1")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
} else if item_id == Item::POTION.id {
|
||||
let state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", "1")])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_ALL);
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(
|
||||
ItemStack::new(1, &Item::GLASS_BOTTLE),
|
||||
args.player.as_ref(),
|
||||
player.as_ref(),
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// Collecting fluid from full cauldrons into empty bucket
|
||||
if item_id == Item::BUCKET.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let (filled_item, sound) = if block_id == BlockId::WATER_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 3 {
|
||||
(Some(&Item::WATER_BUCKET), Sound::ItemBucketFill)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFill)
|
||||
}
|
||||
} else if block_id == BlockId::LAVA_CAULDRON {
|
||||
(Some(&Item::LAVA_BUCKET), Sound::ItemBucketFillLava)
|
||||
} else if block_id == BlockId::POWDER_SNOW_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 3 {
|
||||
(
|
||||
Some(&Item::POWDER_SNOW_BUCKET),
|
||||
Sound::ItemBucketFillPowderSnow,
|
||||
)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFillPowderSnow)
|
||||
}
|
||||
// Collecting fluid from full cauldrons into empty bucket
|
||||
if item_id == Item::BUCKET.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let (filled_item, sound) = if block_id == BlockId::WATER_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 3 {
|
||||
(Some(&Item::WATER_BUCKET), Sound::ItemBucketFill)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFill)
|
||||
};
|
||||
|
||||
if let Some(result_item) = filled_item {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world
|
||||
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, result_item), args.player.as_ref())
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding water bottle to non-full water cauldron
|
||||
if block_id == BlockId::WATER_CAULDRON && item_id == Item::POTION.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
} else if block_id == BlockId::LAVA_CAULDRON {
|
||||
(Some(&Item::LAVA_BUCKET), Sound::ItemBucketFillLava)
|
||||
} else if block_id == BlockId::POWDER_SNOW_CAULDRON {
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level < 3 {
|
||||
let next_level_str = match props.level {
|
||||
1 => "2",
|
||||
_ => "3",
|
||||
};
|
||||
let new_state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", next_level_str)])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
if props.level == 3 {
|
||||
(
|
||||
Some(&Item::POWDER_SNOW_BUCKET),
|
||||
Sound::ItemBucketFillPowderSnow,
|
||||
)
|
||||
} else {
|
||||
(None, Sound::ItemBucketFillPowderSnow)
|
||||
}
|
||||
} else {
|
||||
(None, Sound::ItemBucketFill)
|
||||
};
|
||||
|
||||
if let Some(result_item) = filled_item {
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::CAULDRON.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
args.world
|
||||
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(ItemStack::new(1, result_item), player.as_ref())
|
||||
.await;
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
args.player
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding water bottle to non-full water cauldron
|
||||
if block_id == BlockId::WATER_CAULDRON && item_id == Item::POTION.id {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level < 3 {
|
||||
let next_level_str = match props.level {
|
||||
1 => "2",
|
||||
_ => "3",
|
||||
};
|
||||
let new_state_id = Block::WATER_CAULDRON
|
||||
.from_properties(&[("level", next_level_str)])
|
||||
.to_state_id(&Block::WATER_CAULDRON);
|
||||
args.world
|
||||
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL);
|
||||
args.world.play_sound(
|
||||
Sound::ItemBottleEmpty,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.item_stack.decrement_unless_creative(gamemode, 1);
|
||||
let player = Arc::clone(args.player);
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(
|
||||
ItemStack::new(1, &Item::GLASS_BOTTLE),
|
||||
args.player.as_ref(),
|
||||
player.as_ref(),
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
});
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
}
|
||||
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
})
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
match args.block.id {
|
||||
BlockId::WATER_CAULDRON | BlockId::POWDER_SNOW_CAULDRON => {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
Some(props.level)
|
||||
}
|
||||
BlockId::LAVA_CAULDRON => Some(3),
|
||||
_ => Some(0),
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
match args.block.id {
|
||||
BlockId::WATER_CAULDRON | BlockId::POWDER_SNOW_CAULDRON => {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = WaterCauldronLikeProperties::from_state_id(state_id, args.block);
|
||||
Some(props.level)
|
||||
}
|
||||
})
|
||||
BlockId::LAVA_CAULDRON => Some(3),
|
||||
_ => Some(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::{BlockBehaviour, OnPlaceArgs};
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -10,18 +9,16 @@ use pumpkin_macros::pumpkin_block;
|
||||
pub struct ChainBlock;
|
||||
|
||||
impl BlockBehaviour for ChainBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
pumpkin_data::block_properties::IronChainLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
props.r#axis = match args.direction {
|
||||
BlockDirection::East | BlockDirection::West => Axis::X,
|
||||
BlockDirection::Up | BlockDirection::Down => Axis::Y,
|
||||
BlockDirection::North | BlockDirection::South => Axis::Z,
|
||||
};
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
pumpkin_data::block_properties::IronChainLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
props.r#axis = match args.direction {
|
||||
BlockDirection::East | BlockDirection::West => Axis::X,
|
||||
BlockDirection::Up | BlockDirection::Down => Axis::Y,
|
||||
BlockDirection::North | BlockDirection::South => Axis::Z,
|
||||
};
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use pumpkin_world::world::BlockFlags;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::block::{
|
||||
BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
|
||||
BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs,
|
||||
NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs, PlayerPlacedArgs,
|
||||
RandomTickArgs,
|
||||
};
|
||||
@@ -93,8 +93,8 @@ fn on_place_chest_impl(args: &OnPlaceArgs<'_>) -> BlockStateId {
|
||||
chest_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
async fn placed_chest_impl<E: BlockEntity + 'static>(
|
||||
args: PlacedArgs<'_>,
|
||||
fn placed_chest_impl<E: BlockEntity + 'static>(
|
||||
args: &PlacedArgs<'_>,
|
||||
create_entity: impl FnOnce(BlockPos) -> E,
|
||||
) {
|
||||
let chest = create_entity(*args.position);
|
||||
@@ -117,13 +117,11 @@ async fn placed_chest_impl<E: BlockEntity + 'static>(
|
||||
) {
|
||||
neighbor_props.r#type = chest_props.r#type.opposite();
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
&args.position.offset(connected_towards.to_offset()),
|
||||
neighbor_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&args.position.offset(connected_towards.to_offset()),
|
||||
neighbor_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +138,7 @@ fn player_placed_chest_impl(args: &PlayerPlacedArgs<'_>) {
|
||||
);
|
||||
}
|
||||
|
||||
async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
fn get_chest_comparator_output(args: &GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
let state = args.world.get_block_state_id(args.position);
|
||||
let first_chest = args.world.get_block_entity(args.position);
|
||||
let first_inventory = first_chest.and_then(BlockEntity::get_inventory)?;
|
||||
@@ -163,20 +161,22 @@ async fn get_chest_comparator_output(args: GetComparatorOutputArgs<'_>) -> Optio
|
||||
} else {
|
||||
DoubleInventory::new(second_inventory, first_inventory)
|
||||
};
|
||||
Some(crate::block::calculate_comparator_output(double_inventory.as_ref()).await)
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
double_inventory.as_ref(),
|
||||
))
|
||||
} else {
|
||||
Some(crate::block::calculate_comparator_output(first_inventory.as_ref()).await)
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
first_inventory.as_ref(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenChest as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenChest as i32,
|
||||
1,
|
||||
);
|
||||
let state = args.world.get_block_state_id(args.position);
|
||||
let first_chest = args.world.get_block_entity(args.position);
|
||||
|
||||
@@ -196,9 +196,10 @@ async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
&& let Some(table) = get_chest_loot_table(&loot_key)
|
||||
&& let Some(inv) = entity.clone().get_inventory()
|
||||
{
|
||||
fill_chest_inventory(&inv, table, seed).await;
|
||||
// Mark the block entity dirty so the generated items persist.
|
||||
inv.mark_dirty();
|
||||
tokio::spawn(async move {
|
||||
fill_chest_inventory(&inv, table, seed).await;
|
||||
inv.mark_dirty();
|
||||
});
|
||||
}
|
||||
|
||||
let Some(first_inventory) = first_chest.and_then(BlockEntity::get_inventory) else {
|
||||
@@ -239,14 +240,18 @@ async fn normal_use_chest_impl(args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
first_inventory
|
||||
};
|
||||
|
||||
args.player
|
||||
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position))
|
||||
.await;
|
||||
let player = args.player.clone();
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&ChestScreenFactory(inventory), Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
async fn broken_chest_impl(args: BrokenArgs<'_>) {
|
||||
fn broken_chest_impl(args: &BrokenArgs<'_>) {
|
||||
let chest_props = ChestLikeProperties::from_state_id(args.state.id, args.block);
|
||||
let connected_towards = match chest_props.r#type {
|
||||
ChestType::Single => return,
|
||||
@@ -264,13 +269,11 @@ async fn broken_chest_impl(args: BrokenArgs<'_>) {
|
||||
) {
|
||||
neighbor_props.r#type = ChestType::Single;
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
&args.position.offset(connected_towards.to_offset()),
|
||||
neighbor_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&args.position.offset(connected_towards.to_offset()),
|
||||
neighbor_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,38 +281,32 @@ async fn broken_chest_impl(args: BrokenArgs<'_>) {
|
||||
pub struct ChestBlock;
|
||||
|
||||
impl BlockBehaviour for ChestBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move { on_place_chest_impl(&args) })
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
on_place_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
args.r#type == LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(placed_chest_impl(args, ChestBlockEntity::new))
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
placed_chest_impl(&args, ChestBlockEntity::new);
|
||||
}
|
||||
|
||||
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { player_placed_chest_impl(&args) })
|
||||
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
||||
player_placed_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(normal_use_chest_impl(args))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
normal_use_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(broken_chest_impl(args))
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
broken_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move { get_chest_comparator_output(args).await })
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
get_chest_comparator_output(&args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,65 +349,56 @@ impl
|
||||
impl crate::block::blocks::weathering_copper::WeatheringCopper for CopperChestBlock {}
|
||||
|
||||
impl BlockBehaviour for CopperChestBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move { on_place_chest_impl(&args) })
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
on_place_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
args.r#type == LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(placed_chest_impl(args, ChestBlockEntity::new))
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
placed_chest_impl(&args, ChestBlockEntity::new);
|
||||
}
|
||||
|
||||
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { player_placed_chest_impl(&args) })
|
||||
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
||||
player_placed_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(normal_use_chest_impl(args))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
normal_use_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(broken_chest_impl(args))
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
broken_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let current_state_id = args.world.get_block_state_id(args.position);
|
||||
let chest_props = ChestLikeProperties::from_state_id(current_state_id, args.block);
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let current_state_id = args.world.get_block_state_id(args.position);
|
||||
let chest_props = ChestLikeProperties::from_state_id(current_state_id, args.block);
|
||||
|
||||
// Only oxidize LEFT or SINGLE chests (not RIGHT) to prevent double oxidation
|
||||
if chest_props.r#type == ChestType::Right {
|
||||
return;
|
||||
}
|
||||
// Only oxidize LEFT or SINGLE chests (not RIGHT) to prevent double oxidation
|
||||
if chest_props.r#type == ChestType::Right {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only oxidize if no players are viewing the chest
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(chest_entity) = block_entity.as_any().downcast_ref::<ChestBlockEntity>()
|
||||
&& chest_entity.get_viewer_count() > 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Only oxidize if no players are viewing the chest
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(chest_entity) = block_entity.as_any().downcast_ref::<ChestBlockEntity>()
|
||||
&& chest_entity.get_viewer_count() > 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
crate::block::blocks::weathering_copper::change_over_time(
|
||||
args.world,
|
||||
args.position,
|
||||
args.block,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
crate::block::blocks::weathering_copper::change_over_time(
|
||||
args.world,
|
||||
args.position,
|
||||
args.block,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move { get_chest_comparator_output(args).await })
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
get_chest_comparator_output(&args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,84 +407,64 @@ impl BlockBehaviour for CopperChestBlock {
|
||||
pub struct TrappedChestBlock;
|
||||
|
||||
impl BlockBehaviour for TrappedChestBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move { on_place_chest_impl(&args) })
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
on_place_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { args.r#type == LID_ANIMATION_EVENT_TYPE })
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
args.r#type == LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
|
||||
Box::pin(placed_chest_impl(args, TrappedChestBlockEntity::new))
|
||||
placed_chest_impl(&args, TrappedChestBlockEntity::new);
|
||||
}
|
||||
|
||||
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { player_placed_chest_impl(&args) })
|
||||
fn player_placed(&self, args: PlayerPlacedArgs<'_>) {
|
||||
player_placed_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(normal_use_chest_impl(args))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
normal_use_chest_impl(&args)
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(broken_chest_impl(args))
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
broken_chest_impl(&args);
|
||||
}
|
||||
|
||||
fn emits_redstone_power<'a>(
|
||||
&'a self,
|
||||
_args: EmitsRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { true })
|
||||
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_weak_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: GetRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
|
||||
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||
use crate::block::entities::trapped_chest::TrappedChestBlockEntity;
|
||||
|
||||
// Get viewer count from this chest
|
||||
let viewer_count = if let Some(block_entity) =
|
||||
args.world.get_block_entity(args.position)
|
||||
&& let Some(trapped_chest) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<TrappedChestBlockEntity>()
|
||||
{
|
||||
trapped_chest.get_viewer_count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// Get viewer count from this chest
|
||||
let viewer_count = if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(trapped_chest) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<TrappedChestBlockEntity>()
|
||||
{
|
||||
trapped_chest.get_viewer_count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
viewer_count.min(15) as u8
|
||||
})
|
||||
viewer_count.min(15) as u8
|
||||
}
|
||||
|
||||
fn get_strong_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: GetRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
// Strong power emitted to the block beneath the trapped chest
|
||||
// The block below queries with direction Up (from below looking up at the chest)
|
||||
if args.direction == BlockDirection::Up {
|
||||
self.get_weak_redstone_power(args).await
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
fn get_strong_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||
// Strong power emitted to the block beneath the trapped chest
|
||||
// The block below queries with direction Up (from below looking up at the chest)
|
||||
if args.direction == BlockDirection::Up {
|
||||
self.get_weak_redstone_power(args)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move { get_chest_comparator_output(args).await })
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
get_chest_comparator_output(&args)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ use pumpkin_macros::pumpkin_block;
|
||||
use crate::block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity;
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockHitResult, GetComparatorOutputArgs, NormalUseArgs,
|
||||
OnPlaceArgs, PlacedArgs, UseWithItemArgs, registry::BlockActionResult,
|
||||
BlockBehaviour, BlockHitResult, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
|
||||
PlacedArgs, UseWithItemArgs, registry::BlockActionResult,
|
||||
},
|
||||
entity::{EntityBase, player::Player},
|
||||
world::World,
|
||||
@@ -22,121 +22,102 @@ use pumpkin_data::{
|
||||
};
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
|
||||
#[pumpkin_block("minecraft:chiseled_bookshelf")]
|
||||
pub struct ChiseledBookshelfBlock;
|
||||
|
||||
impl BlockBehaviour for ChiseledBookshelfBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut properties = ChiseledBookshelfLikeProperties::default(args.block);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut properties = ChiseledBookshelfLikeProperties::default(args.block);
|
||||
|
||||
// Face in the opposite direction the player is facing
|
||||
properties.facing = args.player.get_entity().get_horizontal_facing().opposite();
|
||||
// Face in the opposite direction the player is facing
|
||||
properties.facing = args.player.get_entity().get_horizontal_facing().opposite();
|
||||
|
||||
properties.to_state_id(args.block)
|
||||
})
|
||||
properties.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
|
||||
if Self::is_slot_used(properties, slot) {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<ChiseledBookshelfBlockEntity>()
|
||||
{
|
||||
Self::try_remove_book(
|
||||
args.world,
|
||||
args.player,
|
||||
args.position,
|
||||
block_entity,
|
||||
properties,
|
||||
slot,
|
||||
)
|
||||
.await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
} else {
|
||||
return BlockActionResult::Consume;
|
||||
}
|
||||
}
|
||||
BlockActionResult::Pass
|
||||
})
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
if !args
|
||||
.item_stack
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
|
||||
{
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
|
||||
if Self::is_slot_used(properties, slot) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
} else if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
|
||||
if Self::is_slot_used(properties, slot) {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<ChiseledBookshelfBlockEntity>()
|
||||
{
|
||||
Self::try_add_book(
|
||||
Self::try_remove_book(
|
||||
args.world,
|
||||
args.player,
|
||||
args.position,
|
||||
block_entity,
|
||||
properties,
|
||||
slot,
|
||||
args.item_stack,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
} else {
|
||||
return BlockActionResult::Consume;
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
})
|
||||
}
|
||||
BlockActionResult::Pass
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_entity = ChiseledBookshelfBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
})
|
||||
}
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
if !args
|
||||
.item_stack
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
|
||||
{
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) {
|
||||
if Self::is_slot_used(properties, slot) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
} else if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<ChiseledBookshelfBlockEntity>()
|
||||
{
|
||||
return Some((block_entity.last_interacted_slot.load(Ordering::Relaxed) + 1) as u8);
|
||||
Self::try_add_book(
|
||||
args.world,
|
||||
args.player,
|
||||
args.position,
|
||||
block_entity,
|
||||
properties,
|
||||
slot,
|
||||
args.item_stack,
|
||||
);
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
}
|
||||
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let block_entity = ChiseledBookshelfBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
}
|
||||
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<ChiseledBookshelfBlockEntity>()
|
||||
{
|
||||
return Some((block_entity.last_interacted_slot.load(Ordering::Relaxed) + 1) as u8);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ChiseledBookshelfBlock {
|
||||
async fn try_add_book(
|
||||
fn try_add_book(
|
||||
world: &Arc<World>,
|
||||
player: &Player,
|
||||
position: &BlockPos,
|
||||
@@ -153,28 +134,24 @@ impl ChiseledBookshelfBlock {
|
||||
Sound::BlockChiseledBookshelfPickup
|
||||
};
|
||||
|
||||
entity
|
||||
.set_stack(
|
||||
slot as usize,
|
||||
item.split_unless_creative(player.gamemode.load(), 1),
|
||||
)
|
||||
.await;
|
||||
entity
|
||||
.update_state(properties, world.clone(), slot as usize)
|
||||
.await;
|
||||
entity.set_book(
|
||||
slot as usize,
|
||||
item.split_unless_creative(player.gamemode.load(), 1),
|
||||
);
|
||||
entity.update_state(properties, world, slot as usize);
|
||||
|
||||
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
|
||||
}
|
||||
|
||||
async fn try_remove_book(
|
||||
fn try_remove_book(
|
||||
world: &Arc<World>,
|
||||
player: &Player,
|
||||
player: &Arc<Player>,
|
||||
position: &BlockPos,
|
||||
entity: &ChiseledBookshelfBlockEntity,
|
||||
properties: ChiseledBookshelfLikeProperties,
|
||||
slot: i8,
|
||||
) {
|
||||
let mut stack = entity.remove_stack_specific(slot as usize, 1).await;
|
||||
let mut stack = entity.remove_book(slot as usize, 1);
|
||||
|
||||
let sound = if stack.get_item() == &Item::ENCHANTED_BOOK {
|
||||
Sound::BlockChiseledBookshelfPickupEnchanted
|
||||
@@ -182,17 +159,11 @@ impl ChiseledBookshelfBlock {
|
||||
Sound::BlockChiseledBookshelfPickup
|
||||
};
|
||||
|
||||
if !player
|
||||
.get_inventory()
|
||||
.insert_stack_anywhere(&mut stack)
|
||||
.await
|
||||
{
|
||||
if !player.get_inventory().insert_stack_anywhere(&mut stack) {
|
||||
// Drop the item on the ground if the player cannot hold it because of a full inventory
|
||||
player.drop_item(stack).await;
|
||||
player.drop_item(stack);
|
||||
}
|
||||
entity
|
||||
.update_state(properties, world.clone(), slot as usize)
|
||||
.await;
|
||||
entity.update_state(properties, world, slot as usize);
|
||||
|
||||
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, OnEntityCollisionArgs};
|
||||
use crate::block::{BlockBehaviour, OnEntityCollisionArgs};
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
@@ -8,17 +8,15 @@ use pumpkin_util::math::vector3::Vector3;
|
||||
pub struct CobwebBlock;
|
||||
|
||||
impl BlockBehaviour for CobwebBlock {
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = args.entity.get_entity();
|
||||
let vec = if let Some(living) = entity.get_living_entity()
|
||||
&& living.has_effect(&StatusEffect::WEAVING).await
|
||||
{
|
||||
Vector3::new(0.5, 0.25, 0.5)
|
||||
} else {
|
||||
Vector3::new(0.25, 0.05, 0.25)
|
||||
};
|
||||
entity.slow_movement(args.state, vec).await;
|
||||
})
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
let entity = args.entity.get_entity();
|
||||
let vec = if let Some(living) = entity.get_living_entity()
|
||||
&& living.has_effect(&StatusEffect::WEAVING)
|
||||
{
|
||||
Vector3::new(0.5, 0.25, 0.5)
|
||||
} else {
|
||||
Vector3::new(0.25, 0.05, 0.25)
|
||||
};
|
||||
entity.slow_movement(args.state, vec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ use crate::command::CommandSender;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, NormalUseArgs,
|
||||
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
registry::BlockActionResult,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, NormalUseArgs, OnNeighborUpdateArgs,
|
||||
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs, registry::BlockActionResult,
|
||||
},
|
||||
server::Server,
|
||||
world::World,
|
||||
@@ -30,7 +29,7 @@ impl CommandBlock {
|
||||
dir: Facing,
|
||||
) -> Option<(BlockPos, CommandBlockLikeProperties)> {
|
||||
let target_pos = pos.offset(dir.to_block_direction().to_offset());
|
||||
let block = world.get_block(&target_pos);
|
||||
let (block, state_id) = world.get_block_and_state_id(&target_pos);
|
||||
|
||||
let allowed_blocks = [
|
||||
Block::COMMAND_BLOCK.name,
|
||||
@@ -41,7 +40,6 @@ impl CommandBlock {
|
||||
return None;
|
||||
}
|
||||
|
||||
let state_id = world.get_block_state_id(&target_pos);
|
||||
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
|
||||
|
||||
Some((target_pos, props))
|
||||
@@ -161,7 +159,7 @@ impl CommandBlock {
|
||||
if !command_blocks_work {
|
||||
return;
|
||||
}
|
||||
let block = world.get_block(&pos);
|
||||
let (block, state_id) = world.get_block_and_state_id(&pos);
|
||||
|
||||
if block.id != Block::CHAIN_COMMAND_BLOCK.id {
|
||||
break;
|
||||
@@ -178,13 +176,16 @@ impl CommandBlock {
|
||||
};
|
||||
let powered = command_entity.powered.load(Ordering::Relaxed);
|
||||
let auto = command_entity.auto.load(Ordering::Relaxed);
|
||||
let state_id = world.get_block_state_id(&pos);
|
||||
let props = CommandBlockLikeProperties::from_state_id(state_id, block);
|
||||
|
||||
if powered || auto {
|
||||
let conditions_met = Self::conditions_met(&world, &pos, direction);
|
||||
if conditions_met {
|
||||
let command = command_entity.command.lock().await;
|
||||
let command = command_entity
|
||||
.command
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
let Some(entity) = world.get_block_entity(&pos) else {
|
||||
warn!("Command block entity disappeared during execution");
|
||||
break;
|
||||
@@ -220,16 +221,14 @@ impl BlockMetadata for CommandBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CommandBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = CommandBlockLikeProperties::default(args.block);
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = CommandBlockLikeProperties::default(args.block);
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
if args.player.permission_lvl.load() < PermissionLvl::Two {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
@@ -238,11 +237,11 @@ impl BlockBehaviour for CommandBlock {
|
||||
};
|
||||
args.world.update_block_entity(&block_entity);
|
||||
BlockActionResult::SuccessServer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
let command_blocks_work =
|
||||
{ args.world.level_info.load().game_rules.command_blocks_work };
|
||||
if !command_blocks_work {
|
||||
@@ -264,64 +263,68 @@ impl BlockBehaviour for CommandBlock {
|
||||
args.block,
|
||||
command_entity,
|
||||
args.position,
|
||||
block_receives_redstone_power(args.world, args.position).await,
|
||||
block_receives_redstone_power(args.world, args.position),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let command_blocks_work =
|
||||
{ args.world.level_info.load().game_rules.command_blocks_work };
|
||||
if !command_blocks_work {
|
||||
return;
|
||||
}
|
||||
let Some(block_entity) = args.world.get_block_entity(args.position) else {
|
||||
return;
|
||||
};
|
||||
if block_entity.resource_location() != CommandBlockEntity::ID {
|
||||
return;
|
||||
}
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let command_blocks_work = { args.world.level_info.load().game_rules.command_blocks_work };
|
||||
if !command_blocks_work {
|
||||
return;
|
||||
}
|
||||
let Some(block_entity) = args.world.get_block_entity(args.position) else {
|
||||
return;
|
||||
};
|
||||
if block_entity.resource_location() != CommandBlockEntity::ID {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
|
||||
let Some(command_entity) = block_entity.as_any().downcast_ref::<CommandBlockEntity>()
|
||||
else {
|
||||
warn!("Block entity at {} is not a command block", args.position);
|
||||
return;
|
||||
};
|
||||
let Some(server) = args.world.server.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let props = CommandBlockLikeProperties::from_state_id(
|
||||
args.world.get_block_state_id(args.position),
|
||||
args.block,
|
||||
);
|
||||
|
||||
let world = args.world.clone();
|
||||
let entity_clone = block_entity.clone();
|
||||
let position = *args.position;
|
||||
let facing = props.facing;
|
||||
tokio::spawn(async move {
|
||||
let Some(command_entity) = entity_clone.as_any().downcast_ref::<CommandBlockEntity>()
|
||||
else {
|
||||
warn!("Block entity at {} is not a command block", args.position);
|
||||
return;
|
||||
};
|
||||
let Some(server) = args.world.server.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let props = CommandBlockLikeProperties::from_state_id(
|
||||
args.world.get_block_state_id(args.position),
|
||||
args.block,
|
||||
);
|
||||
|
||||
Self::execute(
|
||||
&server,
|
||||
args.world.clone(),
|
||||
block_entity.clone(),
|
||||
&command_entity.command.lock().await,
|
||||
)
|
||||
.await;
|
||||
|
||||
let command = command_entity
|
||||
.command
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
Self::execute(&server, world.clone(), entity_clone, &command).await;
|
||||
Self::chain_execute(
|
||||
&server,
|
||||
args.world.clone(),
|
||||
args.position
|
||||
.offset(props.facing.to_block_direction().to_offset()),
|
||||
props.facing,
|
||||
world,
|
||||
position.offset(facing.to_block_direction().to_offset()),
|
||||
facing,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let block = args.world.get_block(args.position);
|
||||
let is_auto = command_entity.auto.load(Ordering::Relaxed);
|
||||
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
|
||||
if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
|
||||
args.world
|
||||
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
})
|
||||
let block = args.world.get_block(args.position);
|
||||
let is_auto = command_entity.auto.load(Ordering::Relaxed);
|
||||
let can_run = command_entity.powered.load(Ordering::Relaxed) || is_auto;
|
||||
if block == &Block::REPEATING_COMMAND_BLOCK && can_run {
|
||||
args.world
|
||||
.schedule_block_tick(block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -334,8 +337,8 @@ impl BlockBehaviour for CommandBlock {
|
||||
false
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let send_command_feedback = {
|
||||
let game_rules = &args.world.level_info.load().game_rules;
|
||||
game_rules.send_command_feedback
|
||||
@@ -347,14 +350,11 @@ impl BlockBehaviour for CommandBlock {
|
||||
args.block.id == Block::CHAIN_COMMAND_BLOCK.id,
|
||||
);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: crate::block::GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async {
|
||||
fn get_comparator_output(&self, args: crate::block::GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
{
|
||||
let entity = args.world.get_block_entity(args.position);
|
||||
|
||||
entity.map_or_else(
|
||||
@@ -368,6 +368,6 @@ impl BlockBehaviour for CommandBlock {
|
||||
command_block_entity.map(|e| e.success_count.load(Ordering::Acquire) as u8)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs,
|
||||
BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnScheduledTickArgs,
|
||||
UseWithItemArgs, registry::BlockActionResult,
|
||||
},
|
||||
entity::{Entity, item::ItemEntity},
|
||||
@@ -27,32 +27,27 @@ use rand::RngExt;
|
||||
pub struct ComposterBlock;
|
||||
|
||||
impl BlockBehaviour for ComposterBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.level == 8 {
|
||||
self.clear_composter(args.world, args.position, state_id, args.block)
|
||||
.await;
|
||||
self.clear_composter(args.world, args.position, state_id, args.block);
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
|
||||
let level = props.level;
|
||||
|
||||
// Check if the composter is full
|
||||
if level == 8 {
|
||||
self.clear_composter(args.world, args.position, state_id, args.block)
|
||||
.await;
|
||||
self.clear_composter(args.world, args.position, state_id, args.block);
|
||||
return BlockActionResult::Consume;
|
||||
}
|
||||
|
||||
@@ -77,48 +72,35 @@ impl BlockBehaviour for ComposterBlock {
|
||||
state_id,
|
||||
args.block,
|
||||
level + 1,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
args.world
|
||||
.sync_world_event(WorldEvent::ComposterFill, *args.position, 1);
|
||||
}
|
||||
|
||||
// Consume the item
|
||||
BlockActionResult::Consume
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
|
||||
let level = props.level;
|
||||
if level == 7 {
|
||||
self.update_level_composter(
|
||||
args.world,
|
||||
args.position,
|
||||
state_id,
|
||||
args.block,
|
||||
level + 1,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = ComposterLikeProperties::from_state_id(state_id, args.block);
|
||||
let level = props.level;
|
||||
if level == 7 {
|
||||
self.update_level_composter(args.world, args.position, state_id, args.block, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
{
|
||||
let props = ComposterLikeProperties::from_state_id(args.state.id, args.block);
|
||||
Some(props.level)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComposterBlock {
|
||||
pub async fn update_level_composter(
|
||||
pub fn update_level_composter(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
location: &BlockPos,
|
||||
@@ -128,23 +110,20 @@ impl ComposterBlock {
|
||||
) {
|
||||
let mut props = ComposterLikeProperties::from_state_id(state_id, block);
|
||||
props.level = level;
|
||||
world
|
||||
.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(location, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
if level == 7 {
|
||||
world.schedule_block_tick(block, *location, 20, TickPriority::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clear_composter(
|
||||
pub fn clear_composter(
|
||||
&self,
|
||||
world: &Arc<World>,
|
||||
location: &BlockPos,
|
||||
state_id: BlockStateId,
|
||||
block: &Block,
|
||||
) {
|
||||
self.update_level_composter(world, location, state_id, block, 0)
|
||||
.await;
|
||||
self.update_level_composter(world, location, state_id, block, 0);
|
||||
|
||||
let item_position = {
|
||||
let mut rng = rand::rng();
|
||||
@@ -160,6 +139,6 @@ impl ComposterBlock {
|
||||
ItemStack::new(1, &Item::BONE_MEAL),
|
||||
);
|
||||
|
||||
world.spawn_entity(Arc::new(item_entity)).await;
|
||||
world.spawn_entity(Arc::new(item_entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::entities::conduit::ConduitBlockEntity;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs, PlacedArgs};
|
||||
use crate::block::{BlockBehaviour, OnPlaceArgs, PlacedArgs};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
@@ -9,20 +9,18 @@ use std::sync::Arc;
|
||||
pub struct ConduitBlock;
|
||||
|
||||
impl BlockBehaviour for ConduitBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
pumpkin_data::block_properties::MangroveRootsLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let entity = ConduitBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, OnScheduledTickArgs, PlacedArgs,
|
||||
BlockBehaviour, BlockMetadata, OnScheduledTickArgs, PlacedArgs,
|
||||
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
|
||||
};
|
||||
use pumpkin_data::{Block, BlockId, tag};
|
||||
@@ -18,25 +18,22 @@ impl BlockMetadata for CoralBlock {
|
||||
}
|
||||
}
|
||||
impl BlockBehaviour for CoralBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
try_schedule_die_tick(args.block, args.world, args.position).await;
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
|
||||
try_schedule_die_tick(args.block, args.world, args.position);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
let Some(dead_block) = get_dead_coral_block_type(args.block.id) else {
|
||||
return;
|
||||
};
|
||||
let dead_block_state_id = dead_block.default_state.id;
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
|
||||
let Some(dead_block) = get_dead_coral_block_type(args.block.id) else {
|
||||
return;
|
||||
};
|
||||
let dead_block_state_id = dead_block.default_state.id;
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
const fn get_dead_coral_block_type(id: BlockId) -> Option<&'static Block> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs,
|
||||
BlockBehaviour, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs,
|
||||
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
|
||||
},
|
||||
@@ -47,102 +47,90 @@ pub type CoralWallFanLikeProperties = LadderLikeProperties;
|
||||
pub type CoralFanLikeProperties = MangroveRootsLikeProperties;
|
||||
|
||||
impl BlockBehaviour for CoralFanBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return get_default_coral_fan_state_id(
|
||||
args.block,
|
||||
args.replacing.water_source(),
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut directions = args.player.get_entity().get_entity_facing_order();
|
||||
|
||||
if args.replacing == BlockIsReplacing::None {
|
||||
let face = args.direction.to_facing();
|
||||
let mut i = 0;
|
||||
while i < directions.len() && directions[i] != face {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
directions.copy_within(0..i, 1);
|
||||
directions[0] = face;
|
||||
}
|
||||
} else if directions[0] == Facing::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return get_default_coral_fan_state_id(
|
||||
args.block,
|
||||
args.replacing.water_source(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for dir in directions {
|
||||
if let (Some(h_facing), Some(opp_facing)) = (
|
||||
dir.to_horizontal_facing(),
|
||||
dir.opposite().to_horizontal_facing(),
|
||||
) && can_place_at(args.world, args.position, h_facing)
|
||||
{
|
||||
let Some(wall_block) = get_corresponding_wall_fan_type(args.block.id) else {
|
||||
return BlockStateId::AIR;
|
||||
};
|
||||
let mut coral_wall_fan_props = CoralWallFanLikeProperties::default(wall_block);
|
||||
coral_wall_fan_props.waterlogged = args.replacing.water_source();
|
||||
coral_wall_fan_props.facing = opp_facing;
|
||||
return coral_wall_fan_props.to_state_id(wall_block);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
|
||||
}
|
||||
BlockStateId::AIR
|
||||
})
|
||||
}
|
||||
}
|
||||
let mut directions = args.player.get_entity().get_entity_facing_order();
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await {
|
||||
try_schedule_die_tick(args.block, args.world, args.position).await;
|
||||
if args.replacing == BlockIsReplacing::None {
|
||||
let face = args.direction.to_facing();
|
||||
let mut i = 0;
|
||||
while i < directions.len() && directions[i] != face {
|
||||
i += 1;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
let current_state = args.world.get_block_state(args.position);
|
||||
|
||||
let Some(dead_block) = get_dead_type(args.block.id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// VANILLA FIX: Explicitly set waterlogged to false when dying
|
||||
let dead_block_state_id = if is_wall_fan(args.block) {
|
||||
let mut props =
|
||||
CoralWallFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(dead_block)
|
||||
} else {
|
||||
let mut props =
|
||||
CoralFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(dead_block)
|
||||
};
|
||||
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
|
||||
.await;
|
||||
if i > 0 {
|
||||
directions.copy_within(0..i, 1);
|
||||
directions[0] = face;
|
||||
}
|
||||
})
|
||||
} else if directions[0] == Facing::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
|
||||
}
|
||||
}
|
||||
|
||||
for dir in directions {
|
||||
if let (Some(h_facing), Some(opp_facing)) = (
|
||||
dir.to_horizontal_facing(),
|
||||
dir.opposite().to_horizontal_facing(),
|
||||
) && can_place_at(args.world, args.position, h_facing)
|
||||
{
|
||||
let Some(wall_block) = get_corresponding_wall_fan_type(args.block.id) else {
|
||||
return BlockStateId::AIR;
|
||||
};
|
||||
let mut coral_wall_fan_props = CoralWallFanLikeProperties::default(wall_block);
|
||||
coral_wall_fan_props.waterlogged = args.replacing.water_source();
|
||||
coral_wall_fan_props.facing = opp_facing;
|
||||
return coral_wall_fan_props.to_state_id(wall_block);
|
||||
}
|
||||
}
|
||||
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return get_default_coral_fan_state_id(args.block, args.replacing.water_source());
|
||||
}
|
||||
BlockStateId::AIR
|
||||
}
|
||||
|
||||
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> bool {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
if !scan_for_water(args.world, args.position) {
|
||||
try_schedule_die_tick(args.block, args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
|
||||
let current_state = args.world.get_block_state(args.position);
|
||||
|
||||
let Some(dead_block) = get_dead_type(args.block.id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// VANILLA FIX: Explicitly set waterlogged to false when dying
|
||||
let dead_block_state_id = if is_wall_fan(args.block) {
|
||||
let mut props =
|
||||
CoralWallFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(dead_block)
|
||||
} else {
|
||||
let mut props = CoralFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(dead_block)
|
||||
};
|
||||
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
let support_block = args.block_accessor.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) && !is_wall_fan(args.block) {
|
||||
return true;
|
||||
@@ -155,27 +143,25 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if is_wall_fan(args.block) {
|
||||
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.facing.to_block_direction().opposite() == args.direction
|
||||
&& !can_place_at(args.world, args.position, props.facing.opposite())
|
||||
{
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
} else if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if !support_block.is_center_solid(BlockDirection::Up) {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if is_wall_fan(args.block) {
|
||||
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.facing.to_block_direction().opposite() == args.direction
|
||||
&& !can_place_at(args.world, args.position, props.facing.opposite())
|
||||
{
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
} else if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if !support_block.is_center_solid(BlockDirection::Up) {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
}
|
||||
|
||||
args.state_id
|
||||
})
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ use pumpkin_data::{
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
blocks::coral::{is_dead_coral, scan_for_water, try_schedule_die_tick},
|
||||
};
|
||||
pub struct CoralPlantBlock;
|
||||
@@ -26,36 +26,30 @@ impl BlockMetadata for CoralPlantBlock {
|
||||
pub type CoralPlantLikeProperties = MangroveRootsLikeProperties;
|
||||
|
||||
impl BlockBehaviour for CoralPlantBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = CoralPlantLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = CoralPlantLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
try_schedule_die_tick(args.block, args.world, args.position).await;
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
|
||||
try_schedule_die_tick(args.block, args.world, args.position);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
let current_state = args.world.get_block_state(args.position);
|
||||
let dead_block_state_id = {
|
||||
let props =
|
||||
CoralPlantLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.to_state_id(get_dead_type(args.block.id).unwrap_or_default().to_block())
|
||||
};
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !scan_for_water(args.world, args.position) && !is_dead_coral(args.block) {
|
||||
let current_state = args.world.get_block_state(args.position);
|
||||
let dead_block_state_id = {
|
||||
let props = CoralPlantLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.to_state_id(get_dead_type(args.block.id).unwrap_or_default().to_block())
|
||||
};
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> bool {
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
let support_block = args.block_accessor.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) {
|
||||
return true;
|
||||
@@ -63,19 +57,17 @@ impl BlockBehaviour for CoralPlantBlock {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if !support_block.is_center_solid(BlockDirection::Up) {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if args.direction == BlockDirection::Down {
|
||||
let support_block = args.world.get_block_state(&args.position.down());
|
||||
if !support_block.is_center_solid(BlockDirection::Up) {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
const fn get_dead_type(id: BlockId) -> Option<BlockId> {
|
||||
|
||||
@@ -12,7 +12,7 @@ pub mod coral_block;
|
||||
pub mod coral_fan;
|
||||
pub mod coral_plant;
|
||||
|
||||
pub async fn scan_for_water(world: &Arc<World>, pos: &BlockPos) -> bool {
|
||||
pub fn scan_for_water(world: &Arc<World>, pos: &BlockPos) -> bool {
|
||||
for direction in BlockDirection::all() {
|
||||
let neighbor_pos = pos.offset(direction.to_offset());
|
||||
let block = world.get_fluid(&neighbor_pos);
|
||||
@@ -44,7 +44,7 @@ fn is_dead_coral(block: &Block) -> bool {
|
||||
|| block == &Block::DEAD_TUBE_CORAL_FAN
|
||||
|| block == &Block::DEAD_TUBE_CORAL_WALL_FAN
|
||||
}
|
||||
async fn try_schedule_die_tick(block: &Block, world: &Arc<World>, pos: &BlockPos) {
|
||||
pub fn try_schedule_die_tick(block: &Block, world: &Arc<World>, pos: &BlockPos) {
|
||||
let tick_delay = 60 + rand::rng().random_range(0..40);
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs};
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler;
|
||||
@@ -16,24 +16,22 @@ use tokio::sync::Mutex;
|
||||
pub struct CraftingTableBlock;
|
||||
|
||||
impl BlockBehaviour for CraftingTableBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(
|
||||
&CraftingTableScreenFactory(args.server.recipe_manager.clone()),
|
||||
Some(*args.position),
|
||||
)
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let recipe_manager = args.server.recipe_manager.clone();
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&CraftingTableScreenFactory(recipe_manager), Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::entities::creaking_heart::CreakingHeartBlockEntity;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:creaking_heart")]
|
||||
pub struct CreakingHeartBlock;
|
||||
@@ -45,24 +43,20 @@ impl CreakingHeartBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for CreakingHeartBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
CreakingHeartLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.axis = match args.direction {
|
||||
pumpkin_data::BlockDirection::North | pumpkin_data::BlockDirection::South => {
|
||||
Axis::Z
|
||||
}
|
||||
pumpkin_data::BlockDirection::East | pumpkin_data::BlockDirection::West => Axis::X,
|
||||
pumpkin_data::BlockDirection::Up | pumpkin_data::BlockDirection::Down => Axis::Y,
|
||||
};
|
||||
props.creaking_heart_state = CreakingHeartState::Uprooted;
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
CreakingHeartLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.axis = match args.direction {
|
||||
pumpkin_data::BlockDirection::North | pumpkin_data::BlockDirection::South => Axis::Z,
|
||||
pumpkin_data::BlockDirection::East | pumpkin_data::BlockDirection::West => Axis::X,
|
||||
pumpkin_data::BlockDirection::Up | pumpkin_data::BlockDirection::Down => Axis::Y,
|
||||
};
|
||||
props.creaking_heart_state = CreakingHeartState::Uprooted;
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let entity = CreakingHeartBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
|
||||
@@ -71,13 +65,11 @@ impl BlockBehaviour for CreakingHeartBlock {
|
||||
|
||||
if Self::check_active_logs(args.world.as_ref(), args.position, props.axis) {
|
||||
props.creaking_heart_state = CreakingHeartState::Dormant;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
|
||||
args.world.play_sound(
|
||||
Sound::BlockCreakingHeartSpawn,
|
||||
@@ -85,11 +77,11 @@ impl BlockBehaviour for CreakingHeartBlock {
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = CreakingHeartLikeProperties::from_state_id(state_id, args.block);
|
||||
|
||||
@@ -110,27 +102,24 @@ impl BlockBehaviour for CreakingHeartBlock {
|
||||
|
||||
if props.creaking_heart_state != new_state {
|
||||
props.creaking_heart_state = new_state;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
args.world.play_sound(
|
||||
Sound::BlockCreakingHeartBreak,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.world
|
||||
.drop_stack(args.position, ItemStack::new(1, &Item::CREAKING_HEART))
|
||||
.await;
|
||||
})
|
||||
.drop_stack(args.position, ItemStack::new(1, &Item::CREAKING_HEART));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,125 +10,104 @@ use pumpkin_macros::pumpkin_block;
|
||||
use crate::block::entities::decorated_pot::DecoratedPotBlockEntity;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
|
||||
PlacedArgs, UseWithItemArgs,
|
||||
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
|
||||
UseWithItemArgs,
|
||||
};
|
||||
|
||||
#[pumpkin_block("minecraft:decorated_pot")]
|
||||
pub struct DecoratedPotBlock;
|
||||
|
||||
impl BlockBehaviour for DecoratedPotBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
DecoratedPotLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = DecoratedPotBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let entity = DecoratedPotBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if args.item_stack.item_count == 0 {
|
||||
return self
|
||||
.normal_use(NormalUseArgs {
|
||||
server: args.server,
|
||||
world: args.world,
|
||||
block: args.block,
|
||||
position: args.position,
|
||||
player: args.player,
|
||||
hit: args.hit,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
if args.item_stack.item_count == 0 {
|
||||
return self.normal_use(NormalUseArgs {
|
||||
server: args.server,
|
||||
world: args.world,
|
||||
block: args.block,
|
||||
position: args.position,
|
||||
player: args.player,
|
||||
hit: args.hit,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
{
|
||||
if pot_entity.try_insert_item(args.item_stack, 1).await {
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsert,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
} else {
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsertFail,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
}
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
})
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsertFail,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
&& let Some(contained) = pot_entity.take_item().await
|
||||
{
|
||||
args.world.drop_stack(args.position, contained).await;
|
||||
}
|
||||
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotShatter,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.world
|
||||
.drop_stack(args.position, ItemStack::new(4, &Item::BRICK))
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
{
|
||||
Some(pot_entity.get_comparator_output().await)
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
{
|
||||
if pot_entity.try_insert_item(args.item_stack, 1) {
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsert,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
} else {
|
||||
Some(0)
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsertFail,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
}
|
||||
})
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
}
|
||||
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotInsertFail,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
&& let Some(contained) = pot_entity.take_item()
|
||||
{
|
||||
args.world.drop_stack(args.position, contained);
|
||||
}
|
||||
|
||||
args.world.play_sound(
|
||||
Sound::BlockDecoratedPotShatter,
|
||||
SoundCategory::Blocks,
|
||||
&args.position.to_f64(),
|
||||
);
|
||||
args.world
|
||||
.drop_stack(args.position, ItemStack::new(4, &Item::BRICK));
|
||||
}
|
||||
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(pot_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DecoratedPotBlockEntity>()
|
||||
{
|
||||
Some(pot_entity.get_comparator_output())
|
||||
} else {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::CanPlaceAtArgs;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
@@ -17,40 +16,32 @@ use pumpkin_world::world::BlockFlags;
|
||||
pub struct DirtPathBlock;
|
||||
|
||||
impl BlockBehaviour for DirtPathBlock {
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO: push up entities
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
// TODO: push up entities
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::DIRT.default_state.id;
|
||||
}
|
||||
|
||||
args.block.default_state.id
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::DIRT.default_state.id;
|
||||
}
|
||||
|
||||
args.block.default_state.id
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::Axis;
|
||||
@@ -17,7 +16,6 @@ use pumpkin_world::world::BlockFlags;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::BrokenArgs;
|
||||
use crate::block::CanPlaceAtArgs;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
@@ -36,7 +34,7 @@ use pumpkin_util::GameMode;
|
||||
|
||||
type DoorProperties = pumpkin_data::block_properties::OakDoorLikeProperties;
|
||||
|
||||
async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos) {
|
||||
fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos) {
|
||||
let (block, block_state) = world.get_block_and_state_id(block_pos);
|
||||
let mut door_props = DoorProperties::from_state_id(block_state, block);
|
||||
door_props.open = !door_props.open;
|
||||
@@ -58,20 +56,16 @@ async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos)
|
||||
*block_pos,
|
||||
);
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
block_pos,
|
||||
door_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
block_pos,
|
||||
door_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
world.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
|
||||
fn can_open_door(block: &Block) -> bool {
|
||||
@@ -103,7 +97,7 @@ fn get_sound(block: &Block, open: bool) -> Sound {
|
||||
|
||||
#[expect(clippy::pedantic)]
|
||||
#[inline]
|
||||
async fn get_hinge(
|
||||
fn get_hinge(
|
||||
world: &World,
|
||||
pos: &BlockPos,
|
||||
use_item: &SUseItemOn,
|
||||
@@ -177,7 +171,7 @@ impl DoorBlock {
|
||||
door_props.open
|
||||
}
|
||||
|
||||
pub async fn set_open(world: &Arc<World>, block_pos: &BlockPos, open: bool) {
|
||||
pub fn set_open(world: &Arc<World>, block_pos: &BlockPos, open: bool) {
|
||||
let (block, block_state) = world.get_block_and_state_id(block_pos);
|
||||
if !block.has_tag(&tag::Block::MINECRAFT_DOORS) {
|
||||
return;
|
||||
@@ -198,46 +192,39 @@ impl DoorBlock {
|
||||
|
||||
world.play_block_sound(get_sound(block, open), SoundCategory::Blocks, *block_pos);
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
block_pos,
|
||||
door_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
block_pos,
|
||||
door_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
|
||||
if other_block.id == block.id {
|
||||
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
|
||||
other_door_props.open = open;
|
||||
world
|
||||
.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehaviour for DoorBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await
|
||||
|| block_receives_redstone_power(args.world, &args.position.up()).await;
|
||||
|
||||
let direction = args.player.get_entity().get_horizontal_facing();
|
||||
let hinge = get_hinge(args.world, args.position, args.use_item_on, direction).await;
|
||||
|
||||
let mut door_props = DoorProperties::default(args.block);
|
||||
door_props.half = DoubleBlockHalf::Lower;
|
||||
door_props.facing = direction;
|
||||
door_props.hinge = hinge;
|
||||
door_props.powered = powered;
|
||||
door_props.open = powered;
|
||||
|
||||
door_props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut door_props = DoorProperties::default(args.block);
|
||||
let facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
door_props.facing = facing;
|
||||
door_props.half = DoubleBlockHalf::Lower;
|
||||
door_props.hinge = get_hinge(args.world, args.position, args.use_item_on, facing);
|
||||
door_props.open = false;
|
||||
door_props.powered = false;
|
||||
door_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -248,65 +235,59 @@ impl BlockBehaviour for DoorBlock {
|
||||
.replaceable()
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let mut door_props = DoorProperties::from_state_id(args.state_id, args.block);
|
||||
door_props.half = DoubleBlockHalf::Upper;
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
&args.position.offset(BlockDirection::Up.to_offset()),
|
||||
door_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
args.world.set_block_state(
|
||||
&args.position.offset(BlockDirection::Up.to_offset()),
|
||||
door_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
if !can_open_door(args.block) {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
|
||||
toggle_door(args.player, args.world, args.position).await;
|
||||
toggle_door(args.player, args.world, args.position);
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let door_props = DoorProperties::from_state_id(args.state.id, args.block);
|
||||
let other_half_pos = match door_props.half {
|
||||
DoubleBlockHalf::Upper => args.position.down(),
|
||||
DoubleBlockHalf::Lower => args.position.up(),
|
||||
};
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
let door_props = DoorProperties::from_state_id(args.state.id, args.block);
|
||||
let other_half_pos = match door_props.half {
|
||||
DoubleBlockHalf::Upper => args.position.down(),
|
||||
DoubleBlockHalf::Lower => args.position.up(),
|
||||
};
|
||||
|
||||
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
|
||||
if neighbor_state_id.to_block_id() != args.block.id {
|
||||
args.world.update_neighbors(&other_half_pos, None).await;
|
||||
return; // Neighbor is already gone or is a different block
|
||||
}
|
||||
let neighbor_state_id = args.world.get_block_state_id(&other_half_pos);
|
||||
if neighbor_state_id.to_block_id() != args.block.id {
|
||||
args.world.update_neighbors(&other_half_pos, None);
|
||||
return; // Neighbor is already gone or is a different block
|
||||
}
|
||||
|
||||
let is_creative = args.player.gamemode.load() == GameMode::Creative;
|
||||
let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative {
|
||||
BlockFlags::NOTIFY_ALL
|
||||
} else {
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL
|
||||
};
|
||||
let is_creative = args.player.gamemode.load() == GameMode::Creative;
|
||||
let flags = if door_props.half == DoubleBlockHalf::Upper && !is_creative {
|
||||
BlockFlags::NOTIFY_ALL
|
||||
} else {
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL
|
||||
};
|
||||
|
||||
args.world
|
||||
.break_block(&other_half_pos, Some(args.player.clone()), flags)
|
||||
.await;
|
||||
})
|
||||
args.world
|
||||
.break_block(&other_half_pos, Some(args.player.clone()), flags);
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut door_props = DoorProperties::from_state_id(block_state.id, args.block);
|
||||
|
||||
let other_half = match door_props.half {
|
||||
DoubleBlockHalf::Upper => BlockDirection::Down,
|
||||
DoubleBlockHalf::Lower => BlockDirection::Up,
|
||||
@@ -314,23 +295,28 @@ impl BlockBehaviour for DoorBlock {
|
||||
let other_pos = args.position.offset(other_half.to_offset());
|
||||
let (other_block, other_state_id) = args.world.get_block_and_state_id(&other_pos);
|
||||
|
||||
if other_block.id != args.block.id {
|
||||
return;
|
||||
}
|
||||
let powered = block_receives_redstone_power(args.world, args.position)
|
||||
|| block_receives_redstone_power(args.world, &other_pos);
|
||||
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await
|
||||
|| block_receives_redstone_power(args.world, &other_pos).await;
|
||||
if door_props.powered != powered {
|
||||
let sound_half = if door_props.open {
|
||||
DoubleBlockHalf::Lower
|
||||
} else {
|
||||
DoubleBlockHalf::Upper
|
||||
};
|
||||
|
||||
if args.block.id == other_block.id && powered != door_props.powered {
|
||||
let mut other_door_props =
|
||||
DoorProperties::from_state_id(other_state_id, other_block);
|
||||
door_props.powered = !door_props.powered;
|
||||
other_door_props.powered = door_props.powered;
|
||||
|
||||
if powered != door_props.open {
|
||||
door_props.open = door_props.powered;
|
||||
other_door_props.open = other_door_props.powered;
|
||||
door_props.powered = powered;
|
||||
other_door_props.powered = powered;
|
||||
|
||||
if door_props.open != powered {
|
||||
door_props.open = powered;
|
||||
other_door_props.open = powered;
|
||||
}
|
||||
|
||||
if door_props.half == sound_half {
|
||||
args.world.play_block_sound(
|
||||
get_sound(args.block, powered),
|
||||
SoundCategory::Blocks,
|
||||
@@ -338,55 +324,48 @@ impl BlockBehaviour for DoorBlock {
|
||||
);
|
||||
}
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
door_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world
|
||||
.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
door_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
args.world.set_block_state(
|
||||
&other_pos,
|
||||
other_door_props.to_state_id(other_block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
|
||||
if args.direction.to_axis() != Axis::Y
|
||||
|| (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
|
||||
if args.direction.to_axis() != Axis::Y
|
||||
|| (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
|
||||
{
|
||||
if lv == DoubleBlockHalf::Lower
|
||||
&& args.direction == BlockDirection::Down
|
||||
&& !has_support(args.world, args.position)
|
||||
{
|
||||
if lv == DoubleBlockHalf::Lower
|
||||
&& args.direction == BlockDirection::Down
|
||||
&& !has_support(args.world, args.position)
|
||||
{
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
} else if Block::from_state_id(args.neighbor_state_id).id == args.block.id
|
||||
&& DoorProperties::from_state_id(args.neighbor_state_id, args.block).half != lv
|
||||
{
|
||||
let mut new_state =
|
||||
DoorProperties::from_state_id(args.neighbor_state_id, args.block);
|
||||
new_state.half = lv;
|
||||
return new_state.to_state_id(args.block);
|
||||
} else {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
} else if Block::from_state_id(args.neighbor_state_id).id == args.block.id
|
||||
&& DoorProperties::from_state_id(args.neighbor_state_id, args.block).half != lv
|
||||
{
|
||||
let mut new_state = DoorProperties::from_state_id(args.neighbor_state_id, args.block);
|
||||
new_state.half = lv;
|
||||
return new_state.to_state_id(args.block);
|
||||
} else {
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||
{
|
||||
if args.moved {
|
||||
return;
|
||||
}
|
||||
@@ -403,14 +382,12 @@ impl BlockBehaviour for DoorBlock {
|
||||
DoubleBlockHalf::Lower => args.position.up(),
|
||||
};
|
||||
|
||||
args.world
|
||||
.break_block(
|
||||
&other_half_pos,
|
||||
None,
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
args.world.break_block(
|
||||
&other_half_pos,
|
||||
None,
|
||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::block::blocks::falling::FallingBlock;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, PlacedArgs};
|
||||
use crate::block::{BlockBehaviour, BrokenArgs, NormalUseArgs, PlacedArgs};
|
||||
use crate::world::World;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@@ -12,7 +12,7 @@ use std::sync::Arc;
|
||||
pub struct DragonEggBlock;
|
||||
|
||||
impl DragonEggBlock {
|
||||
async fn teleport(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
fn teleport(world: &Arc<World>, pos: &BlockPos) {
|
||||
for _ in 0..1000 {
|
||||
let x = pos.0.x + rng().random_range(-16..16);
|
||||
let y = pos.0.y + rng().random_range(-8..8);
|
||||
@@ -24,20 +24,16 @@ impl DragonEggBlock {
|
||||
|
||||
if state.is_air() && !below_state.is_air() {
|
||||
let current_state = world.get_block_state(pos);
|
||||
world
|
||||
.set_block_state(
|
||||
&test_pos,
|
||||
current_state.id,
|
||||
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
pumpkin_data::Block::AIR.default_state.id,
|
||||
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&test_pos,
|
||||
current_state.id,
|
||||
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
world.set_block_state(
|
||||
pos,
|
||||
pumpkin_data::Block::AIR.default_state.id,
|
||||
pumpkin_world::world::BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -45,33 +41,22 @@ impl DragonEggBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for DragonEggBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 5, TickPriority::Normal);
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 5, TickPriority::Normal);
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
self.teleport(args.world, args.position).await;
|
||||
BlockActionResult::Success
|
||||
})
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
Self::teleport(args.world, args.position);
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
// Dragon egg is typically teleported when attacked
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.teleport(args.world, args.position).await;
|
||||
})
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
Self::teleport(args.world, args.position);
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(
|
||||
&'a self,
|
||||
args: crate::block::OnScheduledTickArgs<'a>,
|
||||
) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: crate::block::OnScheduledTickArgs<'_>) {
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnPlaceArgs, PlacedArgs,
|
||||
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
PlacedArgs,
|
||||
},
|
||||
entity::player::Player,
|
||||
world::World,
|
||||
@@ -30,26 +30,24 @@ impl BlockBehaviour for DripstoneBlock {
|
||||
args.player,
|
||||
)
|
||||
}
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut dripstone_props = PointedDripstoneLikeProperties::default(args.block);
|
||||
dripstone_props.waterlogged = args.replacing.water_source();
|
||||
let Some(support_block_ver_dir) = get_support_block_vertical_direction(
|
||||
args.world,
|
||||
args.position,
|
||||
Some(args.direction),
|
||||
Some(args.player),
|
||||
) else {
|
||||
//this shouldn't happen
|
||||
return Block::AIR.default_state.id;
|
||||
};
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut dripstone_props = PointedDripstoneLikeProperties::default(args.block);
|
||||
dripstone_props.waterlogged = args.replacing.water_source();
|
||||
let Some(support_block_ver_dir) = get_support_block_vertical_direction(
|
||||
args.world,
|
||||
args.position,
|
||||
Some(args.direction),
|
||||
Some(args.player),
|
||||
) else {
|
||||
//this shouldn't happen
|
||||
return Block::AIR.default_state.id;
|
||||
};
|
||||
|
||||
dripstone_props.vertical_direction = flip_dir(support_block_ver_dir);
|
||||
dripstone_props.to_state_id(&Block::POINTED_DRIPSTONE)
|
||||
})
|
||||
dripstone_props.vertical_direction = flip_dir(support_block_ver_dir);
|
||||
dripstone_props.to_state_id(&Block::POINTED_DRIPSTONE)
|
||||
}
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let (len, vertical_dir) = get_stalagmite_or_stalactice_len_and_dir_from_tip_pos(
|
||||
args.world,
|
||||
args.position,
|
||||
@@ -57,16 +55,16 @@ impl BlockBehaviour for DripstoneBlock {
|
||||
);
|
||||
match vertical_dir {
|
||||
VerticalDirection::Up => {
|
||||
update_stalagmite(args.world, len, args.position).await;
|
||||
update_stalagmite(args.world, len, args.position);
|
||||
}
|
||||
VerticalDirection::Down => {
|
||||
update_stalactite(args.world, len, args.position).await;
|
||||
update_stalactite(args.world, len, args.position);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
let broken_dripstone_props =
|
||||
PointedDripstoneLikeProperties::from_state_id(args.state.id, args.block);
|
||||
let new_tip_pos = match broken_dripstone_props.vertical_direction {
|
||||
@@ -81,54 +79,52 @@ impl BlockBehaviour for DripstoneBlock {
|
||||
);
|
||||
match vertical_dir {
|
||||
VerticalDirection::Up => {
|
||||
update_stalagmite(args.world, len, &new_tip_pos).await;
|
||||
update_stalagmite(args.world, len, &new_tip_pos);
|
||||
}
|
||||
VerticalDirection::Down => {
|
||||
update_stalactite(args.world, len, &new_tip_pos).await;
|
||||
update_stalactite(args.world, len, &new_tip_pos);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at_pos(args.world, args.position, None, None) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
let mut dripstone_props =
|
||||
PointedDripstoneLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if dripstone_props.thickness != SpeleothemThickness::TipMerge {
|
||||
return args.state_id;
|
||||
}
|
||||
match dripstone_props.vertical_direction {
|
||||
VerticalDirection::Up => {
|
||||
let block_above = args.world.get_block(&args.position.up());
|
||||
if block_above != &Block::POINTED_DRIPSTONE {
|
||||
dripstone_props.thickness = SpeleothemThickness::Tip;
|
||||
return dripstone_props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
VerticalDirection::Down => {
|
||||
let block_below = args.world.get_block(&args.position.down());
|
||||
if block_below != &Block::POINTED_DRIPSTONE {
|
||||
dripstone_props.thickness = SpeleothemThickness::Tip;
|
||||
return dripstone_props.to_state_id(args.block);
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at_pos(args.world, args.position, None, None) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
let mut dripstone_props =
|
||||
PointedDripstoneLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if dripstone_props.thickness != SpeleothemThickness::TipMerge {
|
||||
return args.state_id;
|
||||
}
|
||||
match dripstone_props.vertical_direction {
|
||||
VerticalDirection::Up => {
|
||||
let block_above = args.world.get_block(&args.position.up());
|
||||
if block_above != &Block::POINTED_DRIPSTONE {
|
||||
dripstone_props.thickness = SpeleothemThickness::Tip;
|
||||
return dripstone_props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
VerticalDirection::Down => {
|
||||
let block_below = args.world.get_block(&args.position.down());
|
||||
if block_below != &Block::POINTED_DRIPSTONE {
|
||||
dripstone_props.thickness = SpeleothemThickness::Tip;
|
||||
return dripstone_props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
async fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
|
||||
fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
|
||||
let block_above = world.get_block(&tip_pos.up());
|
||||
if block_above == &Block::POINTED_DRIPSTONE {
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge).await;
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up(), SpeleothemThickness::TipMerge).await;
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge);
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up(), SpeleothemThickness::TipMerge);
|
||||
} else {
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip).await;
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip);
|
||||
}
|
||||
match stalagmite_len {
|
||||
2 => {
|
||||
@@ -136,74 +132,65 @@ async fn update_stalagmite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &Blo
|
||||
world,
|
||||
&tip_pos.down_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
3 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(2),
|
||||
SpeleothemThickness::Base,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
4 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(2),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(3),
|
||||
SpeleothemThickness::Base,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
5 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(2),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.down_height(3),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
|
||||
fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &BlockPos) {
|
||||
let block_below = world.get_block(&tip_pos.down());
|
||||
if block_below == &Block::POINTED_DRIPSTONE {
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge).await;
|
||||
modify_dripstone_thickness_to(world, &tip_pos.down(), SpeleothemThickness::TipMerge).await;
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::TipMerge);
|
||||
modify_dripstone_thickness_to(world, &tip_pos.down(), SpeleothemThickness::TipMerge);
|
||||
} else {
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip).await;
|
||||
modify_dripstone_thickness_to(world, tip_pos, SpeleothemThickness::Tip);
|
||||
}
|
||||
match stalagmite_len {
|
||||
2 => {
|
||||
@@ -211,54 +198,45 @@ async fn update_stalactite(world: &Arc<World>, stalagmite_len: u8, tip_pos: &Blo
|
||||
world,
|
||||
&tip_pos.up_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
3 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up_height(2), SpeleothemThickness::Base)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up_height(2), SpeleothemThickness::Base);
|
||||
}
|
||||
4 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(2),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up_height(3), SpeleothemThickness::Base)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(world, &tip_pos.up_height(3), SpeleothemThickness::Base);
|
||||
}
|
||||
5 => {
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(1),
|
||||
SpeleothemThickness::Frustum,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(2),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
modify_dripstone_thickness_to(
|
||||
world,
|
||||
&tip_pos.up_height(3),
|
||||
SpeleothemThickness::Middle,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -382,7 +360,7 @@ fn can_support_dripstone(support_block: &Block) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
async fn modify_dripstone_thickness_to(
|
||||
fn modify_dripstone_thickness_to(
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
new_thickness: SpeleothemThickness,
|
||||
@@ -399,13 +377,11 @@ async fn modify_dripstone_thickness_to(
|
||||
return;
|
||||
}
|
||||
support_props.thickness = new_thickness;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
support_props.to_state_id(&Block::POINTED_DRIPSTONE),
|
||||
BlockFlags::empty(),
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
support_props.to_state_id(&Block::POINTED_DRIPSTONE),
|
||||
BlockFlags::empty(),
|
||||
);
|
||||
}
|
||||
fn offset_pos_by_vertical_dir(pos: &BlockPos, ver_dir: VerticalDirection) -> BlockPos {
|
||||
match ver_dir {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::block::entities::enchanting_table::EnchantingTableBlockEntity;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, PlacedArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs, PlacedArgs};
|
||||
use pumpkin_data::{Block, BlockStateId, translation};
|
||||
use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler;
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
@@ -19,70 +19,71 @@ use tokio::sync::Mutex;
|
||||
pub struct EnchantingTableBlock;
|
||||
|
||||
impl BlockBehaviour for EnchantingTableBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = EnchantingTableBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let entity = EnchantingTableBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let mut bookshelf_count = 0;
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let mut bookshelf_count = 0;
|
||||
|
||||
for off_z in -1..=1 {
|
||||
for off_x in -1..=1 {
|
||||
if (off_z != 0 || off_x != 0)
|
||||
&& args
|
||||
.world
|
||||
.get_block_state(&args.position.add(off_x, 0, off_z))
|
||||
.id
|
||||
== BlockStateId::AIR
|
||||
&& args
|
||||
.world
|
||||
.get_block_state(&args.position.add(off_x, 1, off_z))
|
||||
.id
|
||||
== BlockStateId::AIR
|
||||
// Air
|
||||
{
|
||||
for off_y in 0..=1 {
|
||||
for off_z in -1..=1 {
|
||||
for off_x in -1..=1 {
|
||||
if (off_z != 0 || off_x != 0)
|
||||
&& args
|
||||
.world
|
||||
.get_block_state(&args.position.add(off_x, 0, off_z))
|
||||
.id
|
||||
== BlockStateId::AIR
|
||||
&& args
|
||||
.world
|
||||
.get_block_state(&args.position.add(off_x, 1, off_z))
|
||||
.id
|
||||
== BlockStateId::AIR
|
||||
// Air
|
||||
{
|
||||
for off_y in 0..=1 {
|
||||
if Self::is_bookshelf(
|
||||
args.world,
|
||||
&args.position.add(off_x * 2, off_y, off_z * 2),
|
||||
) {
|
||||
bookshelf_count += 1;
|
||||
}
|
||||
if off_x != 0 && off_z != 0 {
|
||||
if Self::is_bookshelf(
|
||||
args.world,
|
||||
&args.position.add(off_x * 2, off_y, off_z * 2),
|
||||
&args.position.add(off_x * 2, off_y, off_z),
|
||||
) {
|
||||
bookshelf_count += 1;
|
||||
}
|
||||
if off_x != 0 && off_z != 0 {
|
||||
if Self::is_bookshelf(
|
||||
args.world,
|
||||
&args.position.add(off_x * 2, off_y, off_z),
|
||||
) {
|
||||
bookshelf_count += 1;
|
||||
}
|
||||
if Self::is_bookshelf(
|
||||
args.world,
|
||||
&args.position.add(off_x, off_y, off_z * 2),
|
||||
) {
|
||||
bookshelf_count += 1;
|
||||
}
|
||||
if Self::is_bookshelf(
|
||||
args.world,
|
||||
&args.position.add(off_x, off_y, off_z * 2),
|
||||
) {
|
||||
bookshelf_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let bookshelf_count = bookshelf_count.min(15);
|
||||
}
|
||||
let bookshelf_count = bookshelf_count.min(15);
|
||||
|
||||
args.player
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
let seed = args.player.enchantment_seed();
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(
|
||||
&EnchantingTableScreenFactory {
|
||||
bookshelf_count,
|
||||
seed: args.player.enchantment_seed(),
|
||||
seed,
|
||||
},
|
||||
Some(*args.position),
|
||||
Some(pos),
|
||||
)
|
||||
.await;
|
||||
BlockActionResult::Success
|
||||
})
|
||||
});
|
||||
BlockActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::OnEntityCollisionArgs;
|
||||
use crate::block::PlacedArgs;
|
||||
use crate::block::entities::end_portal::EndPortalBlockEntity;
|
||||
@@ -12,36 +11,31 @@ use pumpkin_macros::pumpkin_block;
|
||||
pub struct EndPortalBlock;
|
||||
|
||||
impl BlockBehaviour for EndPortalBlock {
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let target_world =
|
||||
if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name {
|
||||
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
|
||||
} else {
|
||||
args.server.get_world_from_dimension(&Dimension::THE_END)
|
||||
};
|
||||
if Arc::ptr_eq(&target_world, args.world) {
|
||||
return;
|
||||
}
|
||||
tracing::info!(
|
||||
"End portal collision at {:?}, targeting world {:?}",
|
||||
args.position,
|
||||
target_world.dimension.minecraft_name
|
||||
);
|
||||
args.entity
|
||||
.get_entity()
|
||||
.try_use_portal(0, target_world, *args.position)
|
||||
.await;
|
||||
})
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
let target_world =
|
||||
if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name {
|
||||
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
|
||||
} else {
|
||||
args.server.get_world_from_dimension(&Dimension::THE_END)
|
||||
};
|
||||
if Arc::ptr_eq(&target_world, args.world) {
|
||||
return;
|
||||
}
|
||||
tracing::info!(
|
||||
"End portal collision at {:?}, targeting world {:?}",
|
||||
args.position,
|
||||
target_world.dimension.minecraft_name
|
||||
);
|
||||
args.entity
|
||||
.get_entity()
|
||||
.try_use_portal(0, target_world, *args.position);
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let nbt = EndPortalBlockEntity::create_nbt(*args.position);
|
||||
args.world.add_block_entity_nbt(*args.position, &nbt);
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let nbt = EndPortalBlockEntity::create_nbt(*args.position);
|
||||
args.world.add_block_entity_nbt(*args.position, &nbt);
|
||||
|
||||
args.world
|
||||
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position)));
|
||||
})
|
||||
args.world
|
||||
.add_block_entity(Arc::new(EndPortalBlockEntity::new(*args.position)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::{
|
||||
block::{BlockBehaviour, BlockFuture, OnPlaceArgs},
|
||||
block::{BlockBehaviour, OnPlaceArgs},
|
||||
entity::EntityBase,
|
||||
};
|
||||
|
||||
@@ -13,13 +13,10 @@ type EndPortalFrameProperties = pumpkin_data::block_properties::EndPortalFrameLi
|
||||
pub struct EndPortalFrameBlock;
|
||||
|
||||
impl BlockBehaviour for EndPortalFrameBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut end_portal_frame_props = EndPortalFrameProperties::default(args.block);
|
||||
end_portal_frame_props.facing =
|
||||
args.player.get_entity().get_horizontal_facing().opposite();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut end_portal_frame_props = EndPortalFrameProperties::default(args.block);
|
||||
end_portal_frame_props.facing = args.player.get_entity().get_horizontal_facing().opposite();
|
||||
|
||||
end_portal_frame_props.to_state_id(args.block)
|
||||
})
|
||||
end_portal_frame_props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::{BlockBehaviour, OnPlaceArgs};
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -10,24 +9,22 @@ use pumpkin_macros::pumpkin_block;
|
||||
pub struct EndRodBlock;
|
||||
|
||||
impl BlockBehaviour for EndRodBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = EndRodLikeProperties::default(args.block);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = EndRodLikeProperties::default(args.block);
|
||||
|
||||
let blockstate = args
|
||||
.world
|
||||
.get_block_state_id(&args.position.offset(args.direction.to_offset()));
|
||||
let blockstate = args
|
||||
.world
|
||||
.get_block_state_id(&args.position.offset(args.direction.to_offset()));
|
||||
|
||||
if Block::from_state_id(blockstate).eq(args.block)
|
||||
&& EndRodLikeProperties::from_state_id(blockstate, args.block).facing
|
||||
== args.direction.to_facing().opposite()
|
||||
{
|
||||
props.facing = args.direction.to_facing();
|
||||
} else {
|
||||
props.facing = args.direction.to_facing().opposite();
|
||||
}
|
||||
if Block::from_state_id(blockstate).eq(args.block)
|
||||
&& EndRodLikeProperties::from_state_id(blockstate, args.block).facing
|
||||
== args.direction.to_facing().opposite()
|
||||
{
|
||||
props.facing = args.direction.to_facing();
|
||||
} else {
|
||||
props.facing = args.direction.to_facing().opposite();
|
||||
}
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::block::entities::ender_chest::EnderChestBlockEntity;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
|
||||
BlockBehaviour, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs,
|
||||
registry::BlockActionResult,
|
||||
};
|
||||
use crate::world::World;
|
||||
@@ -57,77 +57,69 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
|
||||
pub struct EnderChestBlock;
|
||||
|
||||
impl BlockBehaviour for EnderChestBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = LadderLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = LadderLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
// On the server, we don't need to do more because the client is responsible for that.
|
||||
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
|
||||
})
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
// On the server, we don't need to do more because the client is responsible for that.
|
||||
args.r#type == Self::LID_ANIMATION_EVENT_TYPE
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if is_chest_blocked(args.world, args.position) {
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if is_chest_blocked(args.world, args.position) {
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
|
||||
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
|
||||
be
|
||||
} else {
|
||||
let be = Arc::new(EnderChestBlockEntity::new(*args.position));
|
||||
args.world.add_block_entity(be.clone());
|
||||
be
|
||||
};
|
||||
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
|
||||
be
|
||||
} else {
|
||||
let be = Arc::new(EnderChestBlockEntity::new(*args.position));
|
||||
args.world.add_block_entity(be.clone());
|
||||
be
|
||||
};
|
||||
|
||||
if let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<EnderChestBlockEntity>()
|
||||
{
|
||||
let inventory = args.player.ender_chest_inventory();
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
if let Some(block_entity) = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<EnderChestBlockEntity>()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
let tracker = block_entity.get_tracker();
|
||||
tokio::spawn(async move {
|
||||
let inventory = player.ender_chest_inventory();
|
||||
player
|
||||
.open_handled_screen(
|
||||
&EnderChestScreenFactory {
|
||||
inventory: inventory.clone(),
|
||||
tracker: Some(block_entity.get_tracker()),
|
||||
tracker: Some(tracker),
|
||||
},
|
||||
Some(*args.position),
|
||||
Some(pos),
|
||||
)
|
||||
.await;
|
||||
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
|
||||
}
|
||||
});
|
||||
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
|
||||
}
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_entity = EnderChestBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let block_entity = EnderChestBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, GetStateForNeighborUpdateArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
PlacedArgs,
|
||||
},
|
||||
entity::falling::FallingEntity,
|
||||
};
|
||||
@@ -29,33 +29,29 @@ impl BlockMetadata for FallingBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for FallingBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
// TODO: make delay configurable
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal);
|
||||
})
|
||||
}
|
||||
}
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
// TODO: make delay configurable
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal);
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
// TODO: make delay configurable
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 2, TickPriority::Normal);
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let (block, state) = args.world.get_block_and_state(&args.position.down());
|
||||
if !Self::can_fall_through(state, block) || args.position.0.y < args.world.min_y {
|
||||
return;
|
||||
}
|
||||
let state = args.world.get_block_state(args.position);
|
||||
FallingEntity::replace_spawn(args.world, *args.position, state.id).await;
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let (block, state) = args.world.get_block_and_state(&args.position.down());
|
||||
if !Self::can_fall_through(state, block) || args.position.0.y < args.world.min_y {
|
||||
return;
|
||||
}
|
||||
let state = args.world.get_block_state(args.position);
|
||||
FallingEntity::replace_spawn(args.world, *args.position, state.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::CanPlaceAtArgs;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
@@ -28,123 +27,72 @@ type FarmlandProperties = FarmlandLikeProperties;
|
||||
pub struct FarmlandBlock;
|
||||
|
||||
impl BlockBehaviour for FarmlandBlock {
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO: push up entities
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
// TODO: push up entities
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::DIRT.default_state.id;
|
||||
}
|
||||
args.block.default_state.id
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
return Block::DIRT.default_state.id;
|
||||
}
|
||||
args.block.default_state.id
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction == BlockDirection::Up && !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
can_place_at(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO: add rain check. Remember to check which one is most optimized.
|
||||
if is_water_nearby(args.world, args.position) {
|
||||
let mut props = FarmlandProperties::default(args.block);
|
||||
props.moisture = 7;
|
||||
let mut event = crate::plugin::block::moisture_change::MoistureChangeEvent {
|
||||
block_pos: *args.position,
|
||||
world: args.world.clone(),
|
||||
new_moisture: 7,
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
}
|
||||
if !event.cancelled {
|
||||
props.moisture = (event.new_moisture.clamp(0, 7)) as u8;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
// TODO: add rain check. Remember to check which one is most optimized.
|
||||
if is_water_nearby(args.world, args.position) {
|
||||
let mut props = FarmlandProperties::default(args.block);
|
||||
props.moisture = 7;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
} else {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = FarmlandProperties::from_state_id(state_id, args.block);
|
||||
if props.moisture == 0 {
|
||||
if !args
|
||||
.world
|
||||
.get_block(&args.position.up())
|
||||
.has_tag(&tag::Block::MINECRAFT_MAINTAINS_FARMLAND)
|
||||
{
|
||||
//TODO push entities up
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = FarmlandProperties::from_state_id(state_id, args.block);
|
||||
if props.moisture == 0 {
|
||||
if !args
|
||||
.world
|
||||
.get_block(&args.position.up())
|
||||
.has_tag(&tag::Block::MINECRAFT_MAINTAINS_FARMLAND)
|
||||
{
|
||||
let mut event =
|
||||
crate::plugin::api::events::block::block_fade::BlockFadeEvent::new(
|
||||
*args.position,
|
||||
&Block::DIRT,
|
||||
);
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
}
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO push entities up
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::DIRT.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
let mut event = crate::plugin::block::moisture_change::MoistureChangeEvent {
|
||||
block_pos: *args.position,
|
||||
world: args.world.clone(),
|
||||
new_moisture: (props.moisture as i32) - 1,
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = args.world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
}
|
||||
if !event.cancelled {
|
||||
props.moisture = (event.new_moisture.clamp(0, 7)) as u8;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
props.moisture = (props.moisture as i32 - 1).clamp(0, 7) as u8;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ use std::sync::Arc;
|
||||
use crate::block::blocks::redstone::block_receives_redstone_power;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs,
|
||||
OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::player::Player;
|
||||
@@ -38,7 +37,7 @@ fn get_sound(block: &Block, open: bool) -> Sound {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn toggle_fence_gate(
|
||||
pub fn toggle_fence_gate(
|
||||
world: &Arc<World>,
|
||||
block_pos: &BlockPos,
|
||||
player: &Player,
|
||||
@@ -68,13 +67,11 @@ pub async fn toggle_fence_gate(
|
||||
*block_pos,
|
||||
);
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
block_pos,
|
||||
fence_gate_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
block_pos,
|
||||
fence_gate_props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
fence_gate_props.to_state_id(block)
|
||||
}
|
||||
|
||||
@@ -82,43 +79,39 @@ pub async fn toggle_fence_gate(
|
||||
pub struct FenceGateBlock;
|
||||
|
||||
impl BlockBehaviour for FenceGateBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut fence_gate_props = FenceGateProperties::default(args.block);
|
||||
fence_gate_props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut fence_gate_props = FenceGateProperties::default(args.block);
|
||||
fence_gate_props.facing = args.player.get_entity().get_horizontal_facing();
|
||||
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await;
|
||||
fence_gate_props.powered = powered;
|
||||
fence_gate_props.open = powered;
|
||||
let powered = block_receives_redstone_power(args.world, args.position);
|
||||
fence_gate_props.powered = powered;
|
||||
fence_gate_props.open = powered;
|
||||
|
||||
fence_gate_props.to_state_id(args.block)
|
||||
})
|
||||
fence_gate_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let fence_props = is_in_wall(&args);
|
||||
fence_props.to_state_id(args.block)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let fence_props = is_in_wall(&args);
|
||||
fence_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
toggle_fence_gate(args.world, args.position, args.player).await;
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
toggle_fence_gate(args.world, args.position, args.player);
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut fence_gate_props =
|
||||
FenceGateProperties::from_state_id(block_state.id, args.block);
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await;
|
||||
let powered = block_receives_redstone_power(args.world, args.position);
|
||||
|
||||
if powered == fence_gate_props.powered {
|
||||
return;
|
||||
@@ -136,14 +129,12 @@ impl BlockBehaviour for FenceGateBlock {
|
||||
);
|
||||
}
|
||||
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
fence_gate_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
fence_gate_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
@@ -22,23 +21,19 @@ use crate::world::World;
|
||||
pub struct FenceBlock;
|
||||
|
||||
impl BlockBehaviour for FenceBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut fence_props = FenceProperties::default(args.block);
|
||||
fence_props.waterlogged = args.replacing.water_source();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut fence_props = FenceProperties::default(args.block);
|
||||
fence_props.waterlogged = args.replacing.water_source();
|
||||
|
||||
compute_fence_state(fence_props, args.world, args.block, args.position)
|
||||
})
|
||||
compute_fence_state(fence_props, args.world, args.block, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let fence_props = FenceProperties::from_state_id(args.state_id, args.block);
|
||||
compute_fence_state(fence_props, args.world, args.block, args.position)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let fence_props = FenceProperties::from_state_id(args.state_id, args.block);
|
||||
compute_fence_state(fence_props, args.world, args.block, args.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::block::blocks::tnt::TNTBlock;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnEntityCollisionArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
use crate::world::World;
|
||||
@@ -148,7 +148,7 @@ impl FireBlock {
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u8) {
|
||||
fn try_spreading_fire(&self, world: &Arc<World>, pos: &BlockPos, chance: i32, age: u8) {
|
||||
let block = world.get_block(pos);
|
||||
let odds = Self::get_burn_odds(block);
|
||||
if rand::rng().random_range(0..chance) < odds {
|
||||
@@ -161,93 +161,70 @@ impl FireBlock {
|
||||
let mut fire_props = FireProperties::from_state_id(state_id, &Block::FIRE);
|
||||
fire_props.age = new_age;
|
||||
let new_state_id = fire_props.to_state_id(&Block::FIRE);
|
||||
world
|
||||
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
|
||||
.await;
|
||||
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
|
||||
} else {
|
||||
let mut burn_event = crate::plugin::block::block_burn::BlockBurnEvent {
|
||||
igniting_block: &Block::FIRE,
|
||||
block: old_block,
|
||||
cancelled: false,
|
||||
};
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut burn_event).await;
|
||||
}
|
||||
if burn_event.cancelled {
|
||||
return;
|
||||
}
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
|
||||
if old_block == &Block::TNT {
|
||||
TNTBlock::prime(world, pos).await;
|
||||
TNTBlock::prime(world, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehaviour for FireBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if args.old_state_id == args.state_id {
|
||||
// Already a fire
|
||||
return;
|
||||
}
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
if args.old_state_id == args.state_id {
|
||||
// Already a fire
|
||||
return;
|
||||
}
|
||||
|
||||
let dimension = &args.world.dimension;
|
||||
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
|
||||
if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
|
||||
&& let Some(portal) =
|
||||
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X)
|
||||
{
|
||||
portal.create(args.world).await;
|
||||
return;
|
||||
}
|
||||
let dimension = &args.world.dimension;
|
||||
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
|
||||
if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
|
||||
&& let Some(portal) =
|
||||
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X)
|
||||
{
|
||||
portal.create(args.world);
|
||||
return;
|
||||
}
|
||||
|
||||
args.world.schedule_block_tick(
|
||||
args.block,
|
||||
*args.position,
|
||||
Self::get_fire_tick_delay() as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
})
|
||||
args.world.schedule_block_tick(
|
||||
args.block,
|
||||
*args.position,
|
||||
Self::get_fire_tick_delay() as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
FireBlockBase::apply_fire_collision(args, false)
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
FireBlockBase::apply_fire_collision(&args, false);
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if self.can_place_at(CanPlaceAtArgs {
|
||||
server: None,
|
||||
world: Some(args.world),
|
||||
block_accessor: args.world,
|
||||
block: &Block::FIRE,
|
||||
state: Block::FIRE.default_state,
|
||||
position: args.position,
|
||||
direction: None,
|
||||
player: None,
|
||||
use_item_on: None,
|
||||
}) {
|
||||
let old_fire_props = FireProperties::from_state_id(args.state_id, &Block::FIRE);
|
||||
let fire_state_id =
|
||||
self.get_state_for_position(args.world, &Block::FIRE, args.position);
|
||||
let mut fire_props = FireProperties::from_state_id(fire_state_id, &Block::FIRE);
|
||||
fire_props.age = old_fire_props.age;
|
||||
return fire_props.to_state_id(&Block::FIRE);
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if self.can_place_at(CanPlaceAtArgs {
|
||||
server: None,
|
||||
world: Some(args.world),
|
||||
block_accessor: args.world,
|
||||
block: &Block::FIRE,
|
||||
state: Block::FIRE.default_state,
|
||||
position: args.position,
|
||||
direction: None,
|
||||
player: None,
|
||||
use_item_on: None,
|
||||
}) {
|
||||
self.get_state_for_position(args.world, args.block, args.position)
|
||||
} else {
|
||||
Block::AIR.default_state.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -259,247 +236,223 @@ impl BlockBehaviour for FireBlock {
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let (world, block, pos) = (args.world, args.block, args.position);
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let (world, block, pos) = (args.world, args.block, args.position);
|
||||
|
||||
// Schedule next tick first
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
*pos,
|
||||
Self::get_fire_tick_delay() as u8,
|
||||
TickPriority::Normal,
|
||||
// Schedule next tick first
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
*pos,
|
||||
Self::get_fire_tick_delay() as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
|
||||
// Check if fire can survive
|
||||
if !self.can_place_at(CanPlaceAtArgs {
|
||||
server: None,
|
||||
world: Some(world),
|
||||
block_accessor: world.as_ref(),
|
||||
block,
|
||||
state: block.default_state,
|
||||
position: pos,
|
||||
direction: None,
|
||||
player: None,
|
||||
use_item_on: None,
|
||||
}) {
|
||||
world.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if fire can survive
|
||||
if !self.can_place_at(CanPlaceAtArgs {
|
||||
server: None,
|
||||
world: Some(world),
|
||||
block_accessor: world.as_ref(),
|
||||
block,
|
||||
state: block.default_state,
|
||||
position: pos,
|
||||
direction: None,
|
||||
player: None,
|
||||
use_item_on: None,
|
||||
}) {
|
||||
world
|
||||
.set_block_state(
|
||||
let block_state = world.get_block_state(pos);
|
||||
let block_below = world.get_block(&pos.down());
|
||||
|
||||
// Check for infiniburn blocks (depending on dimension)
|
||||
let infiniburn = match world.dimension.id {
|
||||
id if id == Dimension::OVERWORLD.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD)
|
||||
}
|
||||
id if id == Dimension::THE_NETHER.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER)
|
||||
}
|
||||
id if id == Dimension::THE_END.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE);
|
||||
let age = fire_props.age;
|
||||
|
||||
// Check if rain should extinguish the fire
|
||||
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
|
||||
let rain_chance = 0.2 + (age as f32) * 0.03;
|
||||
if rand::random::<f32>() < rain_chance {
|
||||
world.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment age
|
||||
let random = (rand::rng().random_range(0..3) / 2) as u8;
|
||||
let new_age = (age + random).min(15);
|
||||
if new_age != age {
|
||||
fire_props.age = new_age;
|
||||
let new_state_id = fire_props.to_state_id(&Block::FIRE);
|
||||
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
|
||||
}
|
||||
|
||||
if !infiniburn {
|
||||
// Check if fire should extinguish due to lack of fuel
|
||||
if !Self::are_blocks_around_flammable(world.as_ref(), pos) {
|
||||
let block_below_state = world.get_block_state(&pos.down());
|
||||
if !block_below_state.is_side_solid(BlockDirection::Up) || new_age > 3 {
|
||||
world.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At max age, fire has a chance to extinguish if not on flammable block
|
||||
if new_age == 15
|
||||
&& rand::rng().random_range(0..4) == 0
|
||||
&& !Self::is_flammable(world.get_block_state(&pos.down()))
|
||||
{
|
||||
world.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let block_state = world.get_block_state(pos);
|
||||
let block_below = world.get_block(&pos.down());
|
||||
// Burn adjacent blocks
|
||||
let extra = if Self::is_increased_burnout_biome(world, pos) {
|
||||
-50 // Increases chance of block being destroyed
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Check for infiniburn blocks (depending on dimension)
|
||||
let infiniburn = match world.dimension.id {
|
||||
id if id == Dimension::OVERWORLD.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_OVERWORLD)
|
||||
}
|
||||
id if id == Dimension::THE_NETHER.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_NETHER)
|
||||
}
|
||||
id if id == Dimension::THE_END.id => {
|
||||
block_below.has_tag(&tag::Block::MINECRAFT_INFINIBURN_END)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::East.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
);
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::West.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
);
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::Down.to_offset()),
|
||||
250 + extra,
|
||||
new_age,
|
||||
);
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::Up.to_offset()),
|
||||
250 + extra,
|
||||
new_age,
|
||||
);
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::North.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
);
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::South.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
);
|
||||
|
||||
let mut fire_props = FireProperties::from_state_id(block_state.id, &Block::FIRE);
|
||||
let age = fire_props.age;
|
||||
// Respect the `fire_spread_radius_around_player` gamerule.
|
||||
// -1 = disabled (allow unlimited spread), 0 = disabled (no spread), >0 = radius in blocks
|
||||
let spread_radius = world
|
||||
.level_info
|
||||
.load()
|
||||
.game_rules
|
||||
.fire_spread_radius_around_player;
|
||||
|
||||
// Check if rain should extinguish the fire
|
||||
if !infiniburn && Self::is_near_rain(world.as_ref(), pos) {
|
||||
let rain_chance = 0.2 + (age as f32) * 0.03;
|
||||
if rand::random::<f32>() < rain_chance {
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Try to spread fire to nearby air blocks
|
||||
let difficulty = world.level_info.load().difficulty as i32;
|
||||
for xx in -1..=1 {
|
||||
for zz in -1..=1 {
|
||||
for yy in -1..=4 {
|
||||
if xx != 0 || yy != 0 || zz != 0 {
|
||||
let offset_pos = pos.offset(Vector3::new(xx, yy, zz));
|
||||
let ignite_odds = self.get_burn_chance(world, &offset_pos);
|
||||
|
||||
// Increment age
|
||||
let random = (rand::rng().random_range(0..3) / 2) as u8;
|
||||
let new_age = (age + random).min(15);
|
||||
if new_age != age {
|
||||
fire_props.age = new_age;
|
||||
let new_state_id = fire_props.to_state_id(&Block::FIRE);
|
||||
world
|
||||
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
|
||||
.await;
|
||||
}
|
||||
|
||||
if !infiniburn {
|
||||
// Check if fire should extinguish due to lack of fuel
|
||||
if !Self::are_blocks_around_flammable(world.as_ref(), pos) {
|
||||
let block_below_state = world.get_block_state(&pos.down());
|
||||
if !block_below_state.is_side_solid(BlockDirection::Up) || new_age > 3 {
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At max age, fire has a chance to extinguish if not on flammable block
|
||||
if new_age == 15
|
||||
&& rand::rng().random_range(0..4) == 0
|
||||
&& !Self::is_flammable(world.get_block_state(&pos.down()))
|
||||
{
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Burn adjacent blocks
|
||||
let extra = if Self::is_increased_burnout_biome(world, pos) {
|
||||
-50 // Increases chance of block being destroyed
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::East.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::West.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::Down.to_offset()),
|
||||
250 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::Up.to_offset()),
|
||||
250 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::North.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
self.try_spreading_fire(
|
||||
world,
|
||||
&pos.offset(BlockDirection::South.to_offset()),
|
||||
300 + extra,
|
||||
new_age,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Respect the `fire_spread_radius_around_player` gamerule.
|
||||
// -1 = disabled (allow unlimited spread), 0 = disabled (no spread), >0 = radius in blocks
|
||||
let spread_radius = world
|
||||
.level_info
|
||||
.load()
|
||||
.game_rules
|
||||
.fire_spread_radius_around_player;
|
||||
|
||||
// Try to spread fire to nearby air blocks
|
||||
let difficulty = world.level_info.load().difficulty as i32;
|
||||
for xx in -1..=1 {
|
||||
for zz in -1..=1 {
|
||||
for yy in -1..=4 {
|
||||
if xx != 0 || yy != 0 || zz != 0 {
|
||||
let offset_pos = pos.offset(Vector3::new(xx, yy, zz));
|
||||
let ignite_odds = self.get_burn_chance(world, &offset_pos);
|
||||
|
||||
if ignite_odds > 0 {
|
||||
// Skip if spreding is disabled or if there are no players nearby
|
||||
if spread_radius == 0 {
|
||||
if ignite_odds > 0 {
|
||||
// Skip if spreding is disabled or if there are no players nearby
|
||||
if spread_radius == 0 {
|
||||
continue;
|
||||
}
|
||||
if spread_radius != -1 {
|
||||
let center = offset_pos.to_centered_f64();
|
||||
if world
|
||||
.get_closest_player(center, spread_radius as f64)
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if spread_radius != -1 {
|
||||
let center = offset_pos.to_centered_f64();
|
||||
if world
|
||||
.get_closest_player(center, spread_radius as f64)
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate spread rate based on height
|
||||
let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 };
|
||||
// Calculate spread rate based on height
|
||||
let rate = if yy > 1 { 100 + (yy - 1) * 100 } else { 100 };
|
||||
|
||||
// Calculate odds of spreading
|
||||
let mut odds =
|
||||
(ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30);
|
||||
// Calculate odds of spreading
|
||||
let mut odds =
|
||||
(ignite_odds + 40 + difficulty * 7) / (new_age as i32 + 30);
|
||||
|
||||
// Reduce spread odds in certain biomes
|
||||
if Self::is_increased_burnout_biome(world, &offset_pos) {
|
||||
odds /= 2; // Fire spreads 50% slower
|
||||
}
|
||||
// Reduce spread odds in certain biomes
|
||||
if Self::is_increased_burnout_biome(world, &offset_pos) {
|
||||
odds /= 2; // Fire spreads 50% slower
|
||||
}
|
||||
|
||||
if odds > 0
|
||||
&& rand::rng().random_range(0..rate) <= odds
|
||||
&& !Self::is_near_rain(world.as_ref(), &offset_pos)
|
||||
{
|
||||
let spread_age = (new_age + rand::rng().random_range(0..5) / 4)
|
||||
.min(15)
|
||||
as u8;
|
||||
let fire_state_id = self.get_state_for_position(
|
||||
world.as_ref(),
|
||||
block,
|
||||
&offset_pos,
|
||||
);
|
||||
let mut new_fire_props =
|
||||
FireProperties::from_state_id(fire_state_id, &Block::FIRE);
|
||||
new_fire_props.age = spread_age;
|
||||
if odds > 0
|
||||
&& rand::rng().random_range(0..rate) <= odds
|
||||
&& !Self::is_near_rain(world.as_ref(), &offset_pos)
|
||||
{
|
||||
let spread_age =
|
||||
(new_age + rand::rng().random_range(0..5) / 4).min(15) as u8;
|
||||
let fire_state_id =
|
||||
self.get_state_for_position(world.as_ref(), block, &offset_pos);
|
||||
let mut new_fire_props =
|
||||
FireProperties::from_state_id(fire_state_id, &Block::FIRE);
|
||||
new_fire_props.age = spread_age;
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
&offset_pos,
|
||||
new_fire_props.to_state_id(&Block::FIRE),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
world.set_block_state(
|
||||
&offset_pos,
|
||||
new_fire_props.to_state_id(&Block::FIRE),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
FireBlockBase::broken(args.world, *args.position);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use rand::RngExt;
|
||||
use soul_fire::SoulFireBlock;
|
||||
|
||||
use crate::block::blocks::fire::fire::FireBlock;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, OnEntityCollisionArgs};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, OnEntityCollisionArgs};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::world::World;
|
||||
use crate::world::portal::nether::NetherPortal;
|
||||
@@ -117,46 +117,34 @@ impl FireBlockBase {
|
||||
}
|
||||
|
||||
/// Shared fire collision behavior used by `fire` and `soul_fire`.
|
||||
#[must_use]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn apply_fire_collision(
|
||||
args: OnEntityCollisionArgs<'_>,
|
||||
extra_damage_for_living: bool,
|
||||
) -> BlockFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let base_entity = args.entity.get_entity();
|
||||
if !base_entity.entity_type.fire_immune
|
||||
&& !base_entity.fire_immune.load(Ordering::Relaxed)
|
||||
{
|
||||
let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
|
||||
pub fn apply_fire_collision(args: &OnEntityCollisionArgs<'_>, extra_damage_for_living: bool) {
|
||||
let base_entity = args.entity.get_entity();
|
||||
if !base_entity.entity_type.fire_immune && !base_entity.fire_immune.load(Ordering::Relaxed)
|
||||
{
|
||||
let ticks = base_entity.fire_ticks.load(Ordering::Relaxed);
|
||||
|
||||
// Timer logic
|
||||
if ticks < 0 {
|
||||
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
|
||||
} else if base_entity.entity_type == &EntityType::PLAYER {
|
||||
let rnd_ticks = rand::rng().random_range(1..3);
|
||||
base_entity
|
||||
.fire_ticks
|
||||
.store(ticks + rnd_ticks, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// Apply fire ticks
|
||||
if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 {
|
||||
args.entity.set_on_fire_for(8.0);
|
||||
}
|
||||
|
||||
// Regular fire vs soul fire damage
|
||||
if extra_damage_for_living {
|
||||
base_entity
|
||||
.damage(args.entity, 2.0, DamageType::IN_FIRE)
|
||||
.await;
|
||||
} else {
|
||||
base_entity
|
||||
.damage(args.entity, 1.0, DamageType::IN_FIRE)
|
||||
.await;
|
||||
}
|
||||
// Timer logic
|
||||
if ticks < 0 {
|
||||
base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed);
|
||||
} else if base_entity.entity_type == &EntityType::PLAYER {
|
||||
let rnd_ticks = rand::rng().random_range(1..3);
|
||||
base_entity
|
||||
.fire_ticks
|
||||
.store(ticks + rnd_ticks, Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
|
||||
// Apply fire ticks
|
||||
if base_entity.fire_ticks.load(Ordering::Relaxed) >= 0 {
|
||||
args.entity.set_on_fire_for(8.0);
|
||||
}
|
||||
|
||||
// Regular fire vs soul fire damage
|
||||
if extra_damage_for_living {
|
||||
base_entity.damage(args.entity, 2.0, DamageType::IN_FIRE);
|
||||
} else {
|
||||
base_entity.damage(args.entity, 1.0, DamageType::IN_FIRE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn broken(world: &World, block_pos: BlockPos) {
|
||||
|
||||
@@ -3,9 +3,7 @@ use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::{Block, tag};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
|
||||
|
||||
use super::FireBlockBase;
|
||||
use crate::block::OnEntityCollisionArgs;
|
||||
@@ -21,30 +19,28 @@ impl SoulFireBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for SoulFireBlock {
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
FireBlockBase::apply_fire_collision(args, true)
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
FireBlockBase::apply_fire_collision(&args, true);
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !Self::is_soul_base(args.world.get_block(&args.position.down())) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !Self::is_soul_base(args.world.get_block(&args.position.down())) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
|
||||
args.state_id
|
||||
})
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
Self::is_soul_base(args.block_accessor.get_block(&args.position.down()))
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
FireBlockBase::broken(args.world, *args.position);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs};
|
||||
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_macros::pumpkin_block;
|
||||
pub struct FletchingTableBlock;
|
||||
|
||||
impl BlockBehaviour for FletchingTableBlock {
|
||||
fn normal_use<'a>(&'a self, _args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move { BlockActionResult::Pass })
|
||||
fn normal_use(&self, _args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
BlockActionResult::Pass
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, RandomTickArgs, UseWithItemArgs};
|
||||
use crate::block::{BlockBehaviour, RandomTickArgs, UseWithItemArgs};
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::flower_pot_transformations::get_potted_item;
|
||||
use pumpkin_data::{Block, BlockId};
|
||||
@@ -10,23 +10,18 @@ use pumpkin_world::world::BlockFlags;
|
||||
pub struct FlowerPotBlock;
|
||||
|
||||
impl BlockBehaviour for FlowerPotBlock {
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
let item = args.item_stack.item;
|
||||
//Place the flower inside the pot
|
||||
let potted_block_id = get_potted_item(item.id);
|
||||
if args.block.eq(&Block::FLOWER_POT) {
|
||||
if potted_block_id != BlockId::AIR {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::from_id(potted_block_id).default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::from_id(potted_block_id).default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
return BlockActionResult::Success;
|
||||
} else if potted_block_id != BlockId::AIR {
|
||||
@@ -35,43 +30,35 @@ impl BlockBehaviour for FlowerPotBlock {
|
||||
}
|
||||
|
||||
//get the flower + empty the pot
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::FLOWER_POT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::FLOWER_POT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|
||||
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
|
||||
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
|
||||
&& args.world.level_time.lock().await.time_of_day % 24000 > 14500
|
||||
{
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM)
|
||||
&& args.world.level_time.lock().await.time_of_day % 24000 <= 14500
|
||||
{
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if (args.world.dimension.eq(&Dimension::OVERWORLD)
|
||||
|| args.world.dimension.eq(&Dimension::OVERWORLD_CAVES))
|
||||
&& args.block.eq(&Block::POTTED_CLOSED_EYEBLOSSOM)
|
||||
&& args.world.get_time_of_day() % 24000 > 14500
|
||||
{
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::POTTED_OPEN_EYEBLOSSOM.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
if args.block.eq(&Block::POTTED_OPEN_EYEBLOSSOM)
|
||||
&& args.world.get_time_of_day() % 24000 <= 14500
|
||||
{
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::POTTED_CLOSED_EYEBLOSSOM.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs,
|
||||
OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
|
||||
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
|
||||
PlacedArgs, registry::BlockActionResult,
|
||||
},
|
||||
entity::experience_orb::ExperienceOrbEntity,
|
||||
};
|
||||
@@ -82,79 +82,70 @@ impl ScreenHandlerFactory for FurnaceScreenFactory {
|
||||
pub struct FurnaceBlock;
|
||||
|
||||
impl BlockBehaviour for FurnaceBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
let furnace_screen_factory =
|
||||
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
|
||||
args.player
|
||||
.open_handled_screen(&furnace_screen_factory, Some(*args.position))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.clone().get_inventory()
|
||||
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
|
||||
1,
|
||||
);
|
||||
let furnace_screen_factory =
|
||||
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&furnace_screen_factory, Some(pos))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
crate::block::registry::BlockActionResult::Consume
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = FurnaceLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let furnace_block_entity = FurnaceBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(furnace_block_entity));
|
||||
}
|
||||
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
// Extract and drop accumulated XP as orbs before removing the block entity
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
let xp = experience_container.extract_experience();
|
||||
if xp > 0 {
|
||||
let pos = args.position.to_f64();
|
||||
ExperienceOrbEntity::spawn(args.world, pos, xp as u32);
|
||||
}
|
||||
crate::block::registry::BlockActionResult::Consume
|
||||
})
|
||||
}
|
||||
args.world.remove_block_entity(args.position);
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = FurnaceLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let furnace_block_entity = FurnaceBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(furnace_block_entity));
|
||||
})
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Extract and drop accumulated XP as orbs before removing the block entity
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(experience_container) = block_entity.to_experience_container()
|
||||
{
|
||||
let xp = experience_container.extract_experience();
|
||||
if xp > 0 {
|
||||
let pos = args.position.to_f64();
|
||||
ExperienceOrbEntity::spawn(args.world, pos, xp as u32).await;
|
||||
}
|
||||
}
|
||||
args.world.remove_block_entity(args.position);
|
||||
})
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
inventory.as_ref(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
@@ -20,23 +19,19 @@ use crate::world::World;
|
||||
pub struct GlassPaneBlock;
|
||||
|
||||
impl BlockBehaviour for GlassPaneBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut pane_props = GlassPaneProperties::default(args.block);
|
||||
pane_props.waterlogged = args.replacing.water_source();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut pane_props = GlassPaneProperties::default(args.block);
|
||||
pane_props.waterlogged = args.replacing.water_source();
|
||||
|
||||
compute_pane_state(pane_props, args.world, args.block, args.position)
|
||||
})
|
||||
compute_pane_state(pane_props, args.world, args.block, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block);
|
||||
compute_pane_state(pane_props, args.world, args.block, args.position)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let pane_props = GlassPaneProperties::from_state_id(args.state_id, args.block);
|
||||
compute_pane_state(pane_props, args.world, args.block, args.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs};
|
||||
use crate::block::{BlockBehaviour, OnPlaceArgs};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
|
||||
use pumpkin_macros::pumpkin_block_from_tag;
|
||||
@@ -7,16 +7,14 @@ use pumpkin_macros::pumpkin_block_from_tag;
|
||||
pub struct GlazedTerracottaBlock;
|
||||
|
||||
impl BlockBehaviour for GlazedTerracottaBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut prop = WallTorchLikeProperties::default(args.block);
|
||||
prop.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
prop.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut prop = WallTorchLikeProperties::default(args.block);
|
||||
prop.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
prop.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use pumpkin_world::tick::TickPriority;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs};
|
||||
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:grass_block")]
|
||||
pub struct GrassBlock;
|
||||
@@ -30,139 +30,116 @@ impl BlockBehaviour for GrassBlock {
|
||||
&& args.world.get_block_state(&above).is_air()
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
const SPREAD_ATTEMPTS: i32 = 128;
|
||||
const ATTEMPTS_PER_STEP: i32 = 16;
|
||||
const FLOWER_CHANCE: i32 = 8;
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
const SPREAD_ATTEMPTS: i32 = 128;
|
||||
const ATTEMPTS_PER_STEP: i32 = 16;
|
||||
const FLOWER_CHANCE: i32 = 8;
|
||||
|
||||
let origin = args.position.up();
|
||||
for attempt in 0..SPREAD_ATTEMPTS {
|
||||
let mut target = origin;
|
||||
let mut valid = true;
|
||||
for _ in 0..attempt / ATTEMPTS_PER_STEP {
|
||||
let offset_x = rand::rng().random_range(0..3) - 1;
|
||||
let offset_y =
|
||||
((rand::rng().random_range(0..3) - 1) * rand::rng().random_range(0..3)) / 2;
|
||||
let offset_z = rand::rng().random_range(0..3) - 1;
|
||||
target = BlockPos::new(
|
||||
target.0.x + offset_x,
|
||||
target.0.y + offset_y,
|
||||
target.0.z + offset_z,
|
||||
);
|
||||
let origin = args.position.up();
|
||||
for attempt in 0..SPREAD_ATTEMPTS {
|
||||
let mut target = origin;
|
||||
let mut valid = true;
|
||||
for _ in 0..attempt / ATTEMPTS_PER_STEP {
|
||||
let offset_x = rand::rng().random_range(0..3) - 1;
|
||||
let offset_y =
|
||||
((rand::rng().random_range(0..3) - 1) * rand::rng().random_range(0..3)) / 2;
|
||||
let offset_z = rand::rng().random_range(0..3) - 1;
|
||||
target = BlockPos::new(
|
||||
target.0.x + offset_x,
|
||||
target.0.y + offset_y,
|
||||
target.0.z + offset_z,
|
||||
);
|
||||
|
||||
if !args.world.is_loaded(&target)
|
||||
|| args.world.get_block(&target.down()) != args.block
|
||||
|| args.world.get_block_state(&target).is_full_cube()
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if !args.world.is_loaded(&target)
|
||||
|| args.world.get_block(&target.down()) != args.block
|
||||
|| args.world.get_block_state(&target).is_full_cube()
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
if !valid {
|
||||
continue;
|
||||
}
|
||||
let target_state = args.world.get_block_state(&target);
|
||||
if Block::from_state_id(target_state.id) == &Block::SHORT_GRASS
|
||||
&& rand::rng().random_range(0..10) == 0
|
||||
{
|
||||
let above = target.up();
|
||||
if args.world.is_in_height_limit(above.0.y)
|
||||
&& args.world.is_loaded(&above)
|
||||
&& args.world.get_block_state(&above).is_air()
|
||||
{
|
||||
place_tall_grass(args.world, target);
|
||||
}
|
||||
} else if target_state.is_air() && args.world.is_in_height_limit(target.0.y) {
|
||||
let selected = if rand::rng().random_range(0..FLOWER_CHANCE) == 0 {
|
||||
biome_bonemeal_state(args.world, target)
|
||||
} else {
|
||||
Some((Block::SHORT_GRASS.default_state, false))
|
||||
};
|
||||
let Some((state, schedule_tick)) = selected else {
|
||||
continue;
|
||||
};
|
||||
let placed_block = Block::from_state_id(state.id);
|
||||
if !args.world.block_registry.can_place_at(
|
||||
None,
|
||||
Some(args.world),
|
||||
args.world.as_ref(),
|
||||
None,
|
||||
placed_block,
|
||||
state,
|
||||
&target,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let target_state = args.world.get_block_state(&target);
|
||||
if Block::from_state_id(target_state.id) == &Block::SHORT_GRASS
|
||||
&& rand::rng().random_range(0..10) == 0
|
||||
{
|
||||
let above = target.up();
|
||||
if args.world.is_in_height_limit(above.0.y)
|
||||
&& args.world.is_loaded(&above)
|
||||
&& args.world.get_block_state(&above).is_air()
|
||||
{
|
||||
place_tall_grass(args.world, target).await;
|
||||
}
|
||||
} else if target_state.is_air() && args.world.is_in_height_limit(target.0.y) {
|
||||
let selected = if rand::rng().random_range(0..FLOWER_CHANCE) == 0 {
|
||||
biome_bonemeal_state(args.world, target)
|
||||
} else {
|
||||
Some((Block::SHORT_GRASS.default_state, false))
|
||||
};
|
||||
let Some((state, schedule_tick)) = selected else {
|
||||
continue;
|
||||
};
|
||||
let placed_block = Block::from_state_id(state.id);
|
||||
if !args.world.block_registry.can_place_at(
|
||||
None,
|
||||
Some(args.world),
|
||||
args.world.as_ref(),
|
||||
None,
|
||||
placed_block,
|
||||
state,
|
||||
&target,
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if placed_block == &Block::TALL_GRASS
|
||||
&& (!args.world.is_loaded(&target.up())
|
||||
|| !args.world.get_block_state(&target.up()).is_air())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
args.world
|
||||
.set_block_state(&target, state.id, BlockFlags::NOTIFY_LISTENERS);
|
||||
if schedule_tick {
|
||||
args.world
|
||||
.set_block_state(&target, state.id, BlockFlags::NOTIFY_LISTENERS)
|
||||
.await;
|
||||
if schedule_tick {
|
||||
args.world.schedule_block_tick(
|
||||
placed_block,
|
||||
target,
|
||||
1,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
if placed_block == &Block::TALL_GRASS {
|
||||
place_tall_grass_upper(args.world, target, state.id).await;
|
||||
}
|
||||
.schedule_block_tick(placed_block, target, 1, TickPriority::Normal);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let block_above = args.world.get_block(&args.position.up());
|
||||
let mut props =
|
||||
GrassBlockLikeProperties::from_state_id(args.state_id, &Block::GRASS_BLOCK);
|
||||
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
|
||||
if props.snowy == should_be_snowy {
|
||||
return args.state_id;
|
||||
}
|
||||
props.snowy = should_be_snowy;
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let block_above = args.world.get_block(&args.position.up());
|
||||
let mut props = GrassBlockLikeProperties::from_state_id(args.state_id, &Block::GRASS_BLOCK);
|
||||
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
|
||||
if props.snowy == should_be_snowy {
|
||||
return args.state_id;
|
||||
}
|
||||
props.snowy = should_be_snowy;
|
||||
|
||||
props.to_state_id(&Block::GRASS_BLOCK)
|
||||
})
|
||||
props.to_state_id(&Block::GRASS_BLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
async fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) {
|
||||
fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) {
|
||||
let state = Block::TALL_GRASS.default_state.id;
|
||||
world
|
||||
.set_block_state(&position, state, BlockFlags::NOTIFY_LISTENERS)
|
||||
.await;
|
||||
place_tall_grass_upper(world, position, state).await;
|
||||
world.set_block_state(&position, state, BlockFlags::NOTIFY_LISTENERS);
|
||||
place_tall_grass_upper(world, position, state);
|
||||
}
|
||||
|
||||
async fn place_tall_grass_upper(
|
||||
fn place_tall_grass_upper(
|
||||
world: &std::sync::Arc<crate::world::World>,
|
||||
position: BlockPos,
|
||||
lower_state: BlockStateId,
|
||||
) {
|
||||
let mut props = TallSeagrassLikeProperties::from_state_id(lower_state, &Block::TALL_GRASS);
|
||||
props.half = DoubleBlockHalf::Upper;
|
||||
world
|
||||
.set_block_state(
|
||||
&position.up(),
|
||||
props.to_state_id(&Block::TALL_GRASS),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&position.up(),
|
||||
props.to_state_id(&Block::TALL_GRASS),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
|
||||
fn biome_bonemeal_state(
|
||||
|
||||
@@ -6,8 +6,8 @@ use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockAccessor;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::CanPlaceAtArgs;
|
||||
use crate::block::{BlockBehaviour, BlockFuture};
|
||||
use crate::block::{GetStateForNeighborUpdateArgs, OnPlaceArgs};
|
||||
|
||||
use super::abstract_wall_mounting::WallMountedBlock;
|
||||
@@ -16,15 +16,13 @@ use super::abstract_wall_mounting::WallMountedBlock;
|
||||
pub struct GrindstoneBlock;
|
||||
|
||||
impl BlockBehaviour for GrindstoneBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
GrindstoneLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
(props.face, props.facing) =
|
||||
WallMountedBlock::get_placement_face(self, args.player, args.direction);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
GrindstoneLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
(props.face, props.facing) =
|
||||
WallMountedBlock::get_placement_face(self, args.player, args.direction);
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -36,19 +34,19 @@ impl BlockBehaviour for GrindstoneBlock {
|
||||
WallMountedBlock::can_place_at(self, args.block_accessor, args.position, direction)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move { WallMountedBlock::get_state_for_neighbor_update(self, args).await })
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
WallMountedBlock::get_state_for_neighbor_update(self, args)
|
||||
}
|
||||
}
|
||||
|
||||
impl WallMountedBlock for GrindstoneBlock {
|
||||
fn can_place_at<'a>(
|
||||
&'a self,
|
||||
_world: &'a dyn BlockAccessor,
|
||||
_pos: &'a BlockPos,
|
||||
fn can_place_at(
|
||||
&self,
|
||||
_world: &dyn BlockAccessor,
|
||||
_pos: &BlockPos,
|
||||
_direction: BlockDirection,
|
||||
) -> bool {
|
||||
true
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::block::{BlockBehaviour, BlockFuture, OnLandedUponArgs};
|
||||
use crate::block::{BlockBehaviour, OnLandedUponArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:hay_block")]
|
||||
pub struct HayBlock;
|
||||
|
||||
impl BlockBehaviour for HayBlock {
|
||||
fn on_landed_upon<'a>(&'a self, args: OnLandedUponArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(living) = args.entity.get_living_entity() {
|
||||
living
|
||||
.handle_fall_damage(args.entity, args.fall_distance, 0.2)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_landed_upon(&self, args: OnLandedUponArgs<'_>) {
|
||||
if let Some(living) = args.entity.get_living_entity() {
|
||||
living.handle_fall_damage(args.entity, args.fall_distance, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::block::blocks::redstone::block_receives_redstone_power;
|
||||
use crate::block::{
|
||||
BlockFuture, GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs,
|
||||
};
|
||||
use crate::block::{GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs};
|
||||
use crate::block::{
|
||||
registry::BlockActionResult,
|
||||
{BlockBehaviour, NormalUseArgs},
|
||||
@@ -58,93 +56,76 @@ pub struct HopperBlock;
|
||||
type HopperLikeProperties = pumpkin_data::block_properties::HopperLikeProperties;
|
||||
|
||||
impl BlockBehaviour for HopperBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
|
||||
1,
|
||||
)
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(pos))
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(*args.position))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = HopperLikeProperties::default(args.block);
|
||||
props.facing = match args.direction {
|
||||
BlockDirection::North => FacingHopper::North,
|
||||
BlockDirection::East => FacingHopper::East,
|
||||
BlockDirection::South => FacingHopper::South,
|
||||
BlockDirection::West => FacingHopper::West,
|
||||
BlockDirection::Up | BlockDirection::Down => FacingHopper::Down,
|
||||
};
|
||||
props.enabled = true;
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = HopperLikeProperties::default(args.block);
|
||||
props.facing = match args.direction {
|
||||
BlockDirection::North => FacingHopper::North,
|
||||
BlockDirection::East => FacingHopper::East,
|
||||
BlockDirection::South => FacingHopper::South,
|
||||
BlockDirection::West => FacingHopper::West,
|
||||
BlockDirection::Up | BlockDirection::Down => FacingHopper::Down,
|
||||
};
|
||||
props.enabled = true;
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let props = HopperLikeProperties::from_state_id(args.state_id, args.block);
|
||||
let hopper_block_entity = HopperBlockEntity::new(*args.position, props.facing);
|
||||
args.world.add_block_entity(Arc::new(hopper_block_entity));
|
||||
if Block::from_state_id(args.old_state_id) != Block::from_state_id(args.state_id) {
|
||||
check_powered_state(args.world, args.position, args.state_id, args.block).await;
|
||||
}
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let props = HopperLikeProperties::from_state_id(args.state_id, args.block);
|
||||
let hopper_block_entity = HopperBlockEntity::new(*args.position, props.facing);
|
||||
args.world.add_block_entity(Arc::new(hopper_block_entity));
|
||||
if Block::from_state_id(args.old_state_id) != Block::from_state_id(args.state_id) {
|
||||
check_powered_state(args.world, args.position, args.state_id, args.block);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
check_powered_state(
|
||||
args.world,
|
||||
args.position,
|
||||
args.world.get_block_state_id(args.position),
|
||||
args.block,
|
||||
)
|
||||
.await;
|
||||
})
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
check_powered_state(
|
||||
args.world,
|
||||
args.position,
|
||||
args.world.get_block_state_id(args.position),
|
||||
args.block,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(inventory.as_ref()).await)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(inventory) = block_entity.get_inventory()
|
||||
{
|
||||
Some(crate::block::calculate_comparator_output(
|
||||
inventory.as_ref(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_powered_state(
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
state_id: BlockStateId,
|
||||
block: &Block,
|
||||
) {
|
||||
let signal = !block_receives_redstone_power(world, pos).await;
|
||||
fn check_powered_state(world: &Arc<World>, pos: &BlockPos, state_id: BlockStateId, block: &Block) {
|
||||
let signal = !block_receives_redstone_power(world, pos);
|
||||
let mut state = HopperLikeProperties::from_state_id(state_id, block);
|
||||
if signal != state.enabled {
|
||||
state.enabled = signal;
|
||||
world
|
||||
.set_block_state(pos, state.to_state_id(block), BlockFlags::NOTIFY_LISTENERS)
|
||||
.await;
|
||||
world.set_block_state(pos, state.to_state_id(block), BlockFlags::NOTIFY_LISTENERS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,21 @@ use pumpkin_world::world::BlockFlags;
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs,
|
||||
OnScheduledTickArgs, PlacedArgs, RandomTickArgs,
|
||||
BlockBehaviour, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
PlacedArgs, RandomTickArgs,
|
||||
};
|
||||
use crate::world::World;
|
||||
|
||||
/// Melts ice at the given position into water (or removes it in ultrawarm dimensions like the Nether).
|
||||
pub async fn melt(world: &Arc<World>, position: &BlockPos) {
|
||||
pub fn melt(world: &Arc<World>, position: &BlockPos) {
|
||||
if world.dimension == Dimension::THE_NETHER {
|
||||
world
|
||||
.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL);
|
||||
} else {
|
||||
world
|
||||
.set_block_state(
|
||||
position,
|
||||
Block::WATER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.update_neighbors(position, None).await;
|
||||
world.set_block_state(
|
||||
position,
|
||||
Block::WATER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +42,14 @@ pub fn fewer_neighbors_than(world: &World, pos: &BlockPos, limit: usize) -> bool
|
||||
true
|
||||
}
|
||||
|
||||
/// Slightly melts the frosted ice at `pos`.
|
||||
///
|
||||
/// Increments its age if `age < 3`, or completely melts it if `age >= 3`.
|
||||
/// Returns `true` if it completely melted, or `false` if it just aged.
|
||||
async fn slightly_melt(world: &Arc<World>, pos: &BlockPos, block: &Block, age: u8) -> bool {
|
||||
fn slightly_melt(world: &Arc<World>, pos: &BlockPos, block: &Block, age: u8) -> bool {
|
||||
if age < 3 {
|
||||
let mut new_props = NetherWartLikeProperties::default(block);
|
||||
new_props.r#age = age + 1;
|
||||
world
|
||||
.set_block_state(pos, new_props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(pos, new_props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
false
|
||||
} else {
|
||||
melt(world, pos).await;
|
||||
melt(world, pos);
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -74,15 +63,17 @@ impl BlockMetadata for IceBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for IceBlock {
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let held_item = args.player.inventory().held_item().await;
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
let held_item = args.player.inventory().held_item();
|
||||
let has_silk_touch = held_item.get_enchantment_level(&Enchantment::SILK_TOUCH) > 0;
|
||||
if !has_silk_touch {
|
||||
if args.world.dimension == Dimension::THE_NETHER {
|
||||
args.world
|
||||
.set_block_state(args.position, BlockStateId::AIR, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
BlockStateId::AIR,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,26 +84,22 @@ impl BlockBehaviour for IceBlock {
|
||||
|| below_state.is_liquid()
|
||||
|| below_state.is_solid()
|
||||
{
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::WATER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::WATER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let block_light = args.world.get_block_light_level(args.position).unwrap_or(0);
|
||||
if block_light > (11u8.saturating_sub(state.opacity)) {
|
||||
melt(args.world, args.position).await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let block_light = args.world.get_block_light_level(args.position).unwrap_or(0);
|
||||
if block_light > (11u8.saturating_sub(state.opacity)) {
|
||||
melt(args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,85 +112,75 @@ impl BlockMetadata for FrostedIceBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for FrostedIceBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let delay = rand::rng().random_range(60..=120);
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let should_check_melt = rand::rng().random_range(0..3) == 0
|
||||
|| fewer_neighbors_than(args.world, args.position, 4);
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let should_check_melt = rand::rng().random_range(0..3) == 0
|
||||
|| fewer_neighbors_than(args.world, args.position, 4);
|
||||
|
||||
if should_check_melt {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
let age = props.r#age;
|
||||
if should_check_melt {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
let age = props.r#age;
|
||||
|
||||
let brightness = if args.world.dimension == Dimension::THE_END {
|
||||
args.world.get_block_light_level(args.position).unwrap_or(0)
|
||||
} else {
|
||||
args.world.get_max_local_raw_brightness(args.position)
|
||||
};
|
||||
let brightness = if args.world.dimension == Dimension::THE_END {
|
||||
args.world.get_block_light_level(args.position).unwrap_or(0)
|
||||
} else {
|
||||
args.world.get_max_local_raw_brightness(args.position)
|
||||
};
|
||||
|
||||
let threshold = 11u8.saturating_sub(age).saturating_sub(state.opacity);
|
||||
if brightness > threshold
|
||||
&& slightly_melt(args.world, args.position, args.block, age).await
|
||||
{
|
||||
for dir in BlockDirection::all() {
|
||||
let neighbor_pos = args.position.offset(dir.to_offset());
|
||||
let (neighbor_block, neighbor_state_id) =
|
||||
args.world.get_block_and_state_id(&neighbor_pos);
|
||||
if neighbor_block == &Block::FROSTED_ICE {
|
||||
let neighbor_props = NetherWartLikeProperties::from_state_id(
|
||||
neighbor_state_id,
|
||||
let threshold = 11u8.saturating_sub(age).saturating_sub(state.opacity);
|
||||
if brightness > threshold && slightly_melt(args.world, args.position, args.block, age) {
|
||||
for dir in BlockDirection::all() {
|
||||
let neighbor_pos = args.position.offset(dir.to_offset());
|
||||
let (neighbor_block, neighbor_state_id) =
|
||||
args.world.get_block_and_state_id(&neighbor_pos);
|
||||
if neighbor_block == &Block::FROSTED_ICE {
|
||||
let neighbor_props = NetherWartLikeProperties::from_state_id(
|
||||
neighbor_state_id,
|
||||
neighbor_block,
|
||||
);
|
||||
if !slightly_melt(
|
||||
args.world,
|
||||
&neighbor_pos,
|
||||
neighbor_block,
|
||||
neighbor_props.r#age,
|
||||
) {
|
||||
let delay = rand::rng().random_range(20..=40);
|
||||
args.world.schedule_block_tick(
|
||||
neighbor_block,
|
||||
neighbor_pos,
|
||||
delay,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
if !slightly_melt(
|
||||
args.world,
|
||||
&neighbor_pos,
|
||||
neighbor_block,
|
||||
neighbor_props.r#age,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let delay = rand::rng().random_range(20..=40);
|
||||
args.world.schedule_block_tick(
|
||||
neighbor_block,
|
||||
neighbor_pos,
|
||||
delay,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let delay = rand::rng().random_range(20..=40);
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
|
||||
})
|
||||
let delay = rand::rng().random_range(20..=40);
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, delay, TickPriority::Normal);
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if args.source_block == &Block::FROSTED_ICE
|
||||
&& fewer_neighbors_than(args.world, args.position, 2)
|
||||
{
|
||||
melt(args.world, args.position).await;
|
||||
}
|
||||
})
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
if args.source_block == &Block::FROSTED_ICE
|
||||
&& fewer_neighbors_than(args.world, args.position, 2)
|
||||
{
|
||||
melt(args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
IceBlock.broken(args).await;
|
||||
})
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
IceBlock.broken(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_macros::pumpkin_block_from_tag;
|
||||
use pumpkin_util::GameMode;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BrokenArgs;
|
||||
use crate::block::{BlockBehaviour, BlockFuture};
|
||||
use crate::entity::Entity;
|
||||
|
||||
#[pumpkin_block_from_tag("c:cobblestones/infested")]
|
||||
pub struct InfestedBlock;
|
||||
|
||||
impl BlockBehaviour for InfestedBlock {
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
// TODO: ugly fix, use onStacksDropped
|
||||
if args.player.gamemode.load() == GameMode::Creative {
|
||||
return;
|
||||
@@ -24,7 +24,7 @@ impl BlockBehaviour for InfestedBlock {
|
||||
&EntityType::SILVERFISH,
|
||||
);
|
||||
|
||||
args.world.spawn_entity(Arc::new(entity)).await;
|
||||
})
|
||||
args.world.spawn_entity(Arc::new(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::block::BlockFuture;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
@@ -20,23 +19,19 @@ use crate::world::World;
|
||||
pub struct IronBarsBlock;
|
||||
|
||||
impl BlockBehaviour for IronBarsBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut bars_props = IronBarsProperties::default(args.block);
|
||||
bars_props.waterlogged = args.replacing.water_source();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut bars_props = IronBarsProperties::default(args.block);
|
||||
bars_props.waterlogged = args.replacing.water_source();
|
||||
|
||||
compute_bars_state(bars_props, args.world, args.block, args.position)
|
||||
})
|
||||
compute_bars_state(bars_props, args.world, args.block, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block);
|
||||
compute_bars_state(bars_props, args.world, args.block, args.position)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let bars_props = IronBarsProperties::from_state_id(args.state_id, args.block);
|
||||
compute_bars_state(bars_props, args.world, args.block, args.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::block::entities::jigsaw_block::JigsawBlockEntity;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs, PlacedArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs, PlacedArgs};
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::block_properties::{
|
||||
BlockProperties, HorizontalFacing, JigsawLikeProperties, Orientation,
|
||||
@@ -100,24 +100,21 @@ impl JigsawBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for JigsawBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = JigsawLikeProperties::default(args.block);
|
||||
let front = args.direction;
|
||||
let top = if front == BlockDirection::Up || front == BlockDirection::Down {
|
||||
horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing())
|
||||
.opposite()
|
||||
} else {
|
||||
BlockDirection::Up
|
||||
};
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = JigsawLikeProperties::default(args.block);
|
||||
let front = args.direction;
|
||||
let top = if front == BlockDirection::Up || front == BlockDirection::Down {
|
||||
horizontal_facing_to_dir(args.player.get_entity().get_horizontal_facing()).opposite()
|
||||
} else {
|
||||
BlockDirection::Up
|
||||
};
|
||||
|
||||
props.r#orientation = Self::from_front_top(front, top);
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.r#orientation = Self::from_front_top(front, top);
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
{
|
||||
if args.player.permission_lvl.load() < PermissionLvl::Two {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
@@ -129,14 +126,14 @@ impl BlockBehaviour for JigsawBlock {
|
||||
};
|
||||
args.world.update_block_entity(&block_entity);
|
||||
BlockActionResult::SuccessServer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let entity = JigsawBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(entity));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn mirror(
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use crate::block::entities::jukebox::JukeboxBlockEntity;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
|
||||
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
|
||||
GetRedstonePowerArgs, NormalUseArgs, OnStateReplacedArgs, PlacedArgs, UseWithItemArgs,
|
||||
};
|
||||
use crate::entity::Entity;
|
||||
@@ -33,29 +33,22 @@ impl JukeboxBlock {
|
||||
JukeboxLikeProperties::from_state_id(state_id, block).has_record
|
||||
}
|
||||
|
||||
async fn set_record_state(
|
||||
has_record: bool,
|
||||
block: &Block,
|
||||
position: &BlockPos,
|
||||
world: &Arc<World>,
|
||||
) {
|
||||
fn set_record_state(has_record: bool, block: &Block, position: &BlockPos, world: &Arc<World>) {
|
||||
let new_state = JukeboxLikeProperties { has_record };
|
||||
world
|
||||
.set_block_state(
|
||||
position,
|
||||
new_state.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
position,
|
||||
new_state.to_state_id(block),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
|
||||
/// Drops the record from the jukebox - matches vanilla's `JukeboxBlockEntity.dropRecord()`
|
||||
/// Spawns item at (pos + 0.5, pos + 1.01, pos + 0.5) with horizontal random offset
|
||||
async fn drop_record(position: &BlockPos, world: &Arc<World>) {
|
||||
fn drop_record(position: &BlockPos, world: &Arc<World>) {
|
||||
if let Some(block_entity) = world.get_block_entity(position)
|
||||
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
{
|
||||
let record = jukebox_entity.clear_record().await;
|
||||
let record = jukebox_entity.clear_record();
|
||||
if !record.is_empty() {
|
||||
// Vanilla: Vec3d.add(pos, 0.5, 1.01, 0.5).addHorizontalRandom(random, 0.7F)
|
||||
// addHorizontalRandom adds random in range [-0.35, 0.35] to x and z
|
||||
@@ -68,14 +61,14 @@ impl JukeboxBlock {
|
||||
let entity = Entity::new(world.clone(), spawn_pos, &EntityType::ITEM);
|
||||
// Vanilla: setToDefaultPickupDelay() = 10 ticks
|
||||
let item_entity = Arc::new(ItemEntity::new(entity, record));
|
||||
world.spawn_entity(item_entity).await;
|
||||
world.spawn_entity(item_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the music and updates block state
|
||||
async fn stop_playing(block: &Block, position: &BlockPos, world: &Arc<World>) {
|
||||
Self::set_record_state(false, block, position, world).await;
|
||||
fn stop_playing(block: &Block, position: &BlockPos, world: &Arc<World>) {
|
||||
Self::set_record_state(false, block, position, world);
|
||||
world.sync_world_event(WorldEvent::SoundStopJukeboxSong, *position, 0);
|
||||
}
|
||||
|
||||
@@ -87,168 +80,137 @@ impl JukeboxBlock {
|
||||
|
||||
impl BlockBehaviour for JukeboxBlock {
|
||||
/// Called when the jukebox is placed - creates the block entity
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_entity = JukeboxBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let block_entity = JukeboxBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
}
|
||||
|
||||
/// Called when player right-clicks with empty hand or non-disc item
|
||||
/// Vanilla: `JukeboxBlock.onUse()` - drops record if present
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state(args.position).id;
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state(args.position).id;
|
||||
|
||||
// Vanilla: if (state.get(HAS_RECORD) && world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv)
|
||||
if Self::has_record_state(args.block, state_id) {
|
||||
// Drop the record
|
||||
Self::drop_record(args.position, args.world).await;
|
||||
// Stop the music and update block state
|
||||
Self::stop_playing(args.block, args.position, args.world).await;
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
// Vanilla: if (state.get(HAS_RECORD) && world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv)
|
||||
if Self::has_record_state(args.block, state_id) {
|
||||
// Drop the record
|
||||
Self::drop_record(args.position, args.world);
|
||||
// Stop the music and update block state
|
||||
Self::stop_playing(args.block, args.position, args.world);
|
||||
return BlockActionResult::Success;
|
||||
}
|
||||
|
||||
BlockActionResult::Pass
|
||||
})
|
||||
BlockActionResult::Pass
|
||||
}
|
||||
|
||||
/// Called when player right-clicks with an item
|
||||
/// Vanilla: `JukeboxBlock.onUseWithItem()` -> `JukeboxPlayableComponent.tryPlayStack()`
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let world = args.world;
|
||||
let state_id = world.get_block_state(args.position).id;
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let world = args.world;
|
||||
let state_id = world.get_block_state(args.position).id;
|
||||
|
||||
// Vanilla: if (state.get(HAS_RECORD)) return PASS_TO_DEFAULT_BLOCK_ACTION
|
||||
if Self::has_record_state(args.block, state_id) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
// Vanilla: if (state.get(HAS_RECORD)) return PASS_TO_DEFAULT_BLOCK_ACTION
|
||||
if Self::has_record_state(args.block, state_id) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let item_stack = &mut *args.item_stack;
|
||||
let item_stack = &mut *args.item_stack;
|
||||
|
||||
// Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
|
||||
let jukebox_playable = item_stack
|
||||
.get_data_component::<JukeboxPlayableImpl>()
|
||||
.map(|i| i.song);
|
||||
// Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
|
||||
let jukebox_playable = item_stack
|
||||
.get_data_component::<JukeboxPlayableImpl>()
|
||||
.map(|i| i.song);
|
||||
|
||||
// Vanilla: if (lv == null) return PASS_TO_DEFAULT_BLOCK_ACTION
|
||||
let Some(jukebox_playable) = jukebox_playable else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
// Vanilla: if (lv == null) return PASS_TO_DEFAULT_BLOCK_ACTION
|
||||
let Some(jukebox_playable) = jukebox_playable else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
let Some(song_name) = jukebox_playable.split(':').nth(1) else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
let Some(song_name) = jukebox_playable.split(':').nth(1) else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
let Some(jukebox_song) = JukeboxSong::from_name(song_name) else {
|
||||
error!("Jukebox playable song not registered: {song_name}");
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
let Some(jukebox_song) = JukeboxSong::from_name(song_name) else {
|
||||
error!("Jukebox playable song not registered: {song_name}");
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
// Vanilla: ItemStack lv3 = stack.splitUnlessCreative(1, player)
|
||||
let record = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
// Vanilla: ItemStack lv3 = stack.splitUnlessCreative(1, player)
|
||||
let record = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
|
||||
// Vanilla: lv4.setStack(lv3)
|
||||
if let Some(block_entity) = world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) =
|
||||
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
{
|
||||
jukebox_entity.set_record(record).await;
|
||||
// Start tracking playback with song duration
|
||||
jukebox_entity.start_playing(jukebox_song.length_in_ticks());
|
||||
}
|
||||
// Vanilla: lv4.setStack(lv3)
|
||||
if let Some(block_entity) = world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
{
|
||||
jukebox_entity.set_record(record);
|
||||
// Start tracking playback with song duration
|
||||
jukebox_entity.start_playing(jukebox_song.length_in_ticks());
|
||||
}
|
||||
|
||||
// Update block state to has_record = true
|
||||
Self::set_record_state(true, args.block, args.position, world).await;
|
||||
// Update block state to has_record = true
|
||||
Self::set_record_state(true, args.block, args.position, world);
|
||||
|
||||
// Start playing the music (client-side audio)
|
||||
Self::start_playing(args.position, world, jukebox_song.get_id());
|
||||
// Start playing the music (client-side audio)
|
||||
Self::start_playing(args.position, world, jukebox_song.get_id());
|
||||
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::PlayRecord as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::PlayRecord as i32,
|
||||
1,
|
||||
);
|
||||
|
||||
// TODO: world.emitGameEvent(GameEvent.BLOCK_CHANGE, pos, ...)
|
||||
// TODO: world.emitGameEvent(GameEvent.BLOCK_CHANGE, pos, ...)
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
/// Called when the jukebox is broken
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Drop the record if there is one
|
||||
Self::drop_record(args.position, args.world).await;
|
||||
// Stop the music
|
||||
args.world
|
||||
.sync_world_event(WorldEvent::SoundStopJukeboxSong, *args.position, 0);
|
||||
})
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
// Drop the record if there is one
|
||||
Self::drop_record(args.position, args.world);
|
||||
// Stop the music
|
||||
args.world
|
||||
.sync_world_event(WorldEvent::SoundStopJukeboxSong, *args.position, 0);
|
||||
}
|
||||
|
||||
/// Vanilla: `JukeboxBlock.onStateReplaced()` -> `ItemScatterer.onStateReplaced()`
|
||||
fn on_state_replaced<'a>(&'a self, _args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Vanilla calls ItemScatterer.onStateReplaced which updates comparators
|
||||
// TODO: world.updateComparators(pos, block) when implemented
|
||||
})
|
||||
fn on_state_replaced(&self, _args: OnStateReplacedArgs<'_>) {
|
||||
// Vanilla calls ItemScatterer.onStateReplaced which updates comparators
|
||||
// TODO: world.updateComparators(pos, block) when implemented
|
||||
}
|
||||
|
||||
/// Vanilla: `JukeboxBlock.emitsRedstonePower()` returns true
|
||||
fn emits_redstone_power<'a>(
|
||||
&'a self,
|
||||
_args: EmitsRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { true })
|
||||
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Vanilla: Returns 15 if playing, 0 otherwise
|
||||
fn get_weak_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: GetRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv && lv.getManager().isPlaying() ? 15 : 0
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) =
|
||||
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
&& jukebox_entity.is_playing()
|
||||
{
|
||||
15
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv && lv.getManager().isPlaying() ? 15 : 0
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
&& jukebox_entity.is_playing()
|
||||
{
|
||||
15
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Vanilla: Returns the song's comparator output (0-15)
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv ? lv.getComparatorOutput() : 0
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) =
|
||||
block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
// Vanilla: return world.getBlockEntity(pos) instanceof JukeboxBlockEntity lv ? lv.getComparatorOutput() : 0
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(jukebox_entity) = block_entity.as_any().downcast_ref::<JukeboxBlockEntity>()
|
||||
{
|
||||
let record = jukebox_entity.get_record();
|
||||
// Get the song from the record's jukebox_playable component
|
||||
if let Some(playable) = record.get_data_component::<JukeboxPlayableImpl>()
|
||||
&& let Some(song_name) = playable.song.split(':').nth(1)
|
||||
&& let Some(song) = JukeboxSong::from_name(song_name)
|
||||
{
|
||||
let record = jukebox_entity.get_record().await;
|
||||
// Get the song from the record's jukebox_playable component
|
||||
if let Some(playable) = record.get_data_component::<JukeboxPlayableImpl>()
|
||||
&& let Some(song_name) = playable.song.split(':').nth(1)
|
||||
&& let Some(song) = JukeboxSong::from_name(song_name)
|
||||
{
|
||||
return Some(song.comparator_output());
|
||||
}
|
||||
return Some(song.comparator_output());
|
||||
}
|
||||
Some(0)
|
||||
})
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::world::World;
|
||||
@@ -16,40 +15,37 @@ use pumpkin_world::tick::TickPriority;
|
||||
pub struct LadderBlock;
|
||||
|
||||
impl BlockBehaviour for LadderBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let clicked_pos = args.use_item_on.position;
|
||||
let (clicked_block, clicked_block_state_id) =
|
||||
args.world.get_block_and_state_id(&clicked_pos);
|
||||
if clicked_block == &Block::LADDER {
|
||||
//you can't click on a ladder and place a ladder
|
||||
let props =
|
||||
LadderLikeProperties::from_state_id(clicked_block_state_id, clicked_block);
|
||||
let sub = args.position.0.sub(&clicked_pos.0);
|
||||
if let Some(dir) = horizontal_facing_from_offset(sub)
|
||||
&& let Some(horizontal_facing) = dir.to_horizontal_facing()
|
||||
&& props.facing == horizontal_facing
|
||||
{
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let clicked_pos = args.use_item_on.position;
|
||||
let (clicked_block, clicked_block_state_id) =
|
||||
args.world.get_block_and_state_id(&clicked_pos);
|
||||
if clicked_block == &Block::LADDER {
|
||||
//you can't click on a ladder and place a ladder
|
||||
let props = LadderLikeProperties::from_state_id(clicked_block_state_id, clicked_block);
|
||||
let sub = args.position.0.sub(&clicked_pos.0);
|
||||
if let Some(dir) = horizontal_facing_from_offset(sub)
|
||||
&& let Some(horizontal_facing) = dir.to_horizontal_facing()
|
||||
&& props.facing == horizontal_facing
|
||||
{
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
let mut props = LadderLikeProperties::default(args.block);
|
||||
}
|
||||
let mut props = LadderLikeProperties::default(args.block);
|
||||
|
||||
let directions = args.player.get_entity().get_entity_facing_order();
|
||||
for dir in directions {
|
||||
if dir == Facing::Up || dir == Facing::Down {
|
||||
continue;
|
||||
}
|
||||
if !can_place_ladder_at(args.world, args.position, dir.to_block_direction()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(facing) = dir.opposite().to_horizontal_facing() {
|
||||
props.facing = facing;
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
let directions = args.player.get_entity().get_entity_facing_order();
|
||||
for dir in directions {
|
||||
if dir == Facing::Up || dir == Facing::Down {
|
||||
continue;
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
})
|
||||
if !can_place_ladder_at(args.world, args.position, dir.to_block_direction()) {
|
||||
continue;
|
||||
}
|
||||
if let Some(facing) = dir.opposite().to_horizontal_facing() {
|
||||
props.facing = facing;
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
}
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
for dir in BlockDirection::horizontal() {
|
||||
@@ -63,41 +59,37 @@ impl BlockBehaviour for LadderBlock {
|
||||
}
|
||||
false
|
||||
}
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = LadderLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.facing.to_block_direction().opposite() == args.direction
|
||||
&& !can_place_ladder_at(
|
||||
args.world,
|
||||
args.position,
|
||||
props.facing.to_block_direction().opposite(),
|
||||
)
|
||||
{
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
if Block::from_state_id(state_id) != &Block::LADDER {
|
||||
return;
|
||||
}
|
||||
let props = LadderLikeProperties::from_state_id(state_id, args.block);
|
||||
if !can_place_ladder_at(
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let props = LadderLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.facing.to_block_direction().opposite() == args.direction
|
||||
&& !can_place_ladder_at(
|
||||
args.world,
|
||||
args.position,
|
||||
props.facing.to_block_direction().opposite(),
|
||||
) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
})
|
||||
)
|
||||
{
|
||||
return BlockStateId::AIR;
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
if Block::from_state_id(state_id) != &Block::LADDER {
|
||||
return;
|
||||
}
|
||||
let props = LadderLikeProperties::from_state_id(state_id, args.block);
|
||||
if !can_place_ladder_at(
|
||||
args.world,
|
||||
args.position,
|
||||
props.facing.to_block_direction().opposite(),
|
||||
) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
};
|
||||
use crate::world::World;
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -16,19 +15,16 @@ use pumpkin_world::world::BlockFlags;
|
||||
pub struct LanternBlock;
|
||||
|
||||
impl BlockBehaviour for LanternBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
pumpkin_data::block_properties::LanternLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = pumpkin_data::block_properties::LanternLikeProperties::default(args.block);
|
||||
props.r#waterlogged = args.replacing.water_source();
|
||||
|
||||
let block_up_state = args.world.get_block_state(&args.position.up());
|
||||
if block_up_state.is_center_solid(BlockDirection::Down) {
|
||||
props.r#hanging = true;
|
||||
}
|
||||
let block_up_state = args.world.get_block_state(&args.position.up());
|
||||
if block_up_state.is_center_solid(BlockDirection::Down) {
|
||||
props.r#hanging = true;
|
||||
}
|
||||
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
@@ -36,27 +32,22 @@ impl BlockBehaviour for LanternBlock {
|
||||
.is_some_and(|world| can_place_at(world, args.position))
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ use pumpkin_world::{
|
||||
};
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
RandomTickArgs,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs,
|
||||
};
|
||||
|
||||
pub const DECAY_DISTANCE: u8 = 7;
|
||||
@@ -53,59 +52,49 @@ pub fn update_distance(
|
||||
}
|
||||
|
||||
impl BlockBehaviour for LeavesBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props =
|
||||
OakLeavesLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.persistent = true;
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props = update_distance(args.world, args.position, props);
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props =
|
||||
OakLeavesLikeProperties::from_state_id(args.block.default_state.id, args.block);
|
||||
props.persistent = true;
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props = update_distance(args.world, args.position, props);
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
let distance_from_neighbor =
|
||||
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
|
||||
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
let distance_from_neighbor =
|
||||
get_distance_at(neighbor_block, args.neighbor_state_id).saturating_add(1);
|
||||
let current_props = OakLeavesLikeProperties::from_state_id(args.state_id, args.block);
|
||||
|
||||
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
if distance_from_neighbor != 1 || current_props.distance != distance_from_neighbor {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
|
||||
args.state_id
|
||||
})
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
|
||||
let updated_props = update_distance(&**args.world, args.position, props);
|
||||
let new_state_id = updated_props.to_state_id(args.block);
|
||||
if new_state_id != state_id {
|
||||
args.world
|
||||
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
|
||||
let updated_props = update_distance(&**args.world, args.position, props);
|
||||
let new_state_id = updated_props.to_state_id(args.block);
|
||||
if new_state_id != state_id {
|
||||
args.world
|
||||
.set_block_state(args.position, new_state_id, BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
|
||||
if !props.persistent && props.distance == DECAY_DISTANCE {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = OakLeavesLikeProperties::from_state_id(state_id, args.block);
|
||||
if !props.persistent && props.distance == DECAY_DISTANCE {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
|
||||
use crate::block::entities::lectern::LecternBlockEntity;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
|
||||
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
|
||||
GetRedstonePowerArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs,
|
||||
PlacedArgs, UseWithItemArgs,
|
||||
};
|
||||
@@ -63,7 +63,7 @@ impl LecternController for LecternPageController {
|
||||
}
|
||||
entity.page.store(page as usize, Ordering::Relaxed);
|
||||
entity.mark_dirty();
|
||||
LecternBlock::pulse(&self.world, &self.position).await;
|
||||
LecternBlock::pulse(&self.world, &self.position);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl LecternController for LecternPageController {
|
||||
if let Some(entity) = self.entity() {
|
||||
entity.page.store(0, Ordering::Relaxed);
|
||||
}
|
||||
LecternBlock::set_has_book(&self.world, &self.position, false).await;
|
||||
LecternBlock::set_has_book(&self.world, &self.position, false);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -113,22 +113,20 @@ impl LecternBlock {
|
||||
|
||||
/// The lectern strongly powers the block below it, so its neighbors need
|
||||
/// updating whenever the power or book state changes.
|
||||
async fn update_neighbors_below(world: &Arc<World>, position: &BlockPos) {
|
||||
world.update_neighbors(&position.down(), None).await;
|
||||
fn update_neighbors_below(world: &Arc<World>, position: &BlockPos) {
|
||||
world.update_neighbors(&position.down(), None);
|
||||
}
|
||||
|
||||
/// Emits the vanilla page-turn redstone pulse: powered for two game ticks.
|
||||
pub(crate) async fn pulse(world: &Arc<World>, position: &BlockPos) {
|
||||
pub(crate) fn pulse(world: &Arc<World>, position: &BlockPos) {
|
||||
let (block, state_id) = world.get_block_and_state_id(position);
|
||||
if block != &Block::LECTERN {
|
||||
return;
|
||||
}
|
||||
let mut props = LecternLikeProperties::from_state_id(state_id, block);
|
||||
props.powered = true;
|
||||
world
|
||||
.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
Self::update_neighbors_below(world, position).await;
|
||||
world.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
Self::update_neighbors_below(world, position);
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
*position,
|
||||
@@ -139,7 +137,7 @@ impl LecternBlock {
|
||||
}
|
||||
|
||||
/// Sets `has_book`, dropping any pending pulse like vanilla `setHasBook`.
|
||||
pub(crate) async fn set_has_book(world: &Arc<World>, position: &BlockPos, has_book: bool) {
|
||||
pub(crate) fn set_has_book(world: &Arc<World>, position: &BlockPos, has_book: bool) {
|
||||
let (block, state_id) = world.get_block_and_state_id(position);
|
||||
if block != &Block::LECTERN {
|
||||
return;
|
||||
@@ -147,214 +145,175 @@ impl LecternBlock {
|
||||
let mut props = LecternLikeProperties::from_state_id(state_id, block);
|
||||
props.powered = false;
|
||||
props.has_book = has_book;
|
||||
world
|
||||
.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
Self::update_neighbors_below(world, position).await;
|
||||
world.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
Self::update_neighbors_below(world, position);
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehaviour for LecternBlock {
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_entity = LecternBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
let block_entity = LecternBlockEntity::new(*args.position);
|
||||
args.world.add_block_entity(Arc::new(block_entity));
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = LecternLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = LecternLikeProperties::default(args.block);
|
||||
props.facing = args
|
||||
.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
if !props.has_book {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
if !props.has_book {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
|
||||
let Some(block_entity) = args.world.get_block_entity(args.position) else {
|
||||
return BlockActionResult::Pass;
|
||||
};
|
||||
let Some(inventory) = block_entity.get_inventory() else {
|
||||
return BlockActionResult::Pass;
|
||||
};
|
||||
let Some(block_entity) = args.world.get_block_entity(args.position) else {
|
||||
return BlockActionResult::Pass;
|
||||
};
|
||||
let Some(inventory) = block_entity.get_inventory() else {
|
||||
return BlockActionResult::Pass;
|
||||
};
|
||||
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithLectern as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithLectern as i32,
|
||||
1,
|
||||
);
|
||||
|
||||
let controller = Arc::new(LecternPageController {
|
||||
world: args.world.clone(),
|
||||
position: *args.position,
|
||||
inventory: inventory.clone(),
|
||||
});
|
||||
args.player
|
||||
let controller = Arc::new(LecternPageController {
|
||||
world: args.world.clone(),
|
||||
position: *args.position,
|
||||
inventory: inventory.clone(),
|
||||
});
|
||||
let player = args.player.clone();
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(
|
||||
&LecternScreenFactory {
|
||||
inventory,
|
||||
controller,
|
||||
},
|
||||
Some(*args.position),
|
||||
Some(pos),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item_stack = &mut *args.item_stack;
|
||||
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let item_stack = &mut *args.item_stack;
|
||||
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
if props.has_book {
|
||||
// Fall through so `normal_use` opens the reading screen.
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let Some(lectern) = args.world.get_block_entity(args.position) else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
let Some(lectern) = lectern.as_any().downcast_ref::<LecternBlockEntity>() else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
futures::executor::block_on(lectern.set_stack(0, book));
|
||||
|
||||
Self::set_has_book(args.world, args.position, true);
|
||||
args.world
|
||||
.play_block_sound(Sound::ItemBookPut, SoundCategory::Blocks, *args.position);
|
||||
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let mut props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
props.powered = false;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
|
||||
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
|
||||
if props.powered { 15 } else { 0 }
|
||||
}
|
||||
|
||||
fn get_strong_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
|
||||
if props.powered && args.direction == BlockDirection::Up {
|
||||
15
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||
if !args.moved {
|
||||
let props = LecternLikeProperties::from_state_id(args.old_state_id, args.block);
|
||||
if props.powered {
|
||||
Self::update_neighbors_below(args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
if props.has_book {
|
||||
// Fall through so `normal_use` opens the reading screen.
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||
{
|
||||
let book = futures::executor::block_on(lectern_entity.remove_stack(0));
|
||||
if !book.is_empty() {
|
||||
// Drop the book item
|
||||
let entity = Entity::new(
|
||||
args.world.clone(),
|
||||
Vector3::new(
|
||||
f64::from(args.position.0.x) + 0.5,
|
||||
f64::from(args.position.0.y) + 0.5,
|
||||
f64::from(args.position.0.z) + 0.5,
|
||||
),
|
||||
&EntityType::ITEM,
|
||||
);
|
||||
let item_entity = ItemEntity::new(entity, book);
|
||||
args.world.spawn_entity(Arc::new(item_entity));
|
||||
}
|
||||
|
||||
let Some(lectern) = args.world.get_block_entity(args.position) else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
let Some(lectern) = lectern.as_any().downcast_ref::<LecternBlockEntity>() else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
|
||||
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
let _ = item_stack;
|
||||
lectern.set_stack(0, book).await;
|
||||
|
||||
Self::set_has_book(args.world, args.position, true).await;
|
||||
args.world
|
||||
.play_block_sound(Sound::ItemBookPut, SoundCategory::Blocks, *args.position);
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let mut props = LecternLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
props.powered = false;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
Self::update_neighbors_below(args.world, args.position).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn emits_redstone_power<'a>(
|
||||
&'a self,
|
||||
_args: EmitsRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move { true })
|
||||
}
|
||||
|
||||
fn get_weak_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: GetRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
|
||||
if props.powered { 15 } else { 0 }
|
||||
})
|
||||
}
|
||||
|
||||
fn get_strong_redstone_power<'a>(
|
||||
&'a self,
|
||||
args: GetRedstonePowerArgs<'a>,
|
||||
) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
let props = LecternLikeProperties::from_state_id(args.state.id, args.block);
|
||||
if props.powered && args.direction == BlockDirection::Up {
|
||||
15
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !args.moved {
|
||||
let props = LecternLikeProperties::from_state_id(args.old_state_id, args.block);
|
||||
if props.powered {
|
||||
Self::update_neighbors_below(args.world, args.position).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(lectern_entity) =
|
||||
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||
{
|
||||
let book = lectern_entity.remove_stack(0).await;
|
||||
if !book.is_empty() {
|
||||
// Drop the book item
|
||||
let entity = Entity::new(
|
||||
args.world.clone(),
|
||||
Vector3::new(
|
||||
f64::from(args.position.0.x) + 0.5,
|
||||
f64::from(args.position.0.y) + 0.5,
|
||||
f64::from(args.position.0.z) + 0.5,
|
||||
),
|
||||
&EntityType::ITEM,
|
||||
);
|
||||
let item_entity = ItemEntity::new(entity, book);
|
||||
args.world.spawn_entity(Arc::new(item_entity)).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_comparator_output<'a>(
|
||||
&'a self,
|
||||
args: GetComparatorOutputArgs<'a>,
|
||||
) -> BlockFuture<'a, Option<u8>> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(lectern_entity) =
|
||||
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||
{
|
||||
Some(lectern_entity.comparator_output().await)
|
||||
} else {
|
||||
Some(0)
|
||||
}
|
||||
})
|
||||
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position)
|
||||
&& let Some(lectern_entity) = block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||
{
|
||||
Some(futures::executor::block_on(
|
||||
lectern_entity.comparator_output(),
|
||||
))
|
||||
} else {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_macros::pumpkin_block_from_tag;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use crate::block::{BlockBehaviour, BlockFuture};
|
||||
|
||||
type LogProperties = pumpkin_data::block_properties::PaleOakWoodLikeProperties;
|
||||
|
||||
@@ -11,12 +11,10 @@ type LogProperties = pumpkin_data::block_properties::PaleOakWoodLikeProperties;
|
||||
pub struct LogBlock;
|
||||
|
||||
impl BlockBehaviour for LogBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut log_props = LogProperties::default(args.block);
|
||||
log_props.axis = args.direction.to_axis();
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut log_props = LogProperties::default(args.block);
|
||||
log_props.axis = args.direction.to_axis();
|
||||
|
||||
log_props.to_state_id(args.block)
|
||||
})
|
||||
log_props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs, OnPlaceArgs};
|
||||
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs};
|
||||
use crate::entity::EntityBase;
|
||||
|
||||
use pumpkin_data::FacingExt;
|
||||
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_data::{BlockStateId, FacingExt};
|
||||
use pumpkin_inventory::loom_screen_handler::LoomScreenHandler;
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_inventory::screen_handler::{
|
||||
@@ -19,40 +19,35 @@ use tokio::sync::Mutex;
|
||||
pub struct LoomBlock;
|
||||
|
||||
impl BlockBehaviour for LoomBlock {
|
||||
fn on_place<'a>(
|
||||
&'a self,
|
||||
args: OnPlaceArgs<'a>,
|
||||
) -> BlockFuture<'a, pumpkin_data::BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
if let Some(facing) = args
|
||||
.player
|
||||
.get_entity()
|
||||
.get_facing()
|
||||
.opposite()
|
||||
.to_horizontal_facing()
|
||||
{
|
||||
props.facing = facing;
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = WallTorchLikeProperties::default(args.block);
|
||||
if let Some(facing) = args
|
||||
.player
|
||||
.get_entity()
|
||||
.get_facing()
|
||||
.opposite()
|
||||
.to_horizontal_facing()
|
||||
{
|
||||
props.facing = facing;
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
args.player
|
||||
.open_handled_screen(&LoomScreenFactory, Some(*args.position))
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
|
||||
1,
|
||||
);
|
||||
let player = Arc::clone(args.player);
|
||||
let pos = *args.position;
|
||||
tokio::spawn(async move {
|
||||
player
|
||||
.open_handled_screen(&LoomScreenFactory, Some(pos))
|
||||
.await;
|
||||
});
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
BlockActionResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ use pumpkin_data::{
|
||||
};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::block::{BlockBehaviour, BlockFuture, OnEntityStepArgs};
|
||||
use crate::block::{BlockBehaviour, OnEntityStepArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:magma_block")]
|
||||
pub struct MagmaBlock;
|
||||
|
||||
impl BlockBehaviour for MagmaBlock {
|
||||
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_entity_step(&self, args: OnEntityStepArgs<'_>) {
|
||||
{
|
||||
// Only living entities take damage
|
||||
let Some(living_entity) = args.entity.get_living_entity() else {
|
||||
return;
|
||||
@@ -31,7 +31,10 @@ impl BlockBehaviour for MagmaBlock {
|
||||
}
|
||||
|
||||
let has_frost_walker = {
|
||||
let equipment = living_entity.entity_equipment.lock().await;
|
||||
let equipment = living_entity
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::FEET)
|
||||
@@ -45,16 +48,13 @@ impl BlockBehaviour for MagmaBlock {
|
||||
|
||||
if living_entity
|
||||
.get_effect(&StatusEffect::FIRE_RESISTANCE)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply damage
|
||||
args.entity
|
||||
.damage(args.entity, 1.0, DamageType::HOT_FLOOR)
|
||||
.await;
|
||||
})
|
||||
args.entity.damage(args.entity, 1.0, DamageType::HOT_FLOOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::block::{BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs};
|
||||
use crate::block::{BlockBehaviour, GetStateForNeighborUpdateArgs, OnPlaceArgs};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::block_properties::{BlockProperties, MangroveRootsLikeProperties};
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
@@ -9,29 +9,25 @@ use pumpkin_world::tick::TickPriority;
|
||||
pub struct MangroveRootsBlock;
|
||||
|
||||
impl BlockBehaviour for MangroveRootsBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = MangroveRootsLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = MangroveRootsLikeProperties::default(args.block);
|
||||
props.waterlogged = args.replacing.water_source();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = MangroveRootsLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let props = MangroveRootsLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.waterlogged {
|
||||
args.world.schedule_fluid_tick(
|
||||
&Fluid::WATER,
|
||||
*args.position,
|
||||
Fluid::WATER.flow_speed as u8,
|
||||
TickPriority::Normal,
|
||||
);
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
|
||||
OnStateReplacedArgs, RandomTickArgs,
|
||||
BlockBehaviour, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs, OnStateReplacedArgs,
|
||||
RandomTickArgs,
|
||||
},
|
||||
entity::{EntityBase, r#type::from_type},
|
||||
world::{World, portal::nether::NetherPortal},
|
||||
@@ -44,123 +44,116 @@ impl NetherPortalBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for NetherPortalBlock {
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let direction_axis = args.direction.to_axis();
|
||||
let state_axis =
|
||||
NetherPortalLikeProperties::from_state_id(args.state_id, &Block::NETHER_PORTAL)
|
||||
.axis;
|
||||
// Convert HorizontalAxis to Axis for comparison
|
||||
let state_axis_full: Axis = match state_axis {
|
||||
HorizontalAxis::X => Axis::X,
|
||||
HorizontalAxis::Z => Axis::Z,
|
||||
};
|
||||
// Vanilla logic: keep portal if direction is horizontal AND different from portal axis
|
||||
let is_horizontal_and_different =
|
||||
args.direction.is_horizontal() && direction_axis != state_axis_full;
|
||||
if is_horizontal_and_different
|
||||
|| args.neighbor_state_id == args.state_id
|
||||
|| NetherPortal::get_on_axis(args.world, args.position, state_axis)
|
||||
.is_some_and(|e| e.was_already_valid())
|
||||
{
|
||||
return args.state_id;
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let direction_axis = args.direction.to_axis();
|
||||
let state_axis =
|
||||
NetherPortalLikeProperties::from_state_id(args.state_id, &Block::NETHER_PORTAL).axis;
|
||||
// Convert HorizontalAxis to Axis for comparison
|
||||
let state_axis_full: Axis = match state_axis {
|
||||
HorizontalAxis::X => Axis::X,
|
||||
HorizontalAxis::Z => Axis::Z,
|
||||
};
|
||||
// Vanilla logic: keep portal if direction is horizontal AND different from portal axis
|
||||
let is_horizontal_and_different =
|
||||
args.direction.is_horizontal() && direction_axis != state_axis_full;
|
||||
if is_horizontal_and_different
|
||||
|| args.neighbor_state_id == args.state_id
|
||||
|| NetherPortal::get_on_axis(args.world, args.position, state_axis)
|
||||
.is_some_and(|e| e.was_already_valid())
|
||||
{
|
||||
return args.state_id;
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let level_info = args.world.level_info.load();
|
||||
let difficulty = level_info.difficulty;
|
||||
if !level_info.game_rules.spawn_monsters
|
||||
|| !level_info.game_rules.spawn_mobs
|
||||
|| difficulty == Difficulty::Peaceful
|
||||
|| (args.world.dimension != Dimension::OVERWORLD
|
||||
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
|
||||
{
|
||||
return;
|
||||
}
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let level_info = args.world.level_info.load();
|
||||
let difficulty = level_info.difficulty;
|
||||
if !level_info.game_rules.spawn_mobs
|
||||
|| difficulty == Difficulty::Peaceful
|
||||
|| (args.world.dimension != Dimension::OVERWORLD
|
||||
&& args.world.dimension != Dimension::OVERWORLD_CAVES)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let difficulty_id = difficulty as u32;
|
||||
let roll = rand::rng().random_range(0..2000);
|
||||
if roll >= difficulty_id {
|
||||
return;
|
||||
}
|
||||
let difficulty_id = difficulty as u32;
|
||||
let roll = rand::rng().random_range(0..2000);
|
||||
if roll >= difficulty_id {
|
||||
return;
|
||||
}
|
||||
|
||||
let player_close = args
|
||||
.world
|
||||
.get_closest_player(args.position.to_centered_f64(), 128.0)
|
||||
.is_some();
|
||||
if !player_close {
|
||||
return;
|
||||
}
|
||||
let player_close = args
|
||||
.world
|
||||
.get_closest_player(args.position.to_centered_f64(), 128.0)
|
||||
.is_some();
|
||||
if !player_close {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut bottom_pos = *args.position;
|
||||
while args.world.get_block(&bottom_pos) == &Block::NETHER_PORTAL {
|
||||
bottom_pos = bottom_pos.down();
|
||||
}
|
||||
let mut bottom_pos = *args.position;
|
||||
while args.world.get_block(&bottom_pos) == &Block::NETHER_PORTAL {
|
||||
bottom_pos = bottom_pos.down();
|
||||
}
|
||||
|
||||
if args
|
||||
.world
|
||||
.get_block_state(&bottom_pos)
|
||||
.is_side_solid(BlockDirection::Up)
|
||||
{
|
||||
let spawn_pos = Vector3::new(
|
||||
bottom_pos.0.x as f64 + 0.5,
|
||||
(bottom_pos.0.y + 1) as f64,
|
||||
bottom_pos.0.z as f64 + 0.5,
|
||||
);
|
||||
let mob = from_type(
|
||||
&EntityType::ZOMBIFIED_PIGLIN,
|
||||
spawn_pos,
|
||||
args.world,
|
||||
Uuid::new_v4(),
|
||||
);
|
||||
mob.get_entity()
|
||||
.portal_cooldown
|
||||
.store(300, Ordering::Relaxed);
|
||||
args.world.spawn_entity(mob).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let target_world =
|
||||
if args.world.dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
|
||||
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
|
||||
} else {
|
||||
args.server.get_world_from_dimension(&Dimension::THE_NETHER)
|
||||
};
|
||||
|
||||
if Arc::ptr_eq(&target_world, args.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Nether portal collision at {:?}, targeting world {:?}",
|
||||
args.position,
|
||||
target_world.dimension.minecraft_name
|
||||
if args
|
||||
.world
|
||||
.get_block_state(&bottom_pos)
|
||||
.is_side_solid(BlockDirection::Up)
|
||||
{
|
||||
let spawn_pos = Vector3::new(
|
||||
bottom_pos.0.x as f64 + 0.5,
|
||||
(bottom_pos.0.y + 1) as f64,
|
||||
bottom_pos.0.z as f64 + 0.5,
|
||||
);
|
||||
let portal_delay = Self::get_portal_time(args.world, args.entity);
|
||||
|
||||
args.entity
|
||||
.get_entity()
|
||||
.try_use_portal(portal_delay, target_world, *args.position)
|
||||
.await;
|
||||
})
|
||||
let mob = from_type(
|
||||
&EntityType::ZOMBIFIED_PIGLIN,
|
||||
spawn_pos,
|
||||
args.world,
|
||||
Uuid::new_v4(),
|
||||
);
|
||||
mob.get_entity()
|
||||
.portal_cooldown
|
||||
.store(300, Ordering::Relaxed);
|
||||
args.world.spawn_entity_non_save(mob);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_state_replaced<'a>(&'a self, args: OnStateReplacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Remove from POI storage when portal block is replaced
|
||||
let mut poi_storage = args.world.portal_poi.lock().await;
|
||||
poi_storage.remove(args.position);
|
||||
})
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
let target_world =
|
||||
if args.world.dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
|
||||
args.server.get_world_from_dimension(&Dimension::OVERWORLD)
|
||||
} else {
|
||||
args.server.get_world_from_dimension(&Dimension::THE_NETHER)
|
||||
};
|
||||
|
||||
if Arc::ptr_eq(&target_world, args.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Nether portal collision at {:?}, targeting world {:?}",
|
||||
args.position,
|
||||
target_world.dimension.minecraft_name
|
||||
);
|
||||
let portal_delay = Self::get_portal_time(args.world, args.entity);
|
||||
|
||||
args.entity
|
||||
.get_entity()
|
||||
.try_use_portal(portal_delay, target_world, *args.position);
|
||||
}
|
||||
|
||||
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||
// Remove from POI storage when portal block is replaced
|
||||
let mut poi_storage = args
|
||||
.world
|
||||
.portal_poi
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
poi_storage.remove(args.position);
|
||||
}
|
||||
|
||||
fn rotate(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
UseWithItemArgs,
|
||||
};
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -25,7 +25,7 @@ use super::redstone::block_receives_redstone_power;
|
||||
pub struct NoteBlock;
|
||||
|
||||
impl NoteBlock {
|
||||
pub async fn play_note(props: &NoteBlockLikeProperties, world: &World, pos: &BlockPos) {
|
||||
pub fn play_note(props: &NoteBlockLikeProperties, world: &World, pos: &BlockPos) {
|
||||
if !is_base_block(props.instrument) || world.get_block_state(&pos.up()).is_air() {
|
||||
let mut event = crate::plugin::api::events::block::note_play::NotePlayEvent::new(
|
||||
*pos,
|
||||
@@ -33,12 +33,12 @@ impl NoteBlock {
|
||||
props.note,
|
||||
);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
}
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
world.add_synced_block_event(*pos, 0, 0).await;
|
||||
world.add_synced_block_event(*pos, 0, 0);
|
||||
}
|
||||
}
|
||||
fn get_note_pitch(note: u16) -> f32 {
|
||||
@@ -70,116 +70,92 @@ impl NoteBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for NoteBlock {
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await;
|
||||
// check if powered state changed
|
||||
if note_props.powered != powered {
|
||||
if powered {
|
||||
Self::play_note(¬e_props, args.world, args.position).await;
|
||||
}
|
||||
note_props.powered = powered;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
note_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
let powered = block_receives_redstone_power(args.world, args.position);
|
||||
// check if powered state changed
|
||||
if note_props.powered != powered {
|
||||
if powered {
|
||||
Self::play_note(¬e_props, args.world, args.position);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
note_props.note = (note_props.note + 1) % 25;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
note_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
Self::play_note(¬e_props, args.world, args.position).await;
|
||||
|
||||
args.player
|
||||
.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::TuneNoteblock as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
_args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
// TODO
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
})
|
||||
}
|
||||
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
let instrument = note_props.instrument;
|
||||
let pitch = if is_base_block(instrument) {
|
||||
// checks if can be pitched
|
||||
Self::get_note_pitch(u16::from(note_props.note))
|
||||
} else {
|
||||
1.0 // default pitch
|
||||
};
|
||||
// check hasCustomSound
|
||||
args.world.play_sound_raw(
|
||||
convert_instrument_to_sound(instrument) as u16,
|
||||
SoundCategory::Records,
|
||||
&args.position.to_f64(),
|
||||
3.0,
|
||||
pitch,
|
||||
note_props.powered = powered;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
note_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
Self::get_state_with_instrument(
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
note_props.note = (note_props.note + 1) % 25;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
note_props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
Self::play_note(¬e_props, args.world, args.position);
|
||||
|
||||
args.player.increment_stat(
|
||||
pumpkin_data::statistic::StatisticCategory::Custom,
|
||||
pumpkin_data::statistic::CustomStatistic::TuneNoteblock as i32,
|
||||
1,
|
||||
);
|
||||
|
||||
BlockActionResult::Success
|
||||
}
|
||||
|
||||
fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
// TODO
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
}
|
||||
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
let block_state = args.world.get_block_state(args.position);
|
||||
let note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block);
|
||||
let instrument = note_props.instrument;
|
||||
let pitch = if is_base_block(instrument) {
|
||||
// checks if can be pitched
|
||||
Self::get_note_pitch(u16::from(note_props.note))
|
||||
} else {
|
||||
1.0 // default pitch
|
||||
};
|
||||
// check hasCustomSound
|
||||
args.world.play_sound_raw(
|
||||
convert_instrument_to_sound(instrument) as u16,
|
||||
SoundCategory::Records,
|
||||
&args.position.to_f64(),
|
||||
3.0,
|
||||
pitch,
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
Self::get_state_with_instrument(
|
||||
args.world,
|
||||
args.position,
|
||||
Block::NOTE_BLOCK.default_state.id,
|
||||
args.block,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if args.direction.to_axis() == Axis::Y {
|
||||
return Self::get_state_with_instrument(
|
||||
args.world,
|
||||
args.position,
|
||||
Block::NOTE_BLOCK.default_state.id,
|
||||
args.state_id,
|
||||
args.block,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction.to_axis() == Axis::Y {
|
||||
return Self::get_state_with_instrument(
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
args.block,
|
||||
);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{BlockBehaviour, BlockFuture, BonemealArgs, RandomTickArgs};
|
||||
use crate::block::{BlockBehaviour, BonemealArgs, RandomTickArgs};
|
||||
use crate::world::World;
|
||||
|
||||
#[pumpkin_block_from_tag("minecraft:nylium")]
|
||||
@@ -25,18 +25,14 @@ impl NyliumBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for NyliumBlock {
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !Self::can_be_nylium(args.world, args.position) {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::NETHERRACK.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if !Self::can_be_nylium(args.world, args.position) {
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::NETHERRACK.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
|
||||
@@ -50,26 +46,24 @@ impl BlockBehaviour for NyliumBlock {
|
||||
true
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let world = args.world;
|
||||
let block = args.block;
|
||||
let above_pos = args.position.up();
|
||||
fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
|
||||
let world = args.world;
|
||||
let block = args.block;
|
||||
let above_pos = args.position.up();
|
||||
|
||||
if block == &Block::CRIMSON_NYLIUM {
|
||||
place_crimson_vegetation(world, &above_pos).await;
|
||||
} else if block == &Block::WARPED_NYLIUM {
|
||||
place_warped_vegetation(world, &above_pos).await;
|
||||
place_nether_sprouts(world, &above_pos).await;
|
||||
if rand::rng().random_range(0..8) == 0 {
|
||||
place_twisting_vines(world, &above_pos).await;
|
||||
}
|
||||
if block == &Block::CRIMSON_NYLIUM {
|
||||
place_crimson_vegetation(world, &above_pos);
|
||||
} else if block == &Block::WARPED_NYLIUM {
|
||||
place_warped_vegetation(world, &above_pos);
|
||||
place_nether_sprouts(world, &above_pos);
|
||||
if rand::rng().random_range(0..8) == 0 {
|
||||
place_twisting_vines(world, &above_pos);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
for _ in 0..9 {
|
||||
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
|
||||
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
|
||||
@@ -116,13 +110,11 @@ async fn place_crimson_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
world
|
||||
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
for _ in 0..9 {
|
||||
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
|
||||
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
|
||||
@@ -171,13 +163,11 @@ async fn place_warped_vegetation(world: &Arc<World>, origin: &BlockPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
world
|
||||
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
|
||||
fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
|
||||
for _ in 0..9 {
|
||||
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
|
||||
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
|
||||
@@ -216,13 +206,11 @@ async fn place_nether_sprouts(world: &Arc<World>, origin: &BlockPos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
world
|
||||
.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(&target_pos, state.id, BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
|
||||
fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
|
||||
for _ in 0..9 {
|
||||
let dx = rand::rng().random_range(0..3) - rand::rng().random_range(0..3);
|
||||
let dy = rand::rng().random_range(0..1) - rand::rng().random_range(0..1);
|
||||
@@ -256,22 +244,18 @@ async fn place_twisting_vines(world: &Arc<World>, origin: &BlockPos) {
|
||||
|| !world.get_block_state(¤t_pos.up()).is_air();
|
||||
|
||||
if is_top {
|
||||
world
|
||||
.set_block_state(
|
||||
¤t_pos,
|
||||
Block::TWISTING_VINES.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
¤t_pos,
|
||||
Block::TWISTING_VINES.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
break;
|
||||
}
|
||||
world
|
||||
.set_block_state(
|
||||
¤t_pos,
|
||||
Block::TWISTING_VINES_PLANT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
¤t_pos,
|
||||
Block::TWISTING_VINES_PLANT.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
current_pos = current_pos.up();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ impl<'a> PistonHandler<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn calculate_push(&mut self) -> bool {
|
||||
pub fn calculate_push(&mut self) -> bool {
|
||||
self.moved_blocks.clear();
|
||||
self.broken_blocks.clear();
|
||||
let (block, block_state) = self.world.get_block_and_state(&self.pos_to);
|
||||
@@ -64,15 +64,13 @@ impl<'a> PistonHandler<'a> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if !self.try_move(self.pos_to, self.motion_direction).await {
|
||||
if !self.try_move(self.pos_to, self.motion_direction) {
|
||||
return false;
|
||||
}
|
||||
for i in 0..self.moved_blocks.len() {
|
||||
let block_pos = self.moved_blocks[i];
|
||||
let block = self.world.get_block(&block_pos);
|
||||
if Self::is_block_sticky(block)
|
||||
&& !self.try_move_adjacent_block(block, &block_pos).await
|
||||
{
|
||||
if Self::is_block_sticky(block) && !self.try_move_adjacent_block(block, &block_pos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +96,7 @@ impl<'a> PistonHandler<'a> {
|
||||
|| (!self.retracted && pos == self.pos_from.offset(self.piston_direction.to_offset()))
|
||||
}
|
||||
|
||||
async fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool {
|
||||
fn try_move(&mut self, pos: BlockPos, dir: BlockDirection) -> bool {
|
||||
let (mut block, block_state) = self.world.get_block_and_state(&pos);
|
||||
if block_state.is_air() {
|
||||
return true;
|
||||
@@ -154,7 +152,7 @@ impl<'a> PistonHandler<'a> {
|
||||
let block_pos3 = self.moved_blocks[m];
|
||||
let block = self.world.get_block(&block_pos3);
|
||||
if Self::is_block_sticky(block)
|
||||
&& !Box::pin(self.try_move_adjacent_block(block, &block_pos3)).await
|
||||
&& !self.try_move_adjacent_block(block, &block_pos3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -204,7 +202,7 @@ impl<'a> PistonHandler<'a> {
|
||||
self.moved_blocks.extend(list3);
|
||||
}
|
||||
|
||||
async fn try_move_adjacent_block(&mut self, block: &Block, pos: &BlockPos) -> bool {
|
||||
fn try_move_adjacent_block(&mut self, block: &Block, pos: &BlockPos) -> bool {
|
||||
for direction in BlockDirection::all() {
|
||||
if direction.to_axis() == self.motion_direction.to_axis() {
|
||||
continue;
|
||||
@@ -212,7 +210,7 @@ impl<'a> PistonHandler<'a> {
|
||||
let block_pos = pos.offset(direction.to_offset());
|
||||
let block_state2 = self.world.get_block(&block_pos);
|
||||
if Self::is_adjacent_block_stuck(block_state2, block)
|
||||
&& !self.try_move(block_pos, direction).await
|
||||
&& !self.try_move(block_pos, direction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
BlockBehaviour, BlockMetadata, BrokenArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnSyncedBlockEventArgs, PlacedArgs,
|
||||
blocks::{piston::piston_head::PistonHeadProperties, redstone::is_emitting_redstone_power},
|
||||
},
|
||||
@@ -78,249 +78,229 @@ impl PistonBlock {
|
||||
}
|
||||
|
||||
impl BlockBehaviour for PistonBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = PistonProps::default(args.block);
|
||||
props.extended = false;
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = PistonProps::default(args.block);
|
||||
props.extended = false;
|
||||
props.facing = args.player.get_entity().get_facing().opposite();
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let props = PistonProps::from_state_id(args.state.id, args.block);
|
||||
let pos = args
|
||||
.position
|
||||
.offset(props.facing.to_block_direction().to_offset());
|
||||
let (block_to_check, block_to_check_state_id) = args.world.get_block_and_state_id(&pos);
|
||||
if &Block::PISTON_HEAD == block_to_check {
|
||||
let head_props =
|
||||
PistonHeadProperties::from_state_id(block_to_check_state_id, block_to_check);
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
let props = PistonProps::from_state_id(args.state.id, args.block);
|
||||
let pos = args
|
||||
.position
|
||||
.offset(props.facing.to_block_direction().to_offset());
|
||||
let (block_to_check, block_to_check_state_id) = args.world.get_block_and_state_id(&pos);
|
||||
if &Block::PISTON_HEAD == block_to_check {
|
||||
let head_props =
|
||||
PistonHeadProperties::from_state_id(block_to_check_state_id, block_to_check);
|
||||
|
||||
if (head_props.facing.to_block_direction() != props.facing.to_block_direction())
|
||||
&& &Block::PISTON_HEAD == block_to_check
|
||||
{
|
||||
//Then this is a head of some other piston.
|
||||
return;
|
||||
}
|
||||
|
||||
args.world
|
||||
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
} else if &Block::MOVING_PISTON == block_to_check {
|
||||
args.world
|
||||
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if args.old_state_id == args.state_id {
|
||||
if (head_props.facing.to_block_direction() != props.facing.to_block_direction())
|
||||
&& &Block::PISTON_HEAD == block_to_check
|
||||
{
|
||||
//Then this is a head of some other piston.
|
||||
return;
|
||||
}
|
||||
try_move(args.world, args.block, args.position).await;
|
||||
})
|
||||
|
||||
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
|
||||
} else if &Block::MOVING_PISTON == block_to_check {
|
||||
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
try_move(args.world, args.block, args.position).await;
|
||||
})
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
if args.old_state_id == args.state_id {
|
||||
return;
|
||||
}
|
||||
try_move(args.world, args.block, args.position);
|
||||
}
|
||||
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
try_move(args.world, args.block, args.position);
|
||||
}
|
||||
|
||||
fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool {
|
||||
let block_id = args.block.id;
|
||||
let block = Block::from_id(block_id);
|
||||
Self::handle_synced_block_event(block, args.world, args.position, args.r#type, args.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl PistonBlock {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn on_synced_block_event<'a>(
|
||||
&'a self,
|
||||
args: OnSyncedBlockEventArgs<'a>,
|
||||
) -> BlockFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let (block, world, pos, r#type, data) = (
|
||||
args.block,
|
||||
args.world,
|
||||
args.position,
|
||||
args.r#type,
|
||||
args.data,
|
||||
);
|
||||
fn handle_synced_block_event(
|
||||
block: &Block,
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
r#type: u8,
|
||||
data: u8,
|
||||
) -> bool {
|
||||
let state = world.get_block_state(pos);
|
||||
let mut props = PistonProps::from_state_id(state.id, block);
|
||||
let dir = props.facing.to_block_direction();
|
||||
|
||||
let state = world.get_block_state(pos);
|
||||
let mut props = PistonProps::from_state_id(state.id, block);
|
||||
let dir = props.facing.to_block_direction();
|
||||
// I don't think this is optimal ?
|
||||
let sticky = block == &Block::STICKY_PISTON;
|
||||
|
||||
// I don't think this is optimal ?
|
||||
let sticky = block == &Block::STICKY_PISTON;
|
||||
let should_extend = should_extend(world, pos, dir);
|
||||
if should_extend && (r#type == 1 || r#type == 2) {
|
||||
props.extended = true;
|
||||
world.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS);
|
||||
return false;
|
||||
}
|
||||
|
||||
let should_extend = should_extend(world, pos, dir).await;
|
||||
if should_extend && (r#type == 1 || r#type == 2) {
|
||||
props.extended = true;
|
||||
world
|
||||
.set_block_state(pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS)
|
||||
.await;
|
||||
return false;
|
||||
}
|
||||
|
||||
// This may prevents when something happens in the one tick before this function got called
|
||||
if !should_extend && r#type == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extend Piston
|
||||
if r#type == 0 {
|
||||
let mut event =
|
||||
crate::plugin::api::events::block::block_piston::BlockPistonExtendEvent::new(
|
||||
*pos,
|
||||
format!("{dir:?}"),
|
||||
);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
}
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !move_piston(world, dir, pos, true, sticky).await {
|
||||
return false;
|
||||
}
|
||||
props.extended = true;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
|
||||
)
|
||||
.await;
|
||||
// Play piston extend sound
|
||||
let pitch = rand::rng().random_range(0.6f32..0.85);
|
||||
world.play_sound_fine(
|
||||
Sound::BlockPistonExtend,
|
||||
SoundCategory::Blocks,
|
||||
&pos.to_centered_f64(),
|
||||
0.5,
|
||||
pitch,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// Reduce Piston
|
||||
// This may prevents when something happens in the one tick before this function got called
|
||||
if !should_extend && r#type == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extend Piston
|
||||
if r#type == 0 {
|
||||
let mut event =
|
||||
crate::plugin::api::events::block::block_piston::BlockPistonRetractEvent::new(
|
||||
crate::plugin::api::events::block::block_piston::BlockPistonExtendEvent::new(
|
||||
*pos,
|
||||
format!("{dir:?}"),
|
||||
);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
}
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let extended_pos = pos.offset(dir.to_offset());
|
||||
|
||||
if let Some(block_entity) = world.get_block_entity(&extended_pos)
|
||||
&& let Some(piston) = block_entity.as_any().downcast_ref::<PistonBlockEntity>()
|
||||
{
|
||||
piston.finish(world.clone()).await;
|
||||
if !move_piston(world, dir, pos, true, sticky) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut props = MovingPistonLikeProperties::default(&Block::MOVING_PISTON);
|
||||
props.facing = dir.to_facing();
|
||||
props.r#type = if sticky {
|
||||
PistonType::Sticky
|
||||
} else {
|
||||
PistonType::Normal
|
||||
};
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::MOVING_PISTON),
|
||||
BlockFlags::FORCE_STATE,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut props = PistonProps::default(block);
|
||||
props.facing = BlockDirection::by_index((data & 7) as usize)
|
||||
.unwrap_or(BlockDirection::North)
|
||||
.to_facing();
|
||||
|
||||
world.add_block_entity(Arc::new(PistonBlockEntity {
|
||||
position: *pos,
|
||||
facing: dir,
|
||||
pushed_block_state: BlockState::from_id(props.to_state_id(block)),
|
||||
current_progress: 0.0.into(),
|
||||
last_progress: 0.0.into(),
|
||||
extending: false,
|
||||
source: true,
|
||||
}));
|
||||
|
||||
world.update_neighbors(pos, None).await;
|
||||
if sticky {
|
||||
let pull_pos = pos.offset_dir(dir.to_offset(), 2);
|
||||
let (block, state) = world.get_block_and_state(&pull_pos);
|
||||
let piston_piece = if block == &Block::MOVING_PISTON
|
||||
&& let Some(entity) = world.get_block_entity(&pull_pos)
|
||||
&& let Some(piston) = entity.as_any().downcast_ref::<PistonBlockEntity>()
|
||||
&& piston.facing == dir
|
||||
&& piston.extending
|
||||
{
|
||||
piston.finish(world.clone()).await;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !piston_piece {
|
||||
if r#type == 1
|
||||
&& !state.is_air()
|
||||
&& Self::is_movable(block, state, dir, false, dir)
|
||||
&& (state.piston_behavior == PistonBehavior::Normal
|
||||
|| block == &Block::PISTON
|
||||
|| block == &Block::STICKY_PISTON)
|
||||
{
|
||||
move_piston(world, dir, pos, false, sticky).await;
|
||||
} else {
|
||||
// remove
|
||||
world
|
||||
.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// remove
|
||||
world
|
||||
.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
// Play piston contract sound
|
||||
let pitch = rand::rng().random_range(0.6f32..0.75);
|
||||
props.extended = true;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(block),
|
||||
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
|
||||
);
|
||||
// Play piston extend sound
|
||||
let pitch = rand::rng().random_range(0.6f32..0.85);
|
||||
world.play_sound_fine(
|
||||
Sound::BlockPistonContract,
|
||||
Sound::BlockPistonExtend,
|
||||
SoundCategory::Blocks,
|
||||
&pos.to_centered_f64(),
|
||||
0.5,
|
||||
pitch,
|
||||
);
|
||||
true
|
||||
})
|
||||
return true;
|
||||
}
|
||||
// Reduce Piston
|
||||
|
||||
let mut event =
|
||||
crate::plugin::api::events::block::block_piston::BlockPistonRetractEvent::new(
|
||||
*pos,
|
||||
format!("{dir:?}"),
|
||||
);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||
}
|
||||
if event.cancelled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let extended_pos = pos.offset(dir.to_offset());
|
||||
|
||||
if let Some(block_entity) = world.get_block_entity(&extended_pos)
|
||||
&& let Some(piston) = block_entity.as_any().downcast_ref::<PistonBlockEntity>()
|
||||
{
|
||||
piston.finish(world);
|
||||
}
|
||||
|
||||
let mut props = MovingPistonLikeProperties::default(&Block::MOVING_PISTON);
|
||||
props.facing = dir.to_facing();
|
||||
props.r#type = if sticky {
|
||||
PistonType::Sticky
|
||||
} else {
|
||||
PistonType::Normal
|
||||
};
|
||||
|
||||
world.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::MOVING_PISTON),
|
||||
BlockFlags::FORCE_STATE,
|
||||
);
|
||||
|
||||
let mut props = PistonProps::default(block);
|
||||
props.facing = BlockDirection::by_index((data & 7) as usize)
|
||||
.unwrap_or(BlockDirection::North)
|
||||
.to_facing();
|
||||
|
||||
world.add_block_entity(Arc::new(PistonBlockEntity {
|
||||
position: *pos,
|
||||
facing: dir,
|
||||
pushed_block_state: BlockState::from_id(props.to_state_id(block)),
|
||||
current_progress: 0.0.into(),
|
||||
last_progress: 0.0.into(),
|
||||
extending: false,
|
||||
source: true,
|
||||
}));
|
||||
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::FORCE_STATE,
|
||||
);
|
||||
|
||||
world.update_neighbors(pos, None);
|
||||
if sticky {
|
||||
let pull_pos = pos.offset_dir(dir.to_offset(), 2);
|
||||
let (block, state) = world.get_block_and_state(&pull_pos);
|
||||
if data == 2 {
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
} else {
|
||||
let is_air = state.is_air();
|
||||
if !is_air
|
||||
&& (Self::is_movable(block, state, dir, false, dir.opposite())
|
||||
|| Self::is_movable(block, state, dir, false, dir))
|
||||
&& (state.piston_behavior == PistonBehavior::Normal
|
||||
|| block == &Block::PISTON
|
||||
|| block == &Block::STICKY_PISTON)
|
||||
{
|
||||
move_piston(world, dir, pos, false, sticky);
|
||||
} else {
|
||||
// remove
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// remove
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
// Play piston contract sound
|
||||
let pitch = rand::rng().random_range(0.6f32..0.75);
|
||||
world.play_sound_fine(
|
||||
Sound::BlockPistonContract,
|
||||
SoundCategory::Blocks,
|
||||
&pos.to_centered_f64(),
|
||||
0.5,
|
||||
pitch,
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDirection) -> bool {
|
||||
fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDirection) -> bool {
|
||||
for dir in BlockDirection::all() {
|
||||
let neighbor_pos = block_pos.offset(dir.to_offset());
|
||||
let (block, state) = world.get_block_and_state(&neighbor_pos);
|
||||
// Pistons can't be powered from the same direction as they are facing
|
||||
if dir == piston_dir
|
||||
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir).await
|
||||
if dir == piston_dir || !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -328,14 +308,14 @@ async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDir
|
||||
}
|
||||
let neighbor_pos = block_pos.offset(BlockDirection::Down.to_offset());
|
||||
let (block, state) = world.get_block_and_state(&neighbor_pos);
|
||||
if is_emitting_redstone_power(block, state, world, block_pos, BlockDirection::Down).await {
|
||||
if is_emitting_redstone_power(block, state, world, block_pos, BlockDirection::Down) {
|
||||
return true;
|
||||
}
|
||||
for dir in BlockDirection::all() {
|
||||
let neighbor_pos = block_pos.up().offset(dir.to_offset());
|
||||
let (block, state) = world.get_block_and_state(&neighbor_pos);
|
||||
if dir == BlockDirection::Down
|
||||
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir).await
|
||||
|| !is_emitting_redstone_power(block, state, world, &neighbor_pos, dir)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -344,20 +324,15 @@ async fn should_extend(world: &World, block_pos: &BlockPos, piston_dir: BlockDir
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
|
||||
pub fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
|
||||
let state = world.get_block_state(block_pos);
|
||||
let props = PistonProps::from_state_id(state.id, block);
|
||||
let dir = props.facing.to_block_direction();
|
||||
let should_extent = should_extend(world, block_pos, dir).await;
|
||||
let should_extent = should_extend(world, block_pos, dir);
|
||||
|
||||
if should_extent && !props.extended {
|
||||
if PistonHandler::new(world, *block_pos, dir, true)
|
||||
.calculate_push()
|
||||
.await
|
||||
{
|
||||
world
|
||||
.add_synced_block_event(*block_pos, 0, dir.to_index())
|
||||
.await;
|
||||
if PistonHandler::new(world, *block_pos, dir, true).calculate_push() {
|
||||
world.add_synced_block_event(*block_pos, 0, dir.to_index());
|
||||
}
|
||||
} else if !should_extent && props.extended {
|
||||
let new_pos = block_pos.offset_dir(dir.to_offset(), 2);
|
||||
@@ -380,14 +355,12 @@ pub async fn try_move(world: &Arc<World>, block: &Block, block_pos: &BlockPos) {
|
||||
}
|
||||
}
|
||||
}
|
||||
world
|
||||
.add_synced_block_event(*block_pos, r#type, dir.to_index())
|
||||
.await;
|
||||
world.add_synced_block_event(*block_pos, r#type, dir.to_index());
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
async fn move_piston(
|
||||
fn move_piston(
|
||||
world: &Arc<World>,
|
||||
dir: BlockDirection,
|
||||
block_pos: &BlockPos,
|
||||
@@ -396,16 +369,14 @@ async fn move_piston(
|
||||
) -> bool {
|
||||
let extended_pos = block_pos.offset(dir.to_offset());
|
||||
if !extend && world.get_block(&extended_pos) == &Block::PISTON_HEAD {
|
||||
world
|
||||
.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::FORCE_STATE,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
Block::AIR.default_state.id,
|
||||
BlockFlags::FORCE_STATE,
|
||||
);
|
||||
}
|
||||
let mut handler = PistonHandler::new(world, *block_pos, dir, extend);
|
||||
if !handler.calculate_push().await {
|
||||
if !handler.calculate_push() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -427,13 +398,11 @@ async fn move_piston(
|
||||
|
||||
for &broken_block_pos in broken_blocks.iter().rev() {
|
||||
let block_state = world.get_block_state(&broken_block_pos);
|
||||
world
|
||||
.break_block(
|
||||
&broken_block_pos,
|
||||
None,
|
||||
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE,
|
||||
)
|
||||
.await;
|
||||
world.break_block(
|
||||
&broken_block_pos,
|
||||
None,
|
||||
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE,
|
||||
);
|
||||
affected_block_states.push(block_state);
|
||||
}
|
||||
|
||||
@@ -446,9 +415,7 @@ async fn move_piston(
|
||||
props.facing = dir.to_facing();
|
||||
let state = props.to_state_id(&Block::MOVING_PISTON);
|
||||
|
||||
world
|
||||
.set_block_state(&target_pos, state, BlockFlags::MOVED)
|
||||
.await;
|
||||
world.set_block_state(&target_pos, state, BlockFlags::MOVED);
|
||||
|
||||
if let Some(moved_state) = moved_block_states.get(moved_blocks.len() - 1 - index) {
|
||||
world.add_block_entity(Arc::new(PistonBlockEntity {
|
||||
@@ -474,13 +441,11 @@ async fn move_piston(
|
||||
props.facing = dir.to_facing();
|
||||
props.r#type = pistion_type;
|
||||
moved_blocks_map.remove(&extended_pos);
|
||||
world
|
||||
.set_block_state(
|
||||
&extended_pos,
|
||||
props.to_state_id(&Block::MOVING_PISTON),
|
||||
BlockFlags::MOVED,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&extended_pos,
|
||||
props.to_state_id(&Block::MOVING_PISTON),
|
||||
BlockFlags::MOVED,
|
||||
);
|
||||
let mut props = PistonHeadLikeProperties::default(&Block::PISTON_HEAD);
|
||||
props.facing = dir.to_facing();
|
||||
props.r#type = pistion_type;
|
||||
@@ -497,70 +462,56 @@ async fn move_piston(
|
||||
|
||||
let air_state = Block::AIR.default_state.id;
|
||||
for &pos in moved_blocks_map.keys() {
|
||||
world
|
||||
.set_block_state(
|
||||
&pos,
|
||||
air_state,
|
||||
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE | BlockFlags::MOVED,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&pos,
|
||||
air_state,
|
||||
BlockFlags::NOTIFY_LISTENERS | BlockFlags::FORCE_STATE | BlockFlags::MOVED,
|
||||
);
|
||||
}
|
||||
|
||||
for (pos, state) in &moved_blocks_map {
|
||||
world
|
||||
.block_registry
|
||||
.prepare(
|
||||
world,
|
||||
pos,
|
||||
Block::from_state_id(state.id),
|
||||
state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.update_neighbors(pos, None).await;
|
||||
world
|
||||
.block_registry
|
||||
.prepare(
|
||||
world,
|
||||
pos,
|
||||
&Block::AIR,
|
||||
air_state,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.block_registry.prepare(
|
||||
world,
|
||||
pos,
|
||||
Block::from_state_id(state.id),
|
||||
state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
world.update_neighbors(pos, None);
|
||||
world.block_registry.prepare(
|
||||
world,
|
||||
pos,
|
||||
&Block::AIR,
|
||||
air_state,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, &broken_block_pos) in broken_blocks.iter().rev().enumerate() {
|
||||
if let Some(block_state) = affected_block_states.get(i) {
|
||||
world
|
||||
.block_registry
|
||||
.on_state_replaced(
|
||||
world,
|
||||
Block::from_state_id(block_state.id),
|
||||
&broken_block_pos,
|
||||
block_state.id, // ?
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.block_registry
|
||||
.prepare(
|
||||
world,
|
||||
&broken_block_pos,
|
||||
Block::from_state_id(block_state.id),
|
||||
block_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.update_neighbors(&broken_block_pos, None).await;
|
||||
world.block_registry.on_state_replaced(
|
||||
world,
|
||||
Block::from_state_id(block_state.id),
|
||||
&broken_block_pos,
|
||||
block_state.id, // ?
|
||||
false,
|
||||
);
|
||||
world.block_registry.prepare(
|
||||
world,
|
||||
&broken_block_pos,
|
||||
Block::from_state_id(block_state.id),
|
||||
block_state.id,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
world.update_neighbors(&broken_block_pos, None);
|
||||
}
|
||||
}
|
||||
for &moved_block_pos in moved_blocks.iter().rev() {
|
||||
world.update_neighbors(&moved_block_pos, None).await;
|
||||
world.update_neighbors(&moved_block_pos, None);
|
||||
}
|
||||
|
||||
if extend {
|
||||
world.update_neighbors(&extended_pos, None).await;
|
||||
world.update_neighbors(&extended_pos, None);
|
||||
}
|
||||
|
||||
true
|
||||
|
||||
@@ -3,8 +3,8 @@ use pumpkin_data::{Block, FacingExt};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::BrokenArgs;
|
||||
use crate::block::{BlockBehaviour, BlockFuture};
|
||||
|
||||
use super::piston::PistonProps;
|
||||
|
||||
@@ -14,8 +14,8 @@ pub(crate) type MovingPistonProps = pumpkin_data::block_properties::MovingPiston
|
||||
pub struct PistonExtensionBlock;
|
||||
|
||||
impl BlockBehaviour for PistonExtensionBlock {
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
{
|
||||
let props = MovingPistonProps::from_state_id(args.state.id, &Block::MOVING_PISTON);
|
||||
let pos = args
|
||||
.position
|
||||
@@ -25,11 +25,9 @@ impl BlockBehaviour for PistonExtensionBlock {
|
||||
let props = PistonProps::from_state_id(new_state, new_block);
|
||||
if props.extended {
|
||||
// TODO: use player
|
||||
args.world
|
||||
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use pumpkin_data::{Block, FacingExt};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use crate::block::BlockBehaviour;
|
||||
use crate::block::blocks::piston::piston::try_move;
|
||||
use crate::block::{BlockBehaviour, BlockFuture};
|
||||
use crate::block::{BrokenArgs, OnNeighborUpdateArgs};
|
||||
|
||||
use super::piston::PistonProps;
|
||||
@@ -15,50 +15,43 @@ pub(crate) type PistonHeadProperties = pumpkin_data::block_properties::PistonHea
|
||||
pub struct PistonHeadBlock;
|
||||
|
||||
impl BlockBehaviour for PistonHeadBlock {
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD);
|
||||
let pos = args
|
||||
.position
|
||||
.offset(props.facing.opposite().to_block_direction().to_offset());
|
||||
let (new_block, new_state) = args.world.get_block_and_state_id(&pos);
|
||||
if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block {
|
||||
let props = PistonProps::from_state_id(new_state, new_block);
|
||||
if props.extended {
|
||||
// TODO: use player
|
||||
args.world
|
||||
.break_block(&pos, None, BlockFlags::SKIP_DROPS)
|
||||
.await;
|
||||
}
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
let props = PistonHeadProperties::from_state_id(args.state.id, &Block::PISTON_HEAD);
|
||||
let pos = args
|
||||
.position
|
||||
.offset(props.facing.opposite().to_block_direction().to_offset());
|
||||
let (new_block, new_state) = args.world.get_block_and_state_id(&pos);
|
||||
if &Block::PISTON == new_block || &Block::STICKY_PISTON == new_block {
|
||||
let props = PistonProps::from_state_id(new_state, new_block);
|
||||
if props.extended {
|
||||
// TODO: use player
|
||||
args.world.break_block(&pos, None, BlockFlags::SKIP_DROPS);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let head_state_id = args.world.get_block_state_id(args.position);
|
||||
let head_props =
|
||||
PistonHeadProperties::from_state_id(head_state_id, &Block::PISTON_HEAD);
|
||||
if head_props.facing != Facing::Up {
|
||||
return;
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
let head_state_id = args.world.get_block_state_id(args.position);
|
||||
let head_props = PistonHeadProperties::from_state_id(head_state_id, &Block::PISTON_HEAD);
|
||||
if head_props.facing != Facing::Up {
|
||||
return;
|
||||
}
|
||||
let piston_pos = args.position.offset(
|
||||
head_props
|
||||
.facing
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_offset(),
|
||||
);
|
||||
let piston_block = args.world.get_block(&piston_pos);
|
||||
if &Block::PISTON == piston_block || &Block::STICKY_PISTON == piston_block {
|
||||
let up_pos = args
|
||||
.position
|
||||
.offset(head_props.facing.to_block_direction().to_offset());
|
||||
let upper_block = args.world.get_block(&up_pos);
|
||||
if upper_block != &Block::REDSTONE_BLOCK {
|
||||
//Then somebody probably broke the redstone block, try to check if piston should still be extended.
|
||||
try_move(args.world, piston_block, &piston_pos);
|
||||
}
|
||||
let piston_pos = args.position.offset(
|
||||
head_props
|
||||
.facing
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_offset(),
|
||||
);
|
||||
let piston_block = args.world.get_block(&piston_pos);
|
||||
if &Block::PISTON == piston_block || &Block::STICKY_PISTON == piston_block {
|
||||
let up_pos = args
|
||||
.position
|
||||
.offset(head_props.facing.to_block_direction().to_offset());
|
||||
let upper_block = args.world.get_block(&up_pos);
|
||||
if upper_block != &Block::REDSTONE_BLOCK {
|
||||
//Then somebody probably broke the redstone block, try to check if piston should still be extended.
|
||||
try_move(args.world, piston_block, &piston_pos).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use pumpkin_world::tick::TickPriority;
|
||||
use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, blocks::plant::PlantBlockBase};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, blocks::plant::PlantBlockBase};
|
||||
use crate::block::{
|
||||
GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, RandomTickArgs,
|
||||
};
|
||||
@@ -37,105 +37,89 @@ impl BlockBehaviour for BambooBlock {
|
||||
&& args.world.get_block_state(&top.up()).is_air()
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
bone_meal(Arc::clone(args.world), args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
bone_meal(args.world, args.position);
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let (block_below, state_id_below) =
|
||||
args.world.get_block_and_state_id(&args.position.down());
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let (block_below, state_id_below) =
|
||||
args.world.get_block_and_state_id(&args.position.down());
|
||||
|
||||
if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
|
||||
let mut props = BambooLikeProperties::from_state_id(
|
||||
Block::BAMBOO.default_state.id,
|
||||
&Block::BAMBOO,
|
||||
);
|
||||
if block_below == &Block::BAMBOO_SAPLING {
|
||||
return Block::BAMBOO.default_state.id;
|
||||
} else if block_below == &Block::BAMBOO {
|
||||
let props_below =
|
||||
BambooLikeProperties::from_state_id(state_id_below, block_below);
|
||||
if props_below.age > 0 {
|
||||
props.age = 1;
|
||||
}
|
||||
if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
|
||||
let mut props =
|
||||
BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
|
||||
if block_below == &Block::BAMBOO_SAPLING {
|
||||
return Block::BAMBOO.default_state.id;
|
||||
} else if block_below == &Block::BAMBOO {
|
||||
let props_below = BambooLikeProperties::from_state_id(state_id_below, block_below);
|
||||
if props_below.age > 0 {
|
||||
props.age = 1;
|
||||
}
|
||||
} else {
|
||||
let (block_above, state_id_above) =
|
||||
args.world.get_block_and_state_id(&args.position.up());
|
||||
if block_above == &Block::BAMBOO {
|
||||
let props_above =
|
||||
BambooLikeProperties::from_state_id(state_id_above, block_above);
|
||||
props.age = props_above.age;
|
||||
} else {
|
||||
let (block_above, state_id_above) =
|
||||
args.world.get_block_and_state_id(&args.position.up());
|
||||
if block_above == &Block::BAMBOO {
|
||||
let props_above =
|
||||
BambooLikeProperties::from_state_id(state_id_above, block_above);
|
||||
props.age = props_above.age;
|
||||
} else {
|
||||
return Block::BAMBOO_SAPLING.default_state.id;
|
||||
}
|
||||
return Block::BAMBOO_SAPLING.default_state.id;
|
||||
}
|
||||
return props.to_state_id(&Block::BAMBOO);
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
})
|
||||
return props.to_state_id(&Block::BAMBOO);
|
||||
}
|
||||
Block::AIR.default_state.id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
} else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING {
|
||||
args.world
|
||||
.set_block_state(
|
||||
&args.position.down(),
|
||||
Block::BAMBOO.default_state.id,
|
||||
BlockFlags::empty(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
} else if args.world.get_block(&args.position.down()) == &Block::BAMBOO_SAPLING {
|
||||
args.world.set_block_state(
|
||||
&args.position.down(),
|
||||
Block::BAMBOO.default_state.id,
|
||||
BlockFlags::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
if args.direction == BlockDirection::Up && neighbor_block == &Block::BAMBOO {
|
||||
let neighbor_props =
|
||||
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block);
|
||||
let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if neighbor_props.age > props.age {
|
||||
props.age = match props.age {
|
||||
0 => 1,
|
||||
_ => 0,
|
||||
};
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
if args.direction == BlockDirection::Up && neighbor_block == &Block::BAMBOO {
|
||||
let neighbor_props =
|
||||
BambooLikeProperties::from_state_id(args.neighbor_state_id, neighbor_block);
|
||||
let mut props = BambooLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if neighbor_props.age > props.age {
|
||||
props.age = match props.age {
|
||||
0 => 1,
|
||||
_ => 0,
|
||||
};
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if rand::rng().random_range(0..=3) == 0 {
|
||||
update_leaves_and_grow(args.world.clone(), args.position).await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if rand::rng().random_range(0..=3) == 0 {
|
||||
update_leaves_and_grow(args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
|
||||
fn update_leaves_and_grow(world: &Arc<World>, position: &BlockPos) {
|
||||
let above_pos = position.up();
|
||||
let below_pos = position.down();
|
||||
let two_below_pos = position.down_height(2);
|
||||
@@ -152,7 +136,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
let bamboo_count = count_bamboo_below(&world, position);
|
||||
let bamboo_count = count_bamboo_below(world, position);
|
||||
if bamboo_count >= 16 {
|
||||
return;
|
||||
}
|
||||
@@ -178,20 +162,16 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
|
||||
BambooLikeProperties::from_state_id(state_id_two_below, block_two_below);
|
||||
props_two_below.leaves = BambooLeaves::None;
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
&below_pos,
|
||||
props_below.to_state_id(block_below),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world
|
||||
.set_block_state(
|
||||
&two_below_pos,
|
||||
props_two_below.to_state_id(block_two_below),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&below_pos,
|
||||
props_below.to_state_id(block_below),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
world.set_block_state(
|
||||
&two_below_pos,
|
||||
props_two_below.to_state_id(block_two_below),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +181,7 @@ async fn update_leaves_and_grow(world: Arc<World>, position: &BlockPos) {
|
||||
!((bamboo_count < 11 || rand::rng().random::<f32>() >= 0.25) && bamboo_count != 15),
|
||||
);
|
||||
|
||||
world
|
||||
.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
world.set_block_state(&above_pos, props.to_state_id(block), BlockFlags::NOTIFY_ALL);
|
||||
}
|
||||
|
||||
fn count_bamboo_below(world: &World, pos: &BlockPos) -> usize {
|
||||
@@ -236,12 +214,12 @@ fn count_bamboo_above(world: &World, pos: &BlockPos) -> usize {
|
||||
bamboo_count
|
||||
}
|
||||
|
||||
async fn bone_meal(world: Arc<World>, position: &BlockPos) {
|
||||
let bamboo_below = count_bamboo_below(&world, position);
|
||||
fn bone_meal(world: &Arc<World>, position: &BlockPos) {
|
||||
let bamboo_below = count_bamboo_below(world, position);
|
||||
|
||||
let growth_amount = rand::rng().random_range(1..=2);
|
||||
|
||||
for (bamboo_above, _) in (count_bamboo_above(&world, position)..).zip(0..growth_amount) {
|
||||
for (bamboo_above, _) in (count_bamboo_above(world, position)..).zip(0..growth_amount) {
|
||||
let current_total_height = bamboo_above + bamboo_below + 1;
|
||||
|
||||
// `next_pos` is the topmost bamboo of the stalk, so the free space we grow into is the
|
||||
@@ -261,7 +239,7 @@ async fn bone_meal(world: Arc<World>, position: &BlockPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_leaves_and_grow(Arc::clone(&world), &next_pos).await;
|
||||
update_leaves_and_grow(world, &next_pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnNeighborUpdateArgs, blocks::plant::PlantBlockBase,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnNeighborUpdateArgs,
|
||||
blocks::plant::PlantBlockBase,
|
||||
};
|
||||
|
||||
#[pumpkin_block("minecraft:bamboo_sapling")]
|
||||
@@ -24,71 +24,63 @@ impl BlockBehaviour for BambooSaplingBlock {
|
||||
&& args.world.get_block_state(&above).is_air()
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
grow_bamboo(args.world, args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
{
|
||||
grow_bamboo(args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
if args.block == &Block::BAMBOO_SAPLING
|
||||
&& args.world.get_block(&args.position.up()) == &Block::BAMBOO
|
||||
{
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Block::BAMBOO.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Block::BAMBOO.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
if args.direction == BlockDirection::Up
|
||||
&& args.world.get_block(args.neighbor_position) == &Block::BAMBOO
|
||||
{
|
||||
return Block::BAMBOO.default_state.id;
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !<Self as PlantBlockBase>::can_place_at(self, args.world, args.position) {
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
if args.direction == BlockDirection::Up
|
||||
&& args.world.get_block(args.neighbor_position) == &Block::BAMBOO
|
||||
{
|
||||
return Block::BAMBOO.default_state.id;
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: crate::block::RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state_above = args.world.get_block_state(&args.position.up());
|
||||
if !state_above.is_air() || rand::rng().random_range(0..3) > 0 {
|
||||
return;
|
||||
}
|
||||
grow_bamboo(args.world, args.position).await;
|
||||
})
|
||||
fn random_tick(&self, args: crate::block::RandomTickArgs<'_>) {
|
||||
let state_above = args.world.get_block_state(&args.position.up());
|
||||
if !state_above.is_air() || rand::rng().random_range(0..3) > 0 {
|
||||
return;
|
||||
}
|
||||
grow_bamboo(args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
async fn grow_bamboo(world: &std::sync::Arc<crate::world::World>, position: &BlockPos) {
|
||||
fn grow_bamboo(world: &std::sync::Arc<crate::world::World>, position: &BlockPos) {
|
||||
let mut props =
|
||||
BambooLikeProperties::from_state_id(Block::BAMBOO.default_state.id, &Block::BAMBOO);
|
||||
props.leaves = BambooLeaves::Small;
|
||||
world
|
||||
.set_block_state(
|
||||
&position.up(),
|
||||
props.to_state_id(&Block::BAMBOO),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&position.up(),
|
||||
props.to_state_id(&Block::BAMBOO),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
|
||||
impl PlantBlockBase for BambooSaplingBlock {
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::block::blocks::plant::big_dripleaf_stem::{
|
||||
};
|
||||
use crate::block::blocks::redstone::block_receives_redstone_power;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnEntityStepArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnEntityStepArgs,
|
||||
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::ai::pathfinder::node::Coordinate;
|
||||
@@ -30,12 +30,12 @@ use rand::RngExt;
|
||||
pub struct BigDripleafBlock;
|
||||
|
||||
impl BlockBehaviour for BigDripleafBlock {
|
||||
fn on_entity_step<'a>(&'a self, args: OnEntityStepArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn on_entity_step(&self, args: OnEntityStepArgs<'_>) {
|
||||
{
|
||||
let props = BigDripleafLikeProperties::from_state_id(args.state.id, args.block);
|
||||
if props.tilt == Tilt::None
|
||||
&& can_entity_tilt(args.position, args.entity)
|
||||
&& !block_receives_redstone_power(args.world, args.position).await
|
||||
&& !block_receives_redstone_power(args.world, args.position)
|
||||
{
|
||||
set_tilt_and_schedule_tick(
|
||||
args.state.id,
|
||||
@@ -43,96 +43,81 @@ impl BlockBehaviour for BigDripleafBlock {
|
||||
args.position,
|
||||
Tilt::Unstable,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
if block_receives_redstone_power(args.world, args.position).await {
|
||||
reset_tilt(state.id, args.world, args.position).await;
|
||||
} else {
|
||||
let props =
|
||||
BigDripleafLikeProperties::from_state_id(state.id, &Block::BIG_DRIPLEAF);
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let props = BigDripleafLikeProperties::from_state_id(state.id, &Block::BIG_DRIPLEAF);
|
||||
|
||||
if props.tilt == Tilt::Unstable {
|
||||
set_tilt_and_schedule_tick(
|
||||
state.id,
|
||||
args.world,
|
||||
args.position,
|
||||
Tilt::Partial,
|
||||
Some(Sound::BlockBigDripleafTiltDown),
|
||||
)
|
||||
.await;
|
||||
} else if props.tilt == Tilt::Partial {
|
||||
set_tilt_and_schedule_tick(
|
||||
state.id,
|
||||
args.world,
|
||||
args.position,
|
||||
Tilt::Full,
|
||||
Some(Sound::BlockBigDripleafTiltDown),
|
||||
)
|
||||
.await;
|
||||
} else if props.tilt == Tilt::Full {
|
||||
reset_tilt(state.id, args.world, args.position).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
if props.tilt == Tilt::Unstable {
|
||||
set_tilt_and_schedule_tick(
|
||||
state.id,
|
||||
args.world,
|
||||
args.position,
|
||||
Tilt::Partial,
|
||||
Some(Sound::BlockBigDripleafTiltDown),
|
||||
);
|
||||
} else if props.tilt == Tilt::Partial {
|
||||
set_tilt_and_schedule_tick(
|
||||
state.id,
|
||||
args.world,
|
||||
args.position,
|
||||
Tilt::Full,
|
||||
Some(Sound::BlockBigDripleafTiltDown),
|
||||
);
|
||||
} else if props.tilt == Tilt::Full {
|
||||
reset_tilt(state.id, args.world, args.position);
|
||||
}
|
||||
}
|
||||
//TODO: onProjectileHit
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let (support_block, support_block_state_id) =
|
||||
args.world.get_block_and_state_id(&args.position.down());
|
||||
let facing = if support_block == &Block::BIG_DRIPLEAF {
|
||||
get_dripleaf_facing_dir(support_block_state_id)
|
||||
} else {
|
||||
args.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite()
|
||||
};
|
||||
let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let (support_block, support_block_state_id) =
|
||||
args.world.get_block_and_state_id(&args.position.down());
|
||||
let facing = if support_block == &Block::BIG_DRIPLEAF {
|
||||
get_dripleaf_facing_dir(support_block_state_id)
|
||||
} else {
|
||||
args.player
|
||||
.living_entity
|
||||
.entity
|
||||
.get_horizontal_facing()
|
||||
.opposite()
|
||||
};
|
||||
let mut dripleaf_props = BigDripleafLikeProperties::default(args.block);
|
||||
|
||||
dripleaf_props.facing = facing;
|
||||
dripleaf_props.waterlogged = args.replacing.water_source();
|
||||
dripleaf_props.facing = facing;
|
||||
dripleaf_props.waterlogged = args.replacing.water_source();
|
||||
|
||||
dripleaf_props.to_state_id(args.block)
|
||||
})
|
||||
dripleaf_props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
fn on_neighbor_update<'a>(&'a self, args: OnNeighborUpdateArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if block_receives_redstone_power(args.world, args.position).await {
|
||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||
{
|
||||
if block_receives_redstone_power(args.world, args.position) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
reset_tilt(state_id, args.world, args.position).await;
|
||||
reset_tilt(state_id, args.world, args.position);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// if leaf is placed on top of another leaf, turn the lower one into a stem.
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn placed(&self, args: PlacedArgs<'_>) {
|
||||
{
|
||||
let support_pos = args.position.down();
|
||||
let (support_block, support_state_id) = args.world.get_block_and_state_id(&support_pos);
|
||||
if support_block == &Block::BIG_DRIPLEAF {
|
||||
@@ -145,30 +130,27 @@ impl BlockBehaviour for BigDripleafBlock {
|
||||
|
||||
dripleaf_stem_props.facing = old_dripleaf_props.facing;
|
||||
dripleaf_stem_props.waterlogged = old_dripleaf_props.waterlogged;
|
||||
args.world
|
||||
.set_block_state(
|
||||
&support_pos,
|
||||
dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM),
|
||||
BlockFlags::empty(),
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
&support_pos,
|
||||
dripleaf_stem_props.to_state_id(&Block::BIG_DRIPLEAF_STEM),
|
||||
BlockFlags::empty(),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// if the leaf is broken, turn the stem below into a leaf.
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { handle_big_dripleaf_breaking(args.world, args.position).await })
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
handle_big_dripleaf_breaking(args.world, args.position);
|
||||
}
|
||||
}
|
||||
async fn set_tilt_and_schedule_tick(
|
||||
fn set_tilt_and_schedule_tick(
|
||||
state_id: BlockStateId,
|
||||
world: &Arc<World>,
|
||||
pos: &BlockPos,
|
||||
tilt: Tilt,
|
||||
sound_wrapper: Option<Sound>,
|
||||
) {
|
||||
set_tilt(state_id, world, pos, tilt).await;
|
||||
set_tilt(state_id, world, pos, tilt);
|
||||
if let Some(tilt_sound) = sound_wrapper {
|
||||
play_tilt_sound(world, pos, tilt_sound);
|
||||
}
|
||||
@@ -186,30 +168,30 @@ async fn set_tilt_and_schedule_tick(
|
||||
);
|
||||
}
|
||||
}
|
||||
fn play_tilt_sound(world: &Arc<World>, pos: &BlockPos, tilt_sound: Sound) {
|
||||
let pitch = rand::rng().random_range(0.8f32..1.2f32);
|
||||
let v = pos.as_vector3();
|
||||
let position = Vector3::new(v.x as f64, v.y as f64, v.z as f64);
|
||||
world.play_sound_fine(tilt_sound, SoundCategory::Blocks, &position, 1f32, pitch);
|
||||
}
|
||||
async fn reset_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos) {
|
||||
set_tilt(state_id, world, pos, Tilt::None).await;
|
||||
|
||||
fn reset_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos) {
|
||||
set_tilt(state_id, world, pos, Tilt::None);
|
||||
let props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
|
||||
if props.tilt != Tilt::None {
|
||||
play_tilt_sound(world, pos, Sound::BlockBigDripleafTiltUp);
|
||||
}
|
||||
}
|
||||
async fn set_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos, new_tilt: Tilt) {
|
||||
|
||||
fn set_tilt(state_id: BlockStateId, world: &Arc<World>, pos: &BlockPos, new_tilt: Tilt) {
|
||||
let mut props = BigDripleafLikeProperties::from_state_id(state_id, &Block::BIG_DRIPLEAF);
|
||||
props.tilt = new_tilt;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::BIG_DRIPLEAF),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
//todo GameEvents?
|
||||
world.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::BIG_DRIPLEAF),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
|
||||
fn play_tilt_sound(world: &Arc<World>, pos: &BlockPos, tilt_sound: Sound) {
|
||||
let pitch = rand::rng().random_range(0.8f32..1.2f32);
|
||||
let v = pos.as_vector3();
|
||||
let position = Vector3::new(v.x as f64, v.y as f64, v.z as f64);
|
||||
world.play_sound_fine(tilt_sound, SoundCategory::Blocks, &position, 1f32, pitch);
|
||||
}
|
||||
fn can_entity_tilt<T: EntityBase + ?Sized>(pos: &BlockPos, entity: &T) -> bool {
|
||||
entity.get_entity().on_ground.load(Ordering::Relaxed)
|
||||
@@ -229,8 +211,7 @@ impl PlantBlockBase for BigDripleafBlock {
|
||||
let support_block = block_accessor.get_block(pos);
|
||||
can_plant_dripleaf_on_top(support_block)
|
||||
}
|
||||
#[allow(clippy::unused_async_trait_impl)]
|
||||
async fn get_state_for_neighbor_update(
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
block_accessor: &dyn BlockAccessor,
|
||||
block_pos: &BlockPos,
|
||||
|
||||
@@ -2,9 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::block::blocks::plant::PlantBlockBase;
|
||||
use crate::block::blocks::plant::big_dripleaf::can_plant_dripleaf_on_top;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, BrokenArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
|
||||
use crate::world::World;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockStateId;
|
||||
@@ -25,22 +23,19 @@ impl BlockBehaviour for BigDripleafStemBlock {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move { handle_big_dripleaf_breaking(args.world, args.position).await })
|
||||
fn broken(&self, args: BrokenArgs<'_>) {
|
||||
handle_big_dripleaf_breaking(args.world, args.position);
|
||||
}
|
||||
}
|
||||
impl PlantBlockBase for BigDripleafStemBlock {
|
||||
@@ -49,8 +44,7 @@ impl PlantBlockBase for BigDripleafStemBlock {
|
||||
can_plant_dripleaf_on_top(support_block)
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async_trait_impl)]
|
||||
async fn get_state_for_neighbor_update(
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
block_accessor: &dyn BlockAccessor,
|
||||
block_pos: &BlockPos,
|
||||
@@ -69,7 +63,7 @@ impl PlantBlockBase for BigDripleafStemBlock {
|
||||
block_state
|
||||
}
|
||||
}
|
||||
pub async fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPos) {
|
||||
pub fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPos) {
|
||||
let support_pos = position.down();
|
||||
let (support_block, support_state_id) = world.get_block_and_state_id(&support_pos);
|
||||
if support_block == &Block::BIG_DRIPLEAF_STEM {
|
||||
@@ -79,12 +73,10 @@ pub async fn handle_big_dripleaf_breaking(world: &Arc<World>, position: &BlockPo
|
||||
let mut dripleaf_props = BigDripleafLikeProperties::default(&Block::BIG_DRIPLEAF);
|
||||
dripleaf_props.facing = dripleaf_stem_props.facing;
|
||||
dripleaf_props.waterlogged = dripleaf_stem_props.waterlogged;
|
||||
world
|
||||
.set_block_state(
|
||||
&support_pos,
|
||||
dripleaf_props.to_state_id(&Block::BIG_DRIPLEAF),
|
||||
BlockFlags::empty(),
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
&support_pos,
|
||||
dripleaf_props.to_state_id(&Block::BIG_DRIPLEAF),
|
||||
BlockFlags::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use pumpkin_data::BlockId;
|
||||
use pumpkin_data::BlockStateId;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
blocks::plant::PlantBlockBase,
|
||||
};
|
||||
|
||||
@@ -19,19 +19,16 @@ impl BlockBehaviour for BushBlock {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,105 +10,85 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnEntityCollisionArgs, OnScheduledTickArgs, RandomTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnEntityCollisionArgs,
|
||||
OnScheduledTickArgs, RandomTickArgs,
|
||||
};
|
||||
|
||||
#[pumpkin_block("minecraft:cactus")]
|
||||
pub struct CactusBlock;
|
||||
|
||||
impl BlockBehaviour for CactusBlock {
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_place_at(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let block_up = args.position.up();
|
||||
if args.world.get_block_state(&block_up).is_air() {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let mut props = CactusLikeProperties::from_state_id(state.id, args.block);
|
||||
let age = props.age;
|
||||
let mut i = 1;
|
||||
while args.world.get_block(&args.position.down_height(i)) == &Block::CACTUS {
|
||||
i += 1;
|
||||
if 1 == 3 && age == 15 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if age == 8 && can_place_at(args.world.as_ref(), &block_up) {
|
||||
let d = if i >= 3 { 0.25 } else { 0.1 };
|
||||
if rand::rng().random_range(0.0..1.0) <= d {
|
||||
args.world
|
||||
.set_block_state(
|
||||
&block_up,
|
||||
Block::CACTUS_FLOWER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else if age == 15 && i < 3 {
|
||||
args.world
|
||||
.set_block_state(
|
||||
&block_up,
|
||||
Block::CACTUS.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
let mut new_props = CactusLikeProperties::default(&Block::CACTUS);
|
||||
new_props.age = 0;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
new_props.to_state_id(&Block::CACTUS),
|
||||
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
|
||||
)
|
||||
.await;
|
||||
args.world
|
||||
.update_neighbor(args.position, &Block::CACTUS)
|
||||
.await;
|
||||
}
|
||||
if age < 15 {
|
||||
props.age = age + 1;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(&Block::CACTUS),
|
||||
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
|
||||
)
|
||||
.await;
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let block_up = args.position.up();
|
||||
if args.world.get_block_state(&block_up).is_air() {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let mut props = CactusLikeProperties::from_state_id(state.id, args.block);
|
||||
let age = props.age;
|
||||
let mut i = 1;
|
||||
while args.world.get_block(&args.position.down_height(i)) == &Block::CACTUS {
|
||||
i += 1;
|
||||
if 1 == 3 && age == 15 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
args.entity
|
||||
.damage(args.entity, 1.0, DamageType::CACTUS)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
if age == 8 && can_place_at(args.world.as_ref(), &block_up) {
|
||||
let d = if i >= 3 { 0.25 } else { 0.1 };
|
||||
if rand::rng().random_range(0.0..1.0) <= d {
|
||||
args.world.set_block_state(
|
||||
&block_up,
|
||||
Block::CACTUS_FLOWER.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
} else if age == 15 && i < 3 {
|
||||
args.world.set_block_state(
|
||||
&block_up,
|
||||
Block::CACTUS.default_state.id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
let mut new_props = CactusLikeProperties::default(&Block::CACTUS);
|
||||
new_props.age = 0;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
new_props.to_state_id(&Block::CACTUS),
|
||||
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
|
||||
);
|
||||
}
|
||||
if age < 15 {
|
||||
props.age = age + 1;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(&Block::CACTUS),
|
||||
BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.state_id
|
||||
})
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
{
|
||||
args.entity.damage(args.entity, 1.0, DamageType::CACTUS);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_place_at(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::block::blocks::plant::PlantBlockBase;
|
||||
use crate::block::{BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs};
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::tag::{self, Taggable};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||
@@ -14,19 +14,16 @@ impl BlockBehaviour for CactusFlowerBlock {
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +40,7 @@ impl PlantBlockBase for CactusFlowerBlock {
|
||||
}
|
||||
false
|
||||
}
|
||||
#[allow(clippy::unused_async_trait_impl)]
|
||||
async fn get_state_for_neighbor_update(
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
block_accessor: &dyn BlockAccessor,
|
||||
block_pos: &BlockPos,
|
||||
|
||||
@@ -17,8 +17,8 @@ use rand::RngExt;
|
||||
use super::chorus_plant;
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnScheduledTickArgs, RandomTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
RandomTickArgs,
|
||||
},
|
||||
world::World,
|
||||
};
|
||||
@@ -40,156 +40,144 @@ impl BlockBehaviour for ChorusFlowerBlock {
|
||||
can_survive(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if args.direction != BlockDirection::Up && !can_survive(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if args.direction != BlockDirection::Up && !can_survive(args.world, args.position) {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !can_survive(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
if !can_survive(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let above = args.position.up();
|
||||
let max_y = args.world.dimension.min_y + args.world.dimension.height - 1;
|
||||
if args.world.get_block(&above).default_state.is_air() && above.0.y <= max_y {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let state_props = ChorusFlowerLikeProperties::from_state_id(state_id, args.block);
|
||||
let current_age = state_props.age;
|
||||
if current_age < DEAD_AGE {
|
||||
let mut grow_upwards = false;
|
||||
let mut pillar_on_support_block = false;
|
||||
let below_pos = args.position.down();
|
||||
let (below_block, _) = args.world.get_block_and_state(&below_pos);
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
let above = args.position.up();
|
||||
let max_y = args.world.dimension.min_y + args.world.dimension.height - 1;
|
||||
if args.world.get_block(&above).default_state.is_air() && above.0.y <= max_y {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let state_props = ChorusFlowerLikeProperties::from_state_id(state_id, args.block);
|
||||
let current_age = state_props.age;
|
||||
if current_age < DEAD_AGE {
|
||||
let mut grow_upwards = false;
|
||||
let mut pillar_on_support_block = false;
|
||||
let below_pos = args.position.down();
|
||||
let (below_block, _) = args.world.get_block_and_state(&below_pos);
|
||||
|
||||
if below_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
|
||||
grow_upwards = true;
|
||||
} else if below_block == &Block::CHORUS_PLANT {
|
||||
let mut height = 1;
|
||||
for _ in 0..4 {
|
||||
let test_pos = args.position.offset(Vector3::new(0, -(height + 1), 0));
|
||||
let (test_block, _) = args.world.get_block_and_state(&test_pos);
|
||||
if test_block != &Block::CHORUS_PLANT {
|
||||
if test_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER)
|
||||
{
|
||||
pillar_on_support_block = true;
|
||||
}
|
||||
break;
|
||||
if below_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
|
||||
grow_upwards = true;
|
||||
} else if below_block == &Block::CHORUS_PLANT {
|
||||
let mut height = 1;
|
||||
for _ in 0..4 {
|
||||
let test_pos = args.position.offset(Vector3::new(0, -(height + 1), 0));
|
||||
let (test_block, _) = args.world.get_block_and_state(&test_pos);
|
||||
if test_block != &Block::CHORUS_PLANT {
|
||||
if test_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_FLOWER) {
|
||||
pillar_on_support_block = true;
|
||||
}
|
||||
height += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
let max_chance = if pillar_on_support_block { 5 } else { 4 };
|
||||
if height < 2 || height <= rand::rng().random_range(0..max_chance) {
|
||||
grow_upwards = true;
|
||||
}
|
||||
} else if below_block.default_state.is_air() {
|
||||
grow_upwards = true;
|
||||
height += 1;
|
||||
}
|
||||
|
||||
let above_2 = args.position.offset(Vector3::new(0, 2, 0));
|
||||
if grow_upwards
|
||||
&& all_neighbors_empty(args.world.as_ref(), &above, None)
|
||||
&& args.world.get_block(&above_2).default_state.is_air()
|
||||
{
|
||||
let max_chance = if pillar_on_support_block { 5 } else { 4 };
|
||||
if height < 2 || height <= rand::rng().random_range(0..max_chance) {
|
||||
grow_upwards = true;
|
||||
}
|
||||
} else if below_block.default_state.is_air() {
|
||||
grow_upwards = true;
|
||||
}
|
||||
|
||||
let above_2 = args.position.offset(Vector3::new(0, 2, 0));
|
||||
if grow_upwards
|
||||
&& all_neighbors_empty(args.world.as_ref(), &above, None)
|
||||
&& args.world.get_block(&above_2).default_state.is_air()
|
||||
{
|
||||
let plant_state_id = chorus_plant::get_state_with_connections(
|
||||
args.world.as_ref(),
|
||||
&Block::CHORUS_PLANT,
|
||||
args.position,
|
||||
);
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
plant_state_id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
place_grown_flower(args.world, &above, current_age);
|
||||
} else if current_age < 4 {
|
||||
let mut num_branch_attempts = rand::rng().random_range(0..4);
|
||||
if pillar_on_support_block {
|
||||
num_branch_attempts += 1;
|
||||
}
|
||||
|
||||
let mut created_branch = false;
|
||||
|
||||
for _ in 0..num_branch_attempts {
|
||||
let direction = HORIZONTAL_DIRECTIONS[rand::rng().random_range(0..4)];
|
||||
let target = args.position.offset(direction.to_offset());
|
||||
let target_below = target.down();
|
||||
|
||||
if args.world.get_block(&target).default_state.is_air()
|
||||
&& args.world.get_block(&target_below).default_state.is_air()
|
||||
&& all_neighbors_empty(
|
||||
args.world.as_ref(),
|
||||
&target,
|
||||
Some(direction.opposite()),
|
||||
)
|
||||
{
|
||||
place_grown_flower(args.world, &target, current_age + 1);
|
||||
created_branch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if created_branch {
|
||||
let plant_state_id = chorus_plant::get_state_with_connections(
|
||||
args.world.as_ref(),
|
||||
&Block::CHORUS_PLANT,
|
||||
args.position,
|
||||
);
|
||||
args.world
|
||||
.set_block_state(args.position, plant_state_id, BlockFlags::NOTIFY_ALL)
|
||||
.await;
|
||||
place_grown_flower(args.world, &above, current_age).await;
|
||||
} else if current_age < 4 {
|
||||
let mut num_branch_attempts = rand::rng().random_range(0..4);
|
||||
if pillar_on_support_block {
|
||||
num_branch_attempts += 1;
|
||||
}
|
||||
|
||||
let mut created_branch = false;
|
||||
|
||||
for _ in 0..num_branch_attempts {
|
||||
let direction = HORIZONTAL_DIRECTIONS[rand::rng().random_range(0..4)];
|
||||
let target = args.position.offset(direction.to_offset());
|
||||
let target_below = target.down();
|
||||
|
||||
if args.world.get_block(&target).default_state.is_air()
|
||||
&& args.world.get_block(&target_below).default_state.is_air()
|
||||
&& all_neighbors_empty(
|
||||
args.world.as_ref(),
|
||||
&target,
|
||||
Some(direction.opposite()),
|
||||
)
|
||||
{
|
||||
place_grown_flower(args.world, &target, current_age + 1).await;
|
||||
created_branch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if created_branch {
|
||||
let plant_state_id = chorus_plant::get_state_with_connections(
|
||||
args.world.as_ref(),
|
||||
&Block::CHORUS_PLANT,
|
||||
args.position,
|
||||
);
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
plant_state_id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
place_dead_flower(args.world, args.position).await;
|
||||
}
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
plant_state_id,
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
} else {
|
||||
place_dead_flower(args.world, args.position).await;
|
||||
place_dead_flower(args.world, args.position);
|
||||
}
|
||||
} else {
|
||||
place_dead_flower(args.world, args.position);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn place_grown_flower(world: &Arc<World>, pos: &BlockPos, age: u8) {
|
||||
pub fn place_grown_flower(world: &Arc<World>, pos: &BlockPos, age: u8) {
|
||||
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
|
||||
props.age = age;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::CHORUS_FLOWER),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::CHORUS_FLOWER),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
world.sync_world_event(WorldEvent::SoundChorusGrow, *pos, 0);
|
||||
}
|
||||
|
||||
pub async fn place_dead_flower(world: &Arc<World>, pos: &BlockPos) {
|
||||
pub fn place_dead_flower(world: &Arc<World>, pos: &BlockPos) {
|
||||
let mut props = ChorusFlowerLikeProperties::default(&Block::CHORUS_FLOWER);
|
||||
props.age = DEAD_AGE;
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::CHORUS_FLOWER),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
props.to_state_id(&Block::CHORUS_FLOWER),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
world.sync_world_event(WorldEvent::SoundChorusDeath, *pos, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,68 +11,59 @@ use pumpkin_world::{
|
||||
};
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
};
|
||||
|
||||
#[pumpkin_block("minecraft:chorus_plant")]
|
||||
pub struct ChorusPlantBlock;
|
||||
|
||||
impl BlockBehaviour for ChorusPlantBlock {
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
// Compute all 6 face connections immediately so the placed block visually connects to its neighbors.
|
||||
get_state_with_connections(args.world, args.block, args.position)
|
||||
})
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
// Compute all 6 face connections immediately so the placed block visually connects to its neighbors.
|
||||
get_state_with_connections(args.world, args.block, args.position)
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
can_survive(args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
if !can_survive(args.world, args.position) {
|
||||
// Schedule delayed destruction so the whole plant cascades down.
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
return args.state_id;
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
if !can_survive(args.world, args.position) {
|
||||
// Schedule delayed destruction so the whole plant cascades down.
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 1, TickPriority::Normal);
|
||||
return args.state_id;
|
||||
}
|
||||
|
||||
// Update the single face connection for the direction that changed.
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
let connect = neighbor_block == &Block::CHORUS_PLANT
|
||||
|| neighbor_block == &Block::CHORUS_FLOWER
|
||||
|| (args.direction == BlockDirection::Down
|
||||
&& neighbor_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_PLANT));
|
||||
// Update the single face connection for the direction that changed.
|
||||
let neighbor_block = args.world.get_block(args.neighbor_position);
|
||||
let connect = neighbor_block == &Block::CHORUS_PLANT
|
||||
|| neighbor_block == &Block::CHORUS_FLOWER
|
||||
|| (args.direction == BlockDirection::Down
|
||||
&& neighbor_block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_CHORUS_PLANT));
|
||||
|
||||
let mut props =
|
||||
BrownMushroomBlockLikeProperties::from_state_id(args.state_id, args.block);
|
||||
match args.direction {
|
||||
BlockDirection::Down => props.down = connect,
|
||||
BlockDirection::Up => props.up = connect,
|
||||
BlockDirection::North => props.north = connect,
|
||||
BlockDirection::South => props.south = connect,
|
||||
BlockDirection::East => props.east = connect,
|
||||
BlockDirection::West => props.west = connect,
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
})
|
||||
let mut props = BrownMushroomBlockLikeProperties::from_state_id(args.state_id, args.block);
|
||||
match args.direction {
|
||||
BlockDirection::Down => props.down = connect,
|
||||
BlockDirection::Up => props.up = connect,
|
||||
BlockDirection::North => props.north = connect,
|
||||
BlockDirection::South => props.south = connect,
|
||||
BlockDirection::East => props.east = connect,
|
||||
BlockDirection::West => props.west = connect,
|
||||
}
|
||||
props.to_state_id(args.block)
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// Destroy if unsupported; breaking propagates neighbor-update callbacks
|
||||
// to connected chorus blocks, which schedule their own ticks.
|
||||
if !can_survive(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||
// Destroy if unsupported; breaking propagates neighbor-update callbacks
|
||||
// to connected chorus blocks, which schedule their own ticks.
|
||||
if !can_survive(args.world.as_ref(), args.position) {
|
||||
args.world
|
||||
.break_block(args.position, None, BlockFlags::empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BonemealArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
OnPlaceArgs, RandomTickArgs,
|
||||
BlockBehaviour, BonemealArgs, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnPlaceArgs,
|
||||
RandomTickArgs,
|
||||
};
|
||||
use crate::entity::EntityBase;
|
||||
|
||||
@@ -53,57 +53,49 @@ impl BlockBehaviour for CocoaBlock {
|
||||
false
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let mut props = CocoaProperties::default(args.block);
|
||||
props.age = 0;
|
||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||
let mut props = CocoaProperties::default(args.block);
|
||||
props.age = 0;
|
||||
|
||||
let directions = args.player.get_entity().get_entity_facing_order();
|
||||
for dir in directions {
|
||||
if let Some(facing) = dir.to_horizontal_facing()
|
||||
&& Self::can_survive(args.world, args.position, facing)
|
||||
{
|
||||
props.facing = facing;
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
}
|
||||
|
||||
Block::AIR.default_state.id
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = CocoaProperties::from_state_id(args.state_id, args.block);
|
||||
if args.direction == props.facing.to_block_direction()
|
||||
&& !Self::can_survive(args.world, args.position, props.facing)
|
||||
let directions = args.player.get_entity().get_entity_facing_order();
|
||||
for dir in directions {
|
||||
if let Some(facing) = dir.to_horizontal_facing()
|
||||
&& Self::can_survive(args.world, args.position, facing)
|
||||
{
|
||||
return Block::AIR.default_state.id;
|
||||
props.facing = facing;
|
||||
return props.to_state_id(args.block);
|
||||
}
|
||||
args.state_id
|
||||
})
|
||||
}
|
||||
|
||||
Block::AIR.default_state.id
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if rand::random::<u8>().is_multiple_of(5) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = CocoaProperties::from_state_id(state_id, args.block);
|
||||
if props.age < MAX_AGE {
|
||||
props.age += 1;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let props = CocoaProperties::from_state_id(args.state_id, args.block);
|
||||
if args.direction == props.facing.to_block_direction()
|
||||
&& !Self::can_survive(args.world, args.position, props.facing)
|
||||
{
|
||||
return Block::AIR.default_state.id;
|
||||
}
|
||||
args.state_id
|
||||
}
|
||||
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if rand::random::<u8>().is_multiple_of(5) {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = CocoaProperties::from_state_id(state_id, args.block);
|
||||
if props.age < MAX_AGE {
|
||||
props.age += 1;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_bonemeal_target(&self, args: BonemealArgs<'_>) -> bool {
|
||||
@@ -115,20 +107,18 @@ impl BlockBehaviour for CocoaBlock {
|
||||
true
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
|
||||
{
|
||||
let mut props = CocoaProperties::from_state_id(args.state_id, args.block);
|
||||
if props.age < MAX_AGE {
|
||||
props.age += 1;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(args.block),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate(
|
||||
|
||||
@@ -6,9 +6,7 @@ use rand::RngExt;
|
||||
|
||||
use crate::block::blocks::plant::PlantBlockBase;
|
||||
use crate::block::blocks::plant::crop::CropBlockBase;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
|
||||
|
||||
type BeetrootProperties = NetherWartLikeProperties;
|
||||
|
||||
@@ -20,37 +18,30 @@ impl BlockBehaviour for BeetrootBlock {
|
||||
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if rand::rng().random_range(0..3) == 0 {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if rand::rng().random_range(0..3) == 0 {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::block::blocks::plant::PlantBlockBase;
|
||||
use crate::block::blocks::plant::crop::CropBlockBase;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:carrots")]
|
||||
pub struct CarrotBlock;
|
||||
@@ -15,35 +13,28 @@ impl BlockBehaviour for CarrotBlock {
|
||||
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockAccessor;
|
||||
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
blocks::plant::PlantBlockBase,
|
||||
};
|
||||
|
||||
@@ -45,27 +45,24 @@ impl BlockBehaviour for AttachedStemBlock {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let props = AttachedStemProperties::from_state_id(args.state_id, args.block);
|
||||
if args.direction.to_horizontal_facing() == Some(props.facing)
|
||||
&& args.neighbor_state_id != Self::get_gourd(args.block).default_state.id
|
||||
{
|
||||
let mut props = StemProperties::default(Self::get_stem(args.block));
|
||||
props.age = 7;
|
||||
return props.to_state_id(Self::get_stem(args.block));
|
||||
}
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
let props = AttachedStemProperties::from_state_id(args.state_id, args.block);
|
||||
if args.direction.to_horizontal_facing() == Some(props.facing)
|
||||
&& args.neighbor_state_id != Self::get_gourd(args.block).default_state.id
|
||||
{
|
||||
let mut props = StemProperties::default(Self::get_stem(args.block));
|
||||
props.age = 7;
|
||||
return props.to_state_id(Self::get_stem(args.block));
|
||||
}
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs,
|
||||
RandomTickArgs,
|
||||
BlockBehaviour, BlockMetadata, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
blocks::plant::{
|
||||
PlantBlockBase,
|
||||
crop::{CropBlockBase, get_available_moisture},
|
||||
@@ -63,90 +62,76 @@ impl BlockBehaviour for StemBlock {
|
||||
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
|
||||
let (_, state) = args.world.get_block_and_state_id(args.position);
|
||||
if StemProperties::from_state_id(state, args.block).age == 7 {
|
||||
BlockBehaviour::random_tick(
|
||||
self,
|
||||
RandomTickArgs {
|
||||
world: args.world,
|
||||
block: args.block,
|
||||
position: args.position,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
|
||||
let (_, state) = args.world.get_block_and_state_id(args.position);
|
||||
if StemProperties::from_state_id(state, args.block).age == 7 {
|
||||
BlockBehaviour::random_tick(
|
||||
self,
|
||||
RandomTickArgs {
|
||||
world: args.world,
|
||||
block: args.block,
|
||||
position: args.position,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO add light level check
|
||||
let f: f32 = get_available_moisture(args.world, args.position, args.block).await;
|
||||
if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 {
|
||||
let (block, state) = args.world.get_block_and_state_id(args.position);
|
||||
let props = StemProperties::from_state_id(state, block);
|
||||
let age = i32::from(props.age);
|
||||
if age < 7 {
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
Self::state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
let dir = BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro(
|
||||
Xoroshiro::from_seed(rand::rng().random()),
|
||||
));
|
||||
let plant_block_pos = args.position.offset(dir.to_offset());
|
||||
let plant_block_state = args.world.get_block_state(&plant_block_pos);
|
||||
let under_block: &Block = args.world.get_block(&plant_block_pos.down());
|
||||
if plant_block_state.is_air()
|
||||
&& (under_block == &Block::FARMLAND
|
||||
|| under_block.has_tag(&tag::Block::MINECRAFT_DIRT))
|
||||
{
|
||||
let attached_stem = Self::get_attached_stem(dir, block);
|
||||
let gourd = Self::get_gourd(block);
|
||||
args.world
|
||||
.set_block_state(
|
||||
&plant_block_pos,
|
||||
gourd.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
attached_stem,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
// TODO add light level check
|
||||
let f: f32 = get_available_moisture(args.world, args.position, args.block);
|
||||
if rand::rng().random_range(0..=(25.0 / f).floor() as i32) == 0 {
|
||||
let (block, state) = args.world.get_block_and_state_id(args.position);
|
||||
let props = StemProperties::from_state_id(state, block);
|
||||
let age = i32::from(props.age);
|
||||
if age < 7 {
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
Self::state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
} else {
|
||||
let dir = BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro(
|
||||
Xoroshiro::from_seed(rand::rng().random()),
|
||||
));
|
||||
let plant_block_pos = args.position.offset(dir.to_offset());
|
||||
let plant_block_state = args.world.get_block_state(&plant_block_pos);
|
||||
let under_block: &Block = args.world.get_block(&plant_block_pos.down());
|
||||
if plant_block_state.is_air()
|
||||
&& (under_block == &Block::FARMLAND
|
||||
|| under_block.has_tag(&tag::Block::MINECRAFT_DIRT))
|
||||
{
|
||||
let attached_stem = Self::get_attached_stem(dir, block);
|
||||
let gourd = Self::get_gourd(block);
|
||||
args.world.set_block_state(
|
||||
&plant_block_pos,
|
||||
gourd.default_state.id,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
attached_stem,
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
|
||||
use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use rand::RngExt;
|
||||
|
||||
use crate::{
|
||||
block::blocks::plant::PlantBlockBase, plugin::api::events::block::block_grow::BlockGrowEvent,
|
||||
world::World,
|
||||
};
|
||||
use crate::{block::blocks::plant::PlantBlockBase, world::World};
|
||||
|
||||
type CropProperties = WheatLikeProperties;
|
||||
type FarmlandProperties = FarmlandLikeProperties;
|
||||
@@ -57,44 +54,25 @@ trait CropBlockBase: PlantBlockBase {
|
||||
self.get_age(state, block) < self.max_age()
|
||||
}
|
||||
|
||||
async fn perform_bonemeal(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
fn perform_bonemeal(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
let (block, state) = world.get_block_and_state_id(pos);
|
||||
let age = self.get_age(state, block);
|
||||
let new_age = (age + self.bonemeal_age_increase()).min(self.max_age());
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, new_age),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, new_age),
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
);
|
||||
}
|
||||
|
||||
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
let (block, state) = world.get_block_and_state_id(pos);
|
||||
let age = self.get_age(state, block);
|
||||
if age < self.max_age() {
|
||||
let f = get_available_moisture(world, pos, block).await;
|
||||
let f = get_available_moisture(world, pos, block);
|
||||
if rand::rng().random_range(0..=(25.0 / f).floor() as i64) == 0 {
|
||||
let mut new_state_id = self.state_with_age(block, state, age + 1);
|
||||
if let Some(server) = world.server.upgrade() {
|
||||
let mut event = BlockGrowEvent::new(
|
||||
world.clone(),
|
||||
block,
|
||||
state,
|
||||
Block::from_state_id(new_state_id),
|
||||
new_state_id,
|
||||
*pos,
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
new_state_id = event.new_state_id;
|
||||
}
|
||||
world
|
||||
.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS)
|
||||
.await;
|
||||
let new_state_id = self.state_with_age(block, state, age + 1);
|
||||
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_NEIGHBORS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +80,7 @@ trait CropBlockBase: PlantBlockBase {
|
||||
//TODO add impl for light level
|
||||
}
|
||||
|
||||
pub async fn get_available_moisture(world: &Arc<World>, pos: &BlockPos, block: &Block) -> f32 {
|
||||
pub fn get_available_moisture(world: &World, pos: &BlockPos, block: &Block) -> f32 {
|
||||
let mut moisture = 1.0;
|
||||
let down_pos = pos.down();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use rand::RngExt;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
blocks::plant::{PlantBlockBase, crop::CropBlockBase},
|
||||
},
|
||||
world::World,
|
||||
@@ -26,25 +26,20 @@ impl BlockBehaviour for NetherWartBlock {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,17 +74,15 @@ impl CropBlockBase for NetherWartBlock {
|
||||
props.to_state_id(block)
|
||||
}
|
||||
|
||||
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
let (block, state) = world.get_block_and_state_id(pos);
|
||||
let age = self.get_age(state, block);
|
||||
if age < self.max_age() && rand::rng().random_range(0..10) == 0 {
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ use pumpkin_macros::pumpkin_block;
|
||||
|
||||
use crate::block::blocks::plant::PlantBlockBase;
|
||||
use crate::block::blocks::plant::crop::CropBlockBase;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs,
|
||||
};
|
||||
use crate::block::{BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, RandomTickArgs};
|
||||
|
||||
#[pumpkin_block("minecraft:potatoes")]
|
||||
pub struct PotatoBlock;
|
||||
@@ -15,35 +13,28 @@ impl BlockBehaviour for PotatoBlock {
|
||||
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as CropBlockBase>::can_plant_on_top(self, args.block_accessor, &args.position.down())
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
|
||||
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
|
||||
OnEntityCollisionArgs, RandomTickArgs, UseWithItemArgs,
|
||||
blocks::plant::{PlantBlockBase, crop::CropBlockBase},
|
||||
registry::BlockActionResult,
|
||||
@@ -31,127 +31,101 @@ impl BlockBehaviour for SweetBerryBushBlock {
|
||||
<Self as CropBlockBase>::is_valid_bonemeal_target(self, args.world, args.position)
|
||||
}
|
||||
|
||||
fn perform_bonemeal<'a>(&'a self, args: crate::block::BonemealArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position).await;
|
||||
})
|
||||
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
|
||||
<Self as CropBlockBase>::perform_bonemeal(self, args.world, args.position);
|
||||
}
|
||||
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
match props.age {
|
||||
2 | 3 => {
|
||||
let index = props.age;
|
||||
props.age = 1;
|
||||
let count: u8 = rand::rng().random_range((index - 1)..=(index));
|
||||
for _ in 0..count {
|
||||
args.world
|
||||
.drop_stack(
|
||||
args.position,
|
||||
ItemStack::new(1, &Item::SWEET_BERRIES), //
|
||||
)
|
||||
.await;
|
||||
}
|
||||
args.world
|
||||
.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(&Block::SWEET_BERRY_BUSH),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
)
|
||||
.await;
|
||||
BlockActionResult::SuccessServer
|
||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let mut props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
match props.age {
|
||||
2 | 3 => {
|
||||
let index = props.age;
|
||||
props.age = 1;
|
||||
let count: u8 = rand::rng().random_range((index - 1)..=(index));
|
||||
for _ in 0..count {
|
||||
args.world.drop_stack(
|
||||
args.position,
|
||||
ItemStack::new(1, &Item::SWEET_BERRIES), //
|
||||
);
|
||||
}
|
||||
_ => BlockActionResult::Pass,
|
||||
args.world.set_block_state(
|
||||
args.position,
|
||||
props.to_state_id(&Block::SWEET_BERRY_BUSH),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
);
|
||||
BlockActionResult::SuccessServer
|
||||
}
|
||||
})
|
||||
_ => BlockActionResult::Pass,
|
||||
}
|
||||
}
|
||||
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
|
||||
if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL {
|
||||
BlockActionResult::Pass
|
||||
} else {
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
}
|
||||
})
|
||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
|
||||
if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL {
|
||||
BlockActionResult::Pass
|
||||
} else {
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
}
|
||||
}
|
||||
|
||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||
<Self as PlantBlockBase>::can_place_at(self, args.block_accessor, args.position)
|
||||
}
|
||||
|
||||
fn get_state_for_neighbor_update<'a>(
|
||||
&'a self,
|
||||
args: GetStateForNeighborUpdateArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
.await
|
||||
})
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
args: GetStateForNeighborUpdateArgs<'_>,
|
||||
) -> BlockStateId {
|
||||
<Self as PlantBlockBase>::get_state_for_neighbor_update(
|
||||
self,
|
||||
args.world,
|
||||
args.position,
|
||||
args.state_id,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = args.entity.get_entity();
|
||||
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||
let entity = args.entity.get_entity();
|
||||
|
||||
let living_entity_opt = args.entity.get_living_entity();
|
||||
let Some(living_entity) = living_entity_opt else {
|
||||
return;
|
||||
};
|
||||
if entity.entity_type == &EntityType::FOX || entity.entity_type == &EntityType::BEE {
|
||||
return;
|
||||
}
|
||||
entity
|
||||
.slow_movement(args.state, Vector3::new(0.8, 0.75, 0.8))
|
||||
.await;
|
||||
let mov = if living_entity.is_player() {
|
||||
living_entity.get_movement()
|
||||
} else {
|
||||
entity.last_pos.load() - entity.pos.load()
|
||||
};
|
||||
let living_entity_opt = args.entity.get_living_entity();
|
||||
let Some(living_entity) = living_entity_opt else {
|
||||
return;
|
||||
};
|
||||
if entity.entity_type == &EntityType::FOX || entity.entity_type == &EntityType::BEE {
|
||||
return;
|
||||
}
|
||||
entity.slow_movement(args.state, Vector3::new(0.8, 0.75, 0.8));
|
||||
let mov = if living_entity.is_player() {
|
||||
living_entity.get_movement()
|
||||
} else {
|
||||
entity.last_pos.load() - entity.pos.load()
|
||||
};
|
||||
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.age == 0 {
|
||||
return;
|
||||
}
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, args.block);
|
||||
if props.age == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if mov.horizontal_length_squared() <= 0.0
|
||||
|| (mov.x.abs() < 0.003 && mov.z.abs() < 0.003)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if mov.horizontal_length_squared() <= 0.0 || (mov.x.abs() < 0.003 && mov.z.abs() < 0.003) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.entity
|
||||
.damage(args.entity, 1.0, DamageType::SWEET_BERRY_BUSH)
|
||||
.await;
|
||||
})
|
||||
args.entity
|
||||
.damage(args.entity, 1.0, DamageType::SWEET_BERRY_BUSH);
|
||||
}
|
||||
|
||||
fn random_tick<'a>(&'a self, args: RandomTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if rand::rng().random_range(0..5) == 0 {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position).await;
|
||||
}
|
||||
})
|
||||
fn random_tick(&self, args: RandomTickArgs<'_>) {
|
||||
if rand::rng().random_range(0..5) == 0 {
|
||||
<Self as CropBlockBase>::random_tick(self, args.world, args.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlantBlockBase for SweetBerryBushBlock {
|
||||
#[allow(clippy::unused_async_trait_impl)]
|
||||
async fn get_state_for_neighbor_update(
|
||||
fn get_state_for_neighbor_update(
|
||||
&self,
|
||||
block_accessor: &dyn BlockAccessor,
|
||||
block_pos: &BlockPos,
|
||||
@@ -192,7 +166,7 @@ impl CropBlockBase for SweetBerryBushBlock {
|
||||
props.to_state_id(block)
|
||||
}
|
||||
|
||||
async fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
fn random_tick(&self, world: &Arc<World>, pos: &BlockPos) {
|
||||
let (block, state) = world.get_block_and_state_id(pos);
|
||||
let age = self.get_age(state, block);
|
||||
if age < self.max_age() {
|
||||
@@ -201,13 +175,11 @@ impl CropBlockBase for SweetBerryBushBlock {
|
||||
if state_above.is_full_cube() || state_above.is_solid() {
|
||||
return;
|
||||
}
|
||||
world
|
||||
.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
)
|
||||
.await;
|
||||
world.set_block_state(
|
||||
pos,
|
||||
self.state_with_age(block, state, age + 1),
|
||||
BlockFlags::NOTIFY_NEIGHBORS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user