Pumpkin/crates/pumpkin-inventory/src/lectern_screen_handler.rs
2026-08-06 12:56:40 +02:00

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() })
}
}