144 lines
4.5 KiB
Rust
144 lines
4.5 KiB
Rust
use std::any::Any;
|
|
use std::sync::Arc;
|
|
|
|
use crate::screen_handler::{
|
|
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
|
|
ScreenProperty, offer_or_drop_stack,
|
|
};
|
|
use crate::slot::NormalSlot;
|
|
|
|
use pumpkin_data::item_stack::ItemStack;
|
|
use pumpkin_data::screen::WindowType;
|
|
use pumpkin_world::block::entities::PropertyDelegate;
|
|
use pumpkin_world::inventory::Inventory;
|
|
|
|
/// Callbacks into the lectern block so page turns and book removal can drive
|
|
/// block-state changes (redstone pulse, `has_book`) that live outside this crate.
|
|
pub trait LecternController: Send + Sync {
|
|
/// The page currently displayed.
|
|
fn current_page(&self) -> i32;
|
|
|
|
/// Clamps and persists `page`, emitting a redstone pulse when it changes.
|
|
fn set_page(&self, page: i32) -> ScreenHandlerFuture<'_, ()>;
|
|
|
|
/// Restores the bookless block state after the book was taken.
|
|
fn on_book_taken(&self) -> ScreenHandlerFuture<'_, ()>;
|
|
}
|
|
|
|
/// Exposes the current page as container property 0 (see `window_property::Lectern`).
|
|
struct PageDelegate(Arc<dyn LecternController>);
|
|
|
|
impl PropertyDelegate for PageDelegate {
|
|
fn get_property(&self, index: i32) -> i32 {
|
|
if index == 0 { self.0.current_page() } else { 0 }
|
|
}
|
|
|
|
fn set_property(&self, _index: i32, _value: i32) {}
|
|
|
|
fn get_properties_size(&self) -> i32 {
|
|
1
|
|
}
|
|
}
|
|
|
|
/// Vanilla `LecternScreenHandler`: a single book slot, no player slots and the
|
|
/// current page synced as property 0. Page navigation and taking the book are
|
|
/// plain button clicks sent by the client.
|
|
pub struct LecternScreenHandler {
|
|
behaviour: ScreenHandlerBehaviour,
|
|
inventory: Arc<dyn Inventory>,
|
|
controller: Arc<dyn LecternController>,
|
|
}
|
|
|
|
impl LecternScreenHandler {
|
|
const PREVIOUS_PAGE_BUTTON_ID: i32 = 1;
|
|
const NEXT_PAGE_BUTTON_ID: i32 = 2;
|
|
const TAKE_BOOK_BUTTON_ID: i32 = 3;
|
|
/// Button ids at or above this jump directly to `id - JUMP_TO_PAGE_OFFSET`.
|
|
const JUMP_TO_PAGE_OFFSET: i32 = 100;
|
|
|
|
pub fn new(
|
|
sync_id: u8,
|
|
inventory: Arc<dyn Inventory>,
|
|
controller: Arc<dyn LecternController>,
|
|
) -> Self {
|
|
let mut handler = Self {
|
|
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Lectern)),
|
|
inventory: inventory.clone(),
|
|
controller: controller.clone(),
|
|
};
|
|
|
|
handler.add_slot(Arc::new(NormalSlot::new(inventory, 0)));
|
|
handler.add_property(ScreenProperty::new(Arc::new(PageDelegate(controller)), 0));
|
|
|
|
handler
|
|
}
|
|
}
|
|
|
|
impl ScreenHandler for LecternScreenHandler {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
|
|
fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
|
|
&self.behaviour
|
|
}
|
|
|
|
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
|
|
&mut self.behaviour
|
|
}
|
|
|
|
fn on_button_click<'a>(
|
|
&'a mut self,
|
|
player: &'a dyn InventoryPlayer,
|
|
id: i32,
|
|
) -> ScreenHandlerFuture<'a, bool> {
|
|
Box::pin(async move {
|
|
match id {
|
|
Self::PREVIOUS_PAGE_BUTTON_ID => {
|
|
self.controller
|
|
.set_page(self.controller.current_page() - 1)
|
|
.await;
|
|
true
|
|
}
|
|
Self::NEXT_PAGE_BUTTON_ID => {
|
|
self.controller
|
|
.set_page(self.controller.current_page() + 1)
|
|
.await;
|
|
true
|
|
}
|
|
Self::TAKE_BOOK_BUTTON_ID => {
|
|
let stack = self.inventory.remove_stack(0).await;
|
|
if stack.is_empty() {
|
|
return false;
|
|
}
|
|
self.inventory.mark_dirty();
|
|
self.controller.on_book_taken().await;
|
|
offer_or_drop_stack(player, stack).await;
|
|
self.send_content_updates().await;
|
|
true
|
|
}
|
|
_ if id >= Self::JUMP_TO_PAGE_OFFSET => {
|
|
self.controller
|
|
.set_page(id - Self::JUMP_TO_PAGE_OFFSET)
|
|
.await;
|
|
true
|
|
}
|
|
_ => false,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn quick_move<'a>(
|
|
&'a mut self,
|
|
_player: &'a dyn InventoryPlayer,
|
|
_slot_index: i32,
|
|
) -> ItemStackFuture<'a> {
|
|
// The lectern screen has no player slots, so nothing can be shift-clicked.
|
|
Box::pin(async move { ItemStack::EMPTY.clone() })
|
|
}
|
|
}
|