feat: add rudimentary lectern
This commit is contained in:
parent
44270fbde1
commit
30f4d4e70e
153
pumpkin-world/src/block/entities/lectern.rs
Normal file
153
pumpkin-world/src/block/entities/lectern.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
block::entities::BlockEntity,
|
||||||
|
inventory::{Clearable, Inventory, InventoryFuture},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LecternBlockEntity {
|
||||||
|
pub position: BlockPos,
|
||||||
|
pub book: Arc<Mutex<ItemStack>>,
|
||||||
|
pub dirty: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockEntity for LecternBlockEntity {
|
||||||
|
fn resource_location(&self) -> &'static str {
|
||||||
|
Self::ID
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_position(&self) -> BlockPos {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let book = nbt
|
||||||
|
.get_compound("Book")
|
||||||
|
.and_then(ItemStack::read_item_stack)
|
||||||
|
.map(|stack| Arc::new(Mutex::new(stack)))
|
||||||
|
.unwrap_or_else(|| Arc::new(Mutex::new(ItemStack::EMPTY.clone())));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
book,
|
||||||
|
dirty: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_nbt<'a>(
|
||||||
|
&'a self,
|
||||||
|
nbt: &'a mut NbtCompound,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let book = self.book.lock().await;
|
||||||
|
if !book.is_empty() {
|
||||||
|
let mut book_nbt = NbtCompound::default();
|
||||||
|
book.write_item_stack(&mut book_nbt);
|
||||||
|
nbt.put_compound("Book", book_nbt);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_dirty(&self) {
|
||||||
|
self.dirty.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LecternBlockEntity {
|
||||||
|
pub const ID: &'static str = "minecraft:lectern";
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(position: BlockPos) -> Self {
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
book: Arc::new(Mutex::new(ItemStack::EMPTY.clone())),
|
||||||
|
dirty: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inventory for LecternBlockEntity {
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||||
|
Box::pin(async move { self.book.lock().await.is_empty() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||||
|
Box::pin(async move { self.book.clone() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut removed = ItemStack::EMPTY.clone();
|
||||||
|
let mut guard = self.book.lock().await;
|
||||||
|
std::mem::swap(&mut removed, &mut *guard);
|
||||||
|
self.mark_dirty();
|
||||||
|
removed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_stack_specific(&self, _slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut stack = self.book.lock().await;
|
||||||
|
if stack.is_empty() {
|
||||||
|
return ItemStack::EMPTY.clone();
|
||||||
|
}
|
||||||
|
let res = stack.split(amount);
|
||||||
|
self.mark_dirty();
|
||||||
|
res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_stack(&self, _slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
*self.book.lock().await = stack;
|
||||||
|
self.mark_dirty();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_dirty(&self) {
|
||||||
|
self.dirty.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clearable for LecternBlockEntity {
|
||||||
|
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
*self.book.lock().await = ItemStack::EMPTY.clone();
|
||||||
|
self.mark_dirty();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ use crate::block::entities::command_block::CommandBlockEntity;
|
|||||||
use crate::block::entities::ender_chest::EnderChestBlockEntity;
|
use crate::block::entities::ender_chest::EnderChestBlockEntity;
|
||||||
use crate::block::entities::hopper::HopperBlockEntity;
|
use crate::block::entities::hopper::HopperBlockEntity;
|
||||||
use crate::block::entities::jukebox::JukeboxBlockEntity;
|
use crate::block::entities::jukebox::JukeboxBlockEntity;
|
||||||
|
use crate::block::entities::lectern::LecternBlockEntity;
|
||||||
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
use crate::block::entities::mob_spawner::MobSpawnerBlockEntity;
|
||||||
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
|
use crate::block::entities::shulker_box::ShulkerBoxBlockEntity;
|
||||||
use crate::block::entities::smoker::SmokerBlockEntity;
|
use crate::block::entities::smoker::SmokerBlockEntity;
|
||||||
@ -49,6 +50,7 @@ pub mod furnace;
|
|||||||
pub mod furnace_like_block_entity;
|
pub mod furnace_like_block_entity;
|
||||||
pub mod hopper;
|
pub mod hopper;
|
||||||
pub mod jukebox;
|
pub mod jukebox;
|
||||||
|
pub mod lectern;
|
||||||
pub mod mob_spawner;
|
pub mod mob_spawner;
|
||||||
pub mod piston;
|
pub mod piston;
|
||||||
pub mod shulker_box;
|
pub mod shulker_box;
|
||||||
@ -190,6 +192,7 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option<Arc<dyn BlockEntity>>
|
|||||||
Arc::new(block_entity_from_generic::<BrewingStandBlockEntity>(nbt))
|
Arc::new(block_entity_from_generic::<BrewingStandBlockEntity>(nbt))
|
||||||
}
|
}
|
||||||
BellBlockEntity::ID => Arc::new(block_entity_from_generic::<BellBlockEntity>(nbt)),
|
BellBlockEntity::ID => Arc::new(block_entity_from_generic::<BellBlockEntity>(nbt)),
|
||||||
|
LecternBlockEntity::ID => Arc::new(block_entity_from_generic::<LecternBlockEntity>(nbt)),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,50 @@
|
|||||||
use crate::block::{BlockBehaviour, BlockFuture, OnPlaceArgs};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::block::registry::BlockActionResult;
|
||||||
|
use crate::block::{
|
||||||
|
BlockBehaviour, BlockFuture, BrokenArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs,
|
||||||
|
UseWithItemArgs,
|
||||||
|
};
|
||||||
|
use crate::entity::Entity;
|
||||||
|
use crate::entity::item::ItemEntity;
|
||||||
|
use crate::world::World;
|
||||||
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::block_properties::{BlockProperties, LecternLikeProperties};
|
use pumpkin_data::block_properties::{BlockProperties, LecternLikeProperties};
|
||||||
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use pumpkin_world::BlockStateId;
|
use pumpkin_world::BlockStateId;
|
||||||
|
use pumpkin_world::block::entities::lectern::LecternBlockEntity;
|
||||||
|
use pumpkin_world::inventory::Inventory;
|
||||||
|
use pumpkin_world::world::BlockFlags;
|
||||||
|
|
||||||
#[pumpkin_block("minecraft:lectern")]
|
#[pumpkin_block("minecraft:lectern")]
|
||||||
pub struct LecternBlock;
|
pub struct LecternBlock;
|
||||||
|
|
||||||
|
impl LecternBlock {
|
||||||
|
async fn update_lectern_state(
|
||||||
|
has_book: bool,
|
||||||
|
block: &Block,
|
||||||
|
position: &BlockPos,
|
||||||
|
world: &Arc<World>,
|
||||||
|
props: &mut LecternLikeProperties,
|
||||||
|
) {
|
||||||
|
props.has_book = has_book;
|
||||||
|
world
|
||||||
|
.set_block_state(position, props.to_state_id(block), BlockFlags::NOTIFY_ALL)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BlockBehaviour for LecternBlock {
|
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)).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut props = LecternLikeProperties::default(args.block);
|
let mut props = LecternLikeProperties::default(args.block);
|
||||||
@ -19,4 +57,88 @@ impl BlockBehaviour for LecternBlock {
|
|||||||
props.to_state_id(args.block)
|
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).await
|
||||||
|
&& let Some(lectern_entity) =
|
||||||
|
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||||
|
{
|
||||||
|
let book = lectern_entity.remove_stack(0).await;
|
||||||
|
if !book.is_empty() {
|
||||||
|
// Logic to give the book to the player
|
||||||
|
// Need to find a proper way to give items to player. For now skip.
|
||||||
|
|
||||||
|
let mut props = LecternLikeProperties::from_state_id(
|
||||||
|
args.world.get_block_state(args.position).await.id,
|
||||||
|
args.block,
|
||||||
|
);
|
||||||
|
Self::update_lectern_state(
|
||||||
|
false,
|
||||||
|
args.block,
|
||||||
|
args.position,
|
||||||
|
args.world,
|
||||||
|
&mut props,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return BlockActionResult::Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockActionResult::Pass
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_with_item<'a>(
|
||||||
|
&'a self,
|
||||||
|
args: UseWithItemArgs<'a>,
|
||||||
|
) -> BlockFuture<'a, BlockActionResult> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut item_stack = args.item_stack.lock().await;
|
||||||
|
|
||||||
|
// Check if it's a book
|
||||||
|
if item_stack.item.registry_key.contains("book")
|
||||||
|
&& let Some(block_entity) = args.world.get_block_entity(args.position).await
|
||||||
|
&& let Some(lectern_entity) =
|
||||||
|
block_entity.as_any().downcast_ref::<LecternBlockEntity>()
|
||||||
|
&& lectern_entity.book.lock().await.is_empty()
|
||||||
|
{
|
||||||
|
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||||
|
lectern_entity.set_stack(0, book).await;
|
||||||
|
|
||||||
|
let mut props = LecternLikeProperties::from_state_id(
|
||||||
|
args.world.get_block_state(args.position).await.id,
|
||||||
|
args.block,
|
||||||
|
);
|
||||||
|
Self::update_lectern_state(true, args.block, args.position, args.world, &mut props)
|
||||||
|
.await;
|
||||||
|
return BlockActionResult::Success;
|
||||||
|
}
|
||||||
|
BlockActionResult::Pass
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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).await
|
||||||
|
&& 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).await;
|
||||||
|
args.world.spawn_entity(Arc::new(item_entity)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user