mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
docs: pumpkin-inventory (#1993)
* init Signed-off-by: illyrius666 <28700752+illyrius666@users.noreply.github.com> * fixed linting Signed-off-by: illyrius666 <28700752+illyrius666@users.noreply.github.com> * fix clippy Signed-off-by: Illyrius <FitimQ@live.nl> --------- Signed-off-by: illyrius666 <28700752+illyrius666@users.noreply.github.com> Signed-off-by: Illyrius <FitimQ@live.nl> Co-authored-by: Illyrius <FitimQ@live.nl>
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
//! Brewing stand screen handler.
|
||||
//!
|
||||
//! This module implements the screen handler for brewing stands.
|
||||
//! Brewing stands have 5 slots:
|
||||
//! - Slots 0-2: Potion bottles (output/input)
|
||||
//! - Slot 3: Brewing ingredient
|
||||
//! - Slot 4: Fuel (blaze powder)
|
||||
//!
|
||||
//! The brewing stand has two tracked properties:
|
||||
//! - Brew time (0-400): Progress of the current brewing operation
|
||||
//! - Fuel time (0-20): Amount of fuel remaining
|
||||
|
||||
use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::tag::Taggable;
|
||||
@@ -11,13 +23,29 @@ use crate::{
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
|
||||
/// Screen handler for the brewing stand.
|
||||
///
|
||||
/// Manages the brewing stand's 5 slots and tracks brewing progress
|
||||
/// and fuel levels.
|
||||
pub struct BrewingScreenHandler {
|
||||
/// The brewing stand's inventory (5 slots: 0-2 potions, 3 ingredient, 4 fuel).
|
||||
inventory: Arc<dyn Inventory>,
|
||||
/// Core screen handler behavior (slots, sync ID, listeners).
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
/// Delegate for accessing brew time and fuel time properties.
|
||||
///
|
||||
/// Property 0: Brew time (0-400), Property 1: Fuel time (0-20).
|
||||
_property_delegate: Arc<dyn PropertyDelegate>,
|
||||
}
|
||||
|
||||
impl BrewingScreenHandler {
|
||||
/// Creates a new brewing stand screen handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `sync_id` - The sync ID for client-server matching
|
||||
/// - `player_inventory` - The player's inventory
|
||||
/// - `inventory` - The brewing stand's inventory (5 slots)
|
||||
/// - `property_delegate` - Delegate for accessing brew time and fuel properties
|
||||
pub async fn new(
|
||||
sync_id: u8,
|
||||
player_inventory: Arc<PlayerInventory>,
|
||||
@@ -28,10 +56,10 @@ impl BrewingScreenHandler {
|
||||
impl crate::screen_handler::ScreenHandlerListener for BrewingScreenListener {
|
||||
fn on_property_update<'a>(
|
||||
&'a self,
|
||||
screen_handler: &'a crate::screen_handler::ScreenHandlerBehaviour,
|
||||
screen_handler: &'a ScreenHandlerBehaviour,
|
||||
property: u8,
|
||||
value: i32,
|
||||
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if let Some(sync_handler) = screen_handler.sync_handler.as_ref() {
|
||||
sync_handler
|
||||
@@ -90,11 +118,17 @@ impl ScreenHandler for BrewingScreenHandler {
|
||||
Box::pin(async move { self.default_on_closed(player).await })
|
||||
}
|
||||
|
||||
/// Quick move logic for brewing stand.
|
||||
///
|
||||
/// - Potions (0-2) -> Player inventory
|
||||
/// - Ingredient (3) -> Player inventory
|
||||
/// - Fuel (4) -> Player inventory
|
||||
/// - From player: potions -> potion slots, fuel -> fuel slot, else -> ingredient slot
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
_player: &'a dyn crate::screen_handler::InventoryPlayer,
|
||||
slot_index: i32,
|
||||
) -> Pin<Box<dyn std::future::Future<Output = ItemStack> + Send + 'a>> {
|
||||
) -> Pin<Box<dyn Future<Output = ItemStack> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let mut stack_left = ItemStack::EMPTY.clone();
|
||||
|
||||
@@ -152,6 +186,9 @@ impl ScreenHandler for BrewingScreenHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new brewing stand screen handler.
|
||||
///
|
||||
/// Factory function used by the server when a player opens a brewing stand.
|
||||
pub async fn create_brewing(
|
||||
sync_id: u8,
|
||||
player_inventory: Arc<PlayerInventory>,
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
//! Brewing module.
|
||||
//!
|
||||
//! This module handles the brewing stand mechanics:
|
||||
//! - [`BrewingStandScreenHandler`] - Screen handler for the brewing stand UI
|
||||
//!
|
||||
//! The brewing stand allows players to brew potions by combining water bottles
|
||||
//! with various ingredients.
|
||||
|
||||
pub mod brewing_screen_handler;
|
||||
|
||||
pub use brewing_screen_handler::create_brewing;
|
||||
|
||||
@@ -1,22 +1,62 @@
|
||||
//! Container click handling.
|
||||
//!
|
||||
//! This module processes inventory click packets from the client and converts
|
||||
//! them into structured click events. It handles all click types:
|
||||
//! - Mouse clicks (left/right)
|
||||
//! - Shift-clicks
|
||||
//! - Hotbar key presses
|
||||
//! - Drop actions (Q key or clicking outside)
|
||||
//! - Drag operations (click and drag across slots)
|
||||
//! - Double-clicks (pickup all)
|
||||
//!
|
||||
//! # Click Types
|
||||
//!
|
||||
//! The client sends a mode and button value that are decoded into
|
||||
//! [`ClickType`] variants. See the Minecraft protocol documentation
|
||||
//! for packet format details.
|
||||
|
||||
use crate::InventoryError;
|
||||
use pumpkin_protocol::java::server::play::SlotActionType;
|
||||
|
||||
/// A parsed container click event.
|
||||
///
|
||||
/// Contains the slot being clicked and the type of click action.
|
||||
#[derive(Debug)]
|
||||
pub struct Click {
|
||||
/// The slot being clicked (or outside the inventory).
|
||||
pub slot: Slot,
|
||||
/// The type of click action (mouse click, shift-click, drag, etc.).
|
||||
pub click_type: ClickType,
|
||||
}
|
||||
|
||||
/// Button values for normal mouse clicks.
|
||||
const BUTTON_CLICK_LEFT: i8 = 0;
|
||||
const BUTTON_CLICK_RIGHT: i8 = 1;
|
||||
|
||||
/// Key code for offhand swap (F key by default).
|
||||
const KEY_CLICK_OFFHAND: i8 = 40;
|
||||
|
||||
/// Hotbar slot key range (1-9 keys).
|
||||
const KEY_CLICK_HOTBAR_START: i8 = 0;
|
||||
const KEY_CLICK_HOTBAR_END: i8 = 9;
|
||||
|
||||
/// Slot index indicating a click outside the inventory.
|
||||
const SLOT_INDEX_OUTSIDE: i16 = -999;
|
||||
|
||||
impl Click {
|
||||
/// Parses a slot action into a click event.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `mode` - The action type from the protocol
|
||||
/// - `button` - The button value from the protocol
|
||||
/// - `slot` - The slot index (-999 for outside)
|
||||
///
|
||||
/// # Returns
|
||||
/// The parsed click or an error if invalid.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`InventoryError::InvalidSlot`] or [`InventoryError::InvalidPacket`]
|
||||
/// for malformed input.
|
||||
pub fn new(mode: &SlotActionType, button: i8, slot: i16) -> Result<Self, InventoryError> {
|
||||
match mode {
|
||||
SlotActionType::Pickup => Self::new_normal_click(button, slot),
|
||||
@@ -111,36 +151,54 @@ impl Click {
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of click action.
|
||||
#[derive(Debug)]
|
||||
pub enum ClickType {
|
||||
/// Normal mouse click (left or right).
|
||||
MouseClick(MouseClick),
|
||||
/// Shift-click to quick-move an item.
|
||||
ShiftClick,
|
||||
/// Hotbar key press (1-9 or offhand swap).
|
||||
KeyClick(KeyClick),
|
||||
/// Creative mode middle-click (pick block).
|
||||
CreativePickItem,
|
||||
/// Drop item (Q key or drop click).
|
||||
DropType(DropType),
|
||||
/// Drag items across multiple slots.
|
||||
MouseDrag { drag_state: MouseDragState },
|
||||
/// Double-click to gather items.
|
||||
DoubleClick,
|
||||
}
|
||||
|
||||
/// Normal mouse button clicks.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum MouseClick {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
/// Hotbar key clicks.
|
||||
#[derive(Debug)]
|
||||
pub enum KeyClick {
|
||||
/// Hotbar slot index (0-8).
|
||||
Slot(u8),
|
||||
/// Swap with offhand.
|
||||
Offhand,
|
||||
}
|
||||
|
||||
/// A slot reference - either a specific slot or outside the inventory.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Slot {
|
||||
Normal(usize),
|
||||
OutsideInventory,
|
||||
}
|
||||
|
||||
/// Drop action types.
|
||||
#[derive(Debug)]
|
||||
pub enum DropType {
|
||||
/// Drop a single item (Ctrl+Q).
|
||||
SingleItem,
|
||||
/// Drop the full stack (Q).
|
||||
FullStack,
|
||||
}
|
||||
|
||||
@@ -154,15 +212,24 @@ impl DropType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mouse drag button types.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MouseDragType {
|
||||
/// Left button drag - even distribution.
|
||||
Left,
|
||||
/// Right button drag - one item per slot.
|
||||
Right,
|
||||
/// Middle button drag - create full stacks (creative only).
|
||||
Middle,
|
||||
}
|
||||
|
||||
/// Drag operation state.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum MouseDragState {
|
||||
/// Start of drag - button determines drag type.
|
||||
Start(MouseDragType),
|
||||
/// Adding a slot to the drag.
|
||||
AddSlot(usize),
|
||||
/// End of drag - apply to all selected slots.
|
||||
End,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
//! Crafting inventory implementation.
|
||||
//!
|
||||
//! This module provides a temporary inventory for crafting grids.
|
||||
//! Crafting inventories are used for:
|
||||
//! - The 2x2 crafting grid in the player inventory
|
||||
//! - The 3x3 crafting grid in crafting tables
|
||||
//! - Other recipe-based crafting mechanisms
|
||||
//!
|
||||
//! Unlike regular inventories, crafting grids are typically cleared when
|
||||
//! the container closes, and their contents are used up when crafting.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, pin::Pin};
|
||||
|
||||
@@ -9,14 +20,41 @@ use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
|
||||
use super::recipes::RecipeInputInventory;
|
||||
|
||||
/// A temporary inventory for crafting grids.
|
||||
///
|
||||
/// Crafting inventories hold items arranged in a grid pattern for crafting recipes.
|
||||
/// The grid dimensions can vary (2x2 for player inventory, 3x3 for crafting table).
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// When a player places items in the crafting grid, they are stored here.
|
||||
/// When the crafting result is taken, the ingredients are consumed from this inventory.
|
||||
#[derive(Clone)]
|
||||
pub struct CraftingInventory {
|
||||
/// Width of the crafting grid (typically 2 or 3).
|
||||
pub width: u8,
|
||||
/// Height of the crafting grid (typically 2 or 3).
|
||||
pub height: u8,
|
||||
/// Items in the crafting grid, stored row by row.
|
||||
pub items: Vec<Arc<Mutex<ItemStack>>>,
|
||||
}
|
||||
|
||||
impl CraftingInventory {
|
||||
/// Creates a new crafting inventory with the given dimensions.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `width` - Grid width (e.g., 2 for player crafting, 3 for crafting table)
|
||||
/// - `height` - Grid height (e.g., 2 for player crafting, 3 for crafting table)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // 2x2 player inventory crafting grid
|
||||
/// let player_crafting = CraftingInventory::new(2, 2);
|
||||
///
|
||||
/// // 3x3 crafting table grid
|
||||
/// let table_crafting = CraftingInventory::new(3, 3);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn new(width: u8, height: u8) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
//! Crafting screen handler implementation.
|
||||
//!
|
||||
//! This module provides screen handlers for crafting mechanics:
|
||||
//! - [`CraftingScreenHandler`] - Trait for crafting screen handlers
|
||||
//! - [`CraftingTableScreenHandler`] - The 3x3 crafting table UI
|
||||
//! - [`ResultSlot`] - The special result slot that shows crafted items
|
||||
//!
|
||||
//! # Recipe Matching
|
||||
//!
|
||||
//! Crafting recipes are matched against the items in the crafting grid.
|
||||
//! The system supports:
|
||||
//! - Shaped recipes (specific patterns)
|
||||
//! - Shapeless recipes (any arrangement)
|
||||
//! - Transmute recipes (upgrading items)
|
||||
//! - Special recipes (like decorated pots)
|
||||
|
||||
use std::any::Any;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -21,18 +37,34 @@ use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// CraftingResultSlot.java
|
||||
/// The result slot in a crafting screen.
|
||||
///
|
||||
/// Note: This implementation is different from the original Minecraft code.
|
||||
/// Particularly, it does not have a 'result' inventory, we directly store it in the slot.
|
||||
/// This slot should be never modified outside. any modifications to it make change in its input.
|
||||
/// This special slot displays the output of the current crafting recipe.
|
||||
/// Unlike normal slots, it doesn't store items permanently - it calculates
|
||||
/// the result dynamically based on the crafting grid contents.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This implementation differs from vanilla Minecraft. Instead of a separate
|
||||
/// result inventory, we directly store the result in the slot. The slot
|
||||
/// should never be modified directly - modifications to the crafting grid
|
||||
/// automatically update the result.
|
||||
pub struct ResultSlot {
|
||||
/// The crafting inventory (grid) that provides recipe input.
|
||||
pub inventory: Arc<dyn RecipeInputInventory>,
|
||||
/// Protocol ID for this slot (assigned by screen handler).
|
||||
pub id: AtomicU8,
|
||||
/// The cached result item stack.
|
||||
///
|
||||
/// Updated when the crafting grid changes and a recipe matches.
|
||||
pub result: Arc<Mutex<ItemStack>>,
|
||||
/// Cached reference to the last matched recipe for quick re-matching.
|
||||
recipe_cache: AtomicCell<Option<&'static CraftingRecipeTypes>>,
|
||||
}
|
||||
|
||||
/// Checks if a recipe pattern is symmetrical horizontally.
|
||||
///
|
||||
/// Used to try both orientations when matching shaped recipes.
|
||||
fn is_symmetrical_horizontally(pattern: &'static [&'static str]) -> bool {
|
||||
let width = pattern.first().map_or(0, |s| s.len());
|
||||
for row in pattern {
|
||||
@@ -48,6 +80,10 @@ fn is_symmetrical_horizontally(pattern: &'static [&'static str]) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Checks if a crafting recipe matches the current inventory state.
|
||||
///
|
||||
/// Tries the recipe at all possible positions in the grid and handles
|
||||
/// both orientations for asymmetrical recipes.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
async fn recipe_matches<'a>(
|
||||
recipe: &'static CraftingRecipeTypes,
|
||||
@@ -237,6 +273,7 @@ async fn recipe_matches<'a>(
|
||||
impl ResultSlot {
|
||||
//fn stat_crafted(&self, _crafted_amount: u8, _player: &dyn InventoryPlayer) {}
|
||||
|
||||
/// Creates a new result slot for the given crafting inventory.
|
||||
pub fn new(inventory: Arc<dyn RecipeInputInventory>) -> Self {
|
||||
Self {
|
||||
inventory,
|
||||
@@ -312,6 +349,7 @@ impl ResultSlot {
|
||||
None
|
||||
}
|
||||
|
||||
/// Refills the output slot with the current recipe result.
|
||||
async fn refill_output(&self) -> ItemStack {
|
||||
let result = self
|
||||
.match_recipe()
|
||||
@@ -452,10 +490,15 @@ impl ScreenHandlerListener for ResultSlot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for crafting screen handlers.
|
||||
///
|
||||
/// Provides common functionality for crafting UIs including slot setup
|
||||
/// and recipe result management.
|
||||
// AbstractCraftingScreenHandler.java
|
||||
pub trait CraftingScreenHandler<I: RecipeInputInventory>:
|
||||
RecipeFinderScreenHandler + ScreenHandler
|
||||
{
|
||||
/// Adds the result slot and crafting grid slots to the screen handler.
|
||||
fn add_recipe_slots<'a>(
|
||||
&'a mut self,
|
||||
crafing_inventory: Arc<dyn RecipeInputInventory>,
|
||||
@@ -479,13 +522,24 @@ pub trait CraftingScreenHandler<I: RecipeInputInventory>:
|
||||
}
|
||||
}
|
||||
|
||||
/// Screen handler for the crafting table.
|
||||
///
|
||||
/// The crafting table provides a 3x3 crafting grid and displays
|
||||
/// the result of the current recipe configuration.
|
||||
// CraftingMenu
|
||||
pub struct CraftingTableScreenHandler {
|
||||
/// Core screen handler behavior (slots, sync ID, listeners).
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
/// The 3x3 crafting grid inventory.
|
||||
crafting_inventory: Arc<dyn RecipeInputInventory>,
|
||||
}
|
||||
|
||||
impl CraftingTableScreenHandler {
|
||||
/// Creates a new crafting table screen handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `sync_id` - The sync ID for client-server matching
|
||||
/// - `player_inventory` - The player's inventory
|
||||
pub async fn new(sync_id: u8, player_inventory: &Arc<PlayerInventory>) -> Self {
|
||||
let crafting_inventory: Arc<dyn RecipeInputInventory> =
|
||||
Arc::new(CraftingInventory::new(3, 3));
|
||||
@@ -510,15 +564,6 @@ impl CraftingTableScreenHandler {
|
||||
impl RecipeFinderScreenHandler for CraftingTableScreenHandler {}
|
||||
|
||||
impl ScreenHandler for CraftingTableScreenHandler {
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
//TODO: this.craftingResultInventory.clear();
|
||||
self.drop_inventory(player, self.crafting_inventory.clone())
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -531,6 +576,20 @@ impl ScreenHandler for CraftingTableScreenHandler {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
//TODO: this.craftingResultInventory.clear();
|
||||
self.drop_inventory(player, self.crafting_inventory.clone())
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Quick move logic for crafting table.
|
||||
///
|
||||
/// - Result slot (0): Move to player inventory (10-46)
|
||||
/// - Crafting grid (1-9): Move to player inventory
|
||||
/// - Player inventory (10-46): Move to crafting grid first, then shuffle within inventory
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//! Crafting module.
|
||||
//!
|
||||
//! This module handles crafting mechanics including:
|
||||
//! - [`CraftingInventory`] - Temporary inventory for crafting slots
|
||||
//! - [`CraftingScreenHandler`] - Screen handler for crafting tables and inventory crafting
|
||||
//! - [`recipes`] - Recipe matching and crafting result calculation
|
||||
|
||||
pub mod crafting_inventory;
|
||||
pub mod crafting_screen_handler;
|
||||
pub mod recipes;
|
||||
|
||||
@@ -1,18 +1,47 @@
|
||||
//! Recipe-related types and traits.
|
||||
//!
|
||||
//! This module defines the interfaces for recipe handling in crafting systems.
|
||||
//! It provides traits for screen handlers that can find recipes and inventories
|
||||
//! that can serve as recipe input.
|
||||
//!
|
||||
//! # Recipe System
|
||||
//!
|
||||
//! The recipe system involves:
|
||||
//! - [`RecipeFinderScreenHandler`] - Screen handlers that can find matching recipes
|
||||
//! - [`RecipeInputInventory`] - Inventories that provide crafting input
|
||||
//! - [`RecipeMatcher`] - Helper for matching items to recipes
|
||||
//! - [`RecipeFinder`] - Helper for finding recipes
|
||||
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
|
||||
/// Helper struct for matching recipe ingredients.
|
||||
// RecipeMatcher.java
|
||||
pub struct RecipeMatcher;
|
||||
|
||||
/// Helper struct for finding recipes.
|
||||
// RecipeFinder.java
|
||||
pub struct RecipeFinder;
|
||||
|
||||
/// Trait for screen handlers that can find crafting recipes.
|
||||
///
|
||||
/// Screen handlers implementing this trait can search for recipes
|
||||
/// that match the current input inventory state.
|
||||
// AbstractRecipeScreenHandle.java
|
||||
pub trait RecipeFinderScreenHandler {}
|
||||
|
||||
/// Trait for inventories that serve as recipe input.
|
||||
///
|
||||
/// Crafting grids implement this trait to provide their dimensions
|
||||
/// and item access for recipe matching.
|
||||
pub trait RecipeInputInventory: Inventory {
|
||||
/// Gets the width of the crafting grid.
|
||||
fn get_width(&self) -> usize;
|
||||
|
||||
/// Gets the height of the crafting grid.
|
||||
fn get_height(&self) -> usize;
|
||||
//fn get_held_stacks(), Get a lock on the inventory instead
|
||||
|
||||
// TODO: Additional methods for recipe input handling
|
||||
// fn get_held_stacks(), Get a lock on the inventory instead
|
||||
// createRecipeInput
|
||||
// createPositionedRecipeInput
|
||||
}
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
//! Double inventory implementation.
|
||||
//!
|
||||
//! This module provides a composite inventory that combines two inventories
|
||||
//! into one. This is used for large containers like double chests, which
|
||||
//! consist of two single chest inventories viewed as a single 54-slot inventory.
|
||||
//!
|
||||
//! The first inventory's slots come first, followed by the second inventory's
|
||||
//! slots. Operations are delegated to the appropriate underlying inventory
|
||||
//! based on the slot index.
|
||||
|
||||
use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// A composite inventory combining two inventories.
|
||||
///
|
||||
/// Used for double chests and other large containers that span
|
||||
/// multiple block entities. The combined inventory size is the sum
|
||||
/// of both inventories' sizes.
|
||||
pub struct DoubleInventory {
|
||||
/// The first inventory (lower slot indices, 0 to first.size()-1).
|
||||
first: Arc<dyn Inventory>,
|
||||
/// The second inventory (higher slot indices, `first.size()` to total-1).
|
||||
second: Arc<dyn Inventory>,
|
||||
}
|
||||
|
||||
impl DoubleInventory {
|
||||
/// Creates a new double inventory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `first` - The first inventory (lower slot indices)
|
||||
/// - `second` - The second inventory (higher slot indices)
|
||||
///
|
||||
/// # Returns
|
||||
/// A shared reference to the new double inventory.
|
||||
pub fn new(first: Arc<dyn Inventory>, second: Arc<dyn Inventory>) -> Arc<Self> {
|
||||
Arc::new(Self { first, second })
|
||||
}
|
||||
@@ -56,10 +81,6 @@ impl Inventory for DoubleInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_max_count_per_stack(&self) -> u8 {
|
||||
self.first.get_max_count_per_stack()
|
||||
}
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
if slot >= self.first.size() {
|
||||
@@ -70,11 +91,6 @@ impl Inventory for DoubleInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) {
|
||||
self.first.mark_dirty();
|
||||
self.second.mark_dirty();
|
||||
}
|
||||
|
||||
fn on_open(&self) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.first.on_open().await;
|
||||
@@ -89,6 +105,15 @@ impl Inventory for DoubleInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_max_count_per_stack(&self) -> u8 {
|
||||
self.first.get_max_count_per_stack()
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) {
|
||||
self.first.mark_dirty();
|
||||
self.second.mark_dirty();
|
||||
}
|
||||
|
||||
fn is_valid_slot_for(&self, slot: usize, stack: &ItemStack) -> bool {
|
||||
if slot >= self.first.size() {
|
||||
self.second
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
//! Item drag handler.
|
||||
//!
|
||||
//! This module handles the logic for dragging items across multiple inventory slots.
|
||||
//! When a player clicks and drags with an item, they can distribute it across
|
||||
//! multiple slots.
|
||||
//!
|
||||
//! Drag types:
|
||||
//! - Left click drag - Evenly distributes items across slots
|
||||
//! - Right click drag - Places one item in each slot
|
||||
//! - Middle click drag (creative) - Creates full stacks in each slot (creative only)
|
||||
//!
|
||||
//! Note: This implementation is currently disabled/commented out pending completion.
|
||||
|
||||
/*
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DragHandler(RwLock<HashMap<u64, Arc<Mutex<Drag>>>>);
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
//! Entity equipment management.
|
||||
//!
|
||||
//! This module handles the storage and management of entity equipment slots,
|
||||
//! such as armor (head, chest, legs, feet) and off-hand items.
|
||||
//!
|
||||
//! Equipment is stored separately from the main inventory and is visible on
|
||||
//! the entity model (armor is rendered on the player, held items are visible
|
||||
//! in hands).
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Equipment storage for an entity.
|
||||
///
|
||||
/// Stores items equipped in armor slots (head, chest, legs, feet) and
|
||||
/// the off-hand slot. Equipment is separate from the main inventory
|
||||
/// and affects entity appearance and stats.
|
||||
///
|
||||
/// See also: [`EquipmentSlot`](EquipmentSlot)
|
||||
// EntityEquipment.java
|
||||
#[derive(Clone)]
|
||||
pub struct EntityEquipment {
|
||||
/// Map of equipment slots to their equipped items.
|
||||
///
|
||||
/// Keys are equipment slot types (head, chest, legs, feet, off-hand).
|
||||
/// Values are mutex-protected item stacks for thread-safe access.
|
||||
pub equipment: HashMap<EquipmentSlot, Arc<Mutex<ItemStack>>>,
|
||||
}
|
||||
|
||||
@@ -17,6 +37,7 @@ impl Default for EntityEquipment {
|
||||
}
|
||||
|
||||
impl EntityEquipment {
|
||||
/// Creates a new empty equipment storage.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -24,6 +45,14 @@ impl EntityEquipment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Equips an item in a slot, returning the previous item.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `slot` - The equipment slot
|
||||
/// - `stack` - The item to equip
|
||||
///
|
||||
/// # Returns
|
||||
/// The previously equipped item, or an empty stack if the slot was empty.
|
||||
pub async fn put(&mut self, slot: &EquipmentSlot, stack: ItemStack) -> ItemStack {
|
||||
self.equipment
|
||||
.insert(slot.clone(), Arc::new(Mutex::new(stack)))
|
||||
@@ -33,6 +62,12 @@ impl EntityEquipment {
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Gets or inserts an empty stack for a slot.
|
||||
///
|
||||
/// If the slot doesn't exist, creates it with an empty stack.
|
||||
///
|
||||
/// # Returns
|
||||
/// A mutex-protected item stack for this slot.
|
||||
#[must_use]
|
||||
pub fn get_or_insert(&mut self, slot: &EquipmentSlot) -> Arc<Mutex<ItemStack>> {
|
||||
self.equipment
|
||||
@@ -41,6 +76,10 @@ impl EntityEquipment {
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Gets the item in a slot.
|
||||
///
|
||||
/// # Returns
|
||||
/// The equipped item, or an empty stack if nothing is equipped.
|
||||
#[must_use]
|
||||
pub fn get(&self, slot: &EquipmentSlot) -> Arc<Mutex<ItemStack>> {
|
||||
self.equipment
|
||||
@@ -49,6 +88,7 @@ impl EntityEquipment {
|
||||
.unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||
}
|
||||
|
||||
/// Checks if all equipment slots are empty.
|
||||
pub async fn is_empty(&self) -> bool {
|
||||
for stack in self.equipment.values() {
|
||||
if !stack.lock().await.is_empty() {
|
||||
@@ -59,9 +99,10 @@ impl EntityEquipment {
|
||||
true
|
||||
}
|
||||
|
||||
/// Clears all equipped items.
|
||||
pub fn clear(&mut self) {
|
||||
self.equipment.clear();
|
||||
}
|
||||
|
||||
// TODO: tick
|
||||
// TODO: tick - Equipment updates, durability damage, etc.
|
||||
}
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during inventory operations.
|
||||
///
|
||||
/// These errors represent various failure conditions when handling inventory
|
||||
/// interactions, such as invalid slot indices, permission issues, or protocol errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InventoryError {
|
||||
/// Failed to acquire a lock on an inventory or slot.
|
||||
#[error("Unable to lock")]
|
||||
LockError,
|
||||
/// The specified slot index is invalid or out of bounds.
|
||||
#[error("Invalid slot")]
|
||||
InvalidSlot,
|
||||
/// A player attempted to interact with a container that is closed.
|
||||
///
|
||||
/// The parameter is the player's entity ID.
|
||||
#[error("Player '{0}' tried to interact with a closed container")]
|
||||
ClosedContainerInteract(i32),
|
||||
/// Multiple players attempted to drag items in the same container simultaneously.
|
||||
#[error("Multiple players dragging in a container at once")]
|
||||
MultiplePlayersDragging,
|
||||
/// Drag operation was performed out of order (e.g., end before start).
|
||||
#[error("Out of order dragging")]
|
||||
OutOfOrderDragging,
|
||||
/// The received inventory packet is malformed or invalid.
|
||||
#[error("Invalid inventory packet")]
|
||||
InvalidPacket,
|
||||
/// The player lacks permission to perform this inventory operation.
|
||||
#[error("Player does not have enough permissions")]
|
||||
PermissionError,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
//! Furnace-like screen handler.
|
||||
//!
|
||||
//! This module implements the screen handler for furnace-like blocks:
|
||||
//! - Furnace
|
||||
//! - Smoker
|
||||
//! - Blast Furnace
|
||||
//!
|
||||
//! All three share the same 3-slot layout:
|
||||
//! - Slot 0: Input (item to smelt/cook)
|
||||
//! - Slot 1: Fuel (coal, charcoal, etc.)
|
||||
//! - Slot 2: Output (smelted result)
|
||||
//!
|
||||
//! The screen handler tracks 4 properties:
|
||||
//! - Property 0: Fire icon animation (fuel burn time remaining)
|
||||
//! - Property 1: Maximum fuel burn time
|
||||
//! - Property 2: Progress arrow (cooking/smelt time)
|
||||
//! - Property 3: Maximum progress (typically 200 ticks for furnace)
|
||||
|
||||
use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::{fuels::is_fuel, item_stack::ItemStack, screen::WindowType};
|
||||
@@ -17,13 +35,31 @@ use tracing::debug;
|
||||
|
||||
use super::furnace_like_slot::{FurnaceLikeSlot, FurnaceLikeSlotType, FurnaceOutputSlot};
|
||||
|
||||
/// Screen handler for furnace-like containers.
|
||||
///
|
||||
/// Handles the UI for furnaces, smokers, and blast furnaces.
|
||||
/// These all share the same slot layout and quick-move behavior.
|
||||
pub struct FurnaceLikeScreenHandler {
|
||||
/// The furnace's inventory (3 slots: 0 input, 1 fuel, 2 output).
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
/// Container that tracks accumulated smelting experience.
|
||||
///
|
||||
/// Experience is awarded to the player when they take items from the output slot.
|
||||
experience_container: Arc<dyn ExperienceContainer>,
|
||||
/// Core screen handler behavior (slots, sync ID, properties, listeners).
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
}
|
||||
|
||||
impl FurnaceLikeScreenHandler {
|
||||
/// Creates a new furnace-like screen handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `sync_id` - The sync ID for client-server matching
|
||||
/// - `player_inventory` - The player's inventory
|
||||
/// - `inventory` - The furnace's inventory (3 slots)
|
||||
/// - `property_delegate` - Delegate for accessing furnace properties
|
||||
/// - `experience_container` - Container that tracks smelting experience
|
||||
/// - `window_type` - The window type (Furnace, Smoker, or `BlastFurnace`)
|
||||
pub async fn new(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -73,6 +109,11 @@ impl FurnaceLikeScreenHandler {
|
||||
handler
|
||||
}
|
||||
|
||||
/// Adds the 3 furnace inventory slots.
|
||||
///
|
||||
/// - Slot 0: Input (top)
|
||||
/// - Slot 1: Fuel (bottom)
|
||||
/// - Slot 2: Output
|
||||
fn add_inventory_slots(&mut self) {
|
||||
self.add_slot(Arc::new(FurnaceLikeSlot::new(
|
||||
self.inventory.clone(),
|
||||
@@ -91,13 +132,6 @@ impl FurnaceLikeScreenHandler {
|
||||
}
|
||||
|
||||
impl ScreenHandler for FurnaceLikeScreenHandler {
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
// TODO: self.inventory.on_closed(player).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -110,6 +144,18 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
// TODO: self.inventory.on_closed(player).await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Quick move logic for furnace-like containers.
|
||||
///
|
||||
/// - From furnace slots (0-2): Move to player inventory
|
||||
/// - Fuel items: Move to fuel slot (1)
|
||||
/// - Other items: Move to input slot (0)
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
//! Furnace-like slot implementations.
|
||||
//!
|
||||
//! This module provides specialized slot types for furnace-like containers.
|
||||
//! Furnaces have three slots with specific behaviors:
|
||||
//! - Input slot (top): Accepts any smeltable item
|
||||
//! - Fuel slot (bottom): Only accepts fuel items (coal, charcoal, etc.)
|
||||
//! - Output slot: Cannot receive items, awards experience when items are taken
|
||||
|
||||
use std::sync::{Arc, atomic::AtomicU8};
|
||||
|
||||
use pumpkin_data::{fuels::is_fuel, item::Item};
|
||||
@@ -12,13 +20,19 @@ use crate::{
|
||||
slot::{BoxFuture, Slot},
|
||||
};
|
||||
|
||||
/// Type of furnace slot.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FurnaceLikeSlotType {
|
||||
/// Input slot (top) - accepts items to smelt.
|
||||
Top = 0,
|
||||
/// Fuel slot (bottom) - accepts fuel items.
|
||||
Bottom = 1,
|
||||
}
|
||||
|
||||
/// Slot for furnace input (top) and fuel (bottom)
|
||||
/// Slot for furnace input or fuel.
|
||||
///
|
||||
/// The input slot accepts any item, while the fuel slot only accepts
|
||||
/// valid fuel items (and empty buckets for lava fuel).
|
||||
pub struct FurnaceLikeSlot {
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
pub slot_type: FurnaceLikeSlotType,
|
||||
@@ -27,6 +41,11 @@ pub struct FurnaceLikeSlot {
|
||||
}
|
||||
|
||||
impl FurnaceLikeSlot {
|
||||
/// Creates a new furnace slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `inventory` - The furnace's inventory
|
||||
/// - `slot_type` - Whether this is the input (Top) or fuel (Bottom) slot
|
||||
pub fn new(inventory: Arc<dyn Inventory>, slot_type: FurnaceLikeSlotType) -> Self {
|
||||
Self {
|
||||
inventory,
|
||||
@@ -51,12 +70,10 @@ impl Slot for FurnaceLikeSlot {
|
||||
.store(id as u8, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.inventory.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
/// Restricts inserts based on slot type.
|
||||
///
|
||||
/// - Top slot: accepts any item (smeltables)
|
||||
/// - Bottom slot: only accepts fuel items and buckets
|
||||
fn can_insert<'a>(
|
||||
&'a self,
|
||||
stack: &'a pumpkin_data::item_stack::ItemStack,
|
||||
@@ -70,9 +87,19 @@ impl Slot for FurnaceLikeSlot {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.inventory.mark_dirty();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Output slot for furnace that awards experience when items are taken
|
||||
/// Output slot for furnace-like containers.
|
||||
///
|
||||
/// This slot cannot receive items directly (items are placed here by smelting).
|
||||
/// When items are taken from this slot, the player receives experience
|
||||
/// based on the smelting recipes used.
|
||||
pub struct FurnaceOutputSlot {
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
pub experience_container: Arc<dyn ExperienceContainer>,
|
||||
@@ -80,6 +107,11 @@ pub struct FurnaceOutputSlot {
|
||||
}
|
||||
|
||||
impl FurnaceOutputSlot {
|
||||
/// Creates a new furnace output slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `inventory` - The furnace's inventory
|
||||
/// - `experience_container` - Container that tracks accumulated experience
|
||||
pub fn new(
|
||||
inventory: Arc<dyn Inventory>,
|
||||
experience_container: Arc<dyn ExperienceContainer>,
|
||||
@@ -106,20 +138,7 @@ impl Slot for FurnaceOutputSlot {
|
||||
.store(id as u8, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.inventory.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
fn can_insert<'a>(
|
||||
&'a self,
|
||||
_stack: &'a pumpkin_data::item_stack::ItemStack,
|
||||
) -> BoxFuture<'a, bool> {
|
||||
// Cannot insert items into the output slot
|
||||
Box::pin(async move { false })
|
||||
}
|
||||
|
||||
/// Awards experience when items are taken from this slot.
|
||||
fn on_take_item<'a>(
|
||||
&'a self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
@@ -137,4 +156,19 @@ impl Slot for FurnaceOutputSlot {
|
||||
self.mark_dirty().await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Output slot cannot receive inserted items.
|
||||
fn can_insert<'a>(
|
||||
&'a self,
|
||||
_stack: &'a pumpkin_data::item_stack::ItemStack,
|
||||
) -> BoxFuture<'a, bool> {
|
||||
// Cannot insert items into the output slot
|
||||
Box::pin(async move { false })
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.inventory.mark_dirty();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,21 @@
|
||||
//! Furnace-like containers module.
|
||||
//!
|
||||
//! This module handles screen handlers for furnace-like blocks:
|
||||
//! - Furnace
|
||||
//! - Smoker
|
||||
//! - Blast Furnace
|
||||
//!
|
||||
//! These containers share the same slot layout and behavior:
|
||||
//! - Slot 0: Input (item to smelt/cook)
|
||||
//! - Slot 1: Fuel (coal, charcoal, etc.)
|
||||
//! - Slot 2: Output (smelted/cooked result)
|
||||
//!
|
||||
//! # Properties
|
||||
//!
|
||||
/// Furnace-like containers track four properties:
|
||||
/// - Property 0: Fire icon (fuel remaining)
|
||||
/// - Property 1: Maximum fuel burn time
|
||||
/// - Property 2: Progress arrow (smelting progress)
|
||||
/// - Property 3: Maximum progress (typically 200 ticks)
|
||||
pub mod furnace_like_screen_handler;
|
||||
pub mod furnace_like_slot;
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
//! Generic container screen handler.
|
||||
//!
|
||||
//! This module provides a generic screen handler for simple containers like:
|
||||
//! - Chests (single, double, ender chest)
|
||||
//! - Hoppers
|
||||
//! - Dispensers/Droppers
|
||||
//! - Barrels
|
||||
//!
|
||||
//! These containers have a simple grid layout with no special behaviors
|
||||
//! (no smelting, no crafting, just item storage).
|
||||
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
|
||||
@@ -12,6 +23,9 @@ use crate::{
|
||||
slot::NormalSlot,
|
||||
};
|
||||
|
||||
/// Creates a generic 9x3 container (single chest).
|
||||
///
|
||||
/// Used for single chests, ender chests, and similar containers.
|
||||
pub async fn create_generic_9x3(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -28,6 +42,9 @@ pub async fn create_generic_9x3(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a generic 9x6 container (double chest).
|
||||
///
|
||||
/// Used for double chests and similar large containers.
|
||||
pub async fn create_generic_9x6(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -44,6 +61,9 @@ pub async fn create_generic_9x6(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a generic 3x3 container.
|
||||
///
|
||||
/// Used for dispensers, droppers, and similar containers.
|
||||
pub async fn create_generic_3x3(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -60,6 +80,9 @@ pub async fn create_generic_3x3(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a hopper container (5 slots).
|
||||
///
|
||||
/// Hoppers have a single row of 5 slots.
|
||||
pub async fn create_hopper(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
@@ -76,14 +99,31 @@ pub async fn create_hopper(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Generic container screen handler.
|
||||
///
|
||||
/// Handles simple grid-based containers without special behaviors.
|
||||
/// The container grid is followed by the player's inventory (27 slots + 9 hotbar).
|
||||
pub struct GenericContainerScreenHandler {
|
||||
/// The container's inventory.
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
/// Number of rows in the container grid.
|
||||
pub rows: u8,
|
||||
/// Number of columns in the container grid.
|
||||
pub columns: u8,
|
||||
/// Core screen handler behavior (slots, sync ID, listeners).
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
}
|
||||
|
||||
impl GenericContainerScreenHandler {
|
||||
/// Creates a new generic container screen handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `screen_type` - The window type for this container
|
||||
/// - `sync_id` - The sync ID for client-server matching
|
||||
/// - `player_inventory` - The player's inventory
|
||||
/// - `inventory` - The container's inventory
|
||||
/// - `rows` - Number of rows in the container
|
||||
/// - `columns` - Number of columns in the container
|
||||
async fn new(
|
||||
screen_type: WindowType,
|
||||
sync_id: u8,
|
||||
@@ -109,6 +149,7 @@ impl GenericContainerScreenHandler {
|
||||
handler
|
||||
}
|
||||
|
||||
/// Adds slots for the container's inventory grid.
|
||||
fn add_inventory_slots(&mut self) {
|
||||
for i in 0..self.rows {
|
||||
for j in 0..self.columns {
|
||||
@@ -122,13 +163,6 @@ impl GenericContainerScreenHandler {
|
||||
}
|
||||
|
||||
impl ScreenHandler for GenericContainerScreenHandler {
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
self.inventory.on_close().await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -141,6 +175,17 @@ impl ScreenHandler for GenericContainerScreenHandler {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
self.inventory.on_close().await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Quick move logic for generic containers.
|
||||
///
|
||||
/// - From container: Move to player inventory (end first)
|
||||
/// - From player inventory: Move to container (start first)
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
//! Pumpkin inventory system.
|
||||
//!
|
||||
//! This crate provides the inventory management system for the Pumpkin Minecraft server.
|
||||
//! It handles player inventories, container screens (like chests, furnaces, crafting tables),
|
||||
//! slot interactions, item dragging, and inventory synchronization between server and client.
|
||||
//!
|
||||
//! # Core Concepts
|
||||
//!
|
||||
//! - [`Inventory`] - A trait representing any item storage (player inventory, chest, furnace, etc.)
|
||||
//! - [`ScreenHandler`] - Manages the UI screen for a container, handling slot layout and interactions
|
||||
//! - [`Slot`] - Represents a single slot in an inventory that can hold items
|
||||
//! - [`PlayerInventory`] - The player's 36-slot main inventory plus equipment slots
|
||||
//! - [`SyncHandler`] - Synchronizes inventory state between server and client
|
||||
//!
|
||||
//! # Module Structure
|
||||
//!
|
||||
//! - [`player`] - Player inventory and screen handler implementations
|
||||
//! - [`crafting`] - Crafting table and inventory crafting mechanics
|
||||
//! - [`furnace_like`] - Furnace, smoker, and blast furnace screen handlers
|
||||
//! - [`brewing`] - Brewing stand handling
|
||||
//! - [`slot`] - Slot trait and implementations (normal slots, armor slots)
|
||||
//! - [`container_click`] - Mouse and keyboard click handling
|
||||
//! - [`drag_handler`] - Item dragging across multiple slots
|
||||
//! - [`sync_handler`] - Client-server inventory synchronization
|
||||
//! - [`window_property`] - Container UI properties (furnace progress, enchantment levels, etc.)
|
||||
//!
|
||||
//! [`Inventory`]: pumpkin_world::inventory::Inventory
|
||||
//! [`ScreenHandler`]: screen_handler::ScreenHandler
|
||||
//! [`Slot`]: slot::Slot
|
||||
//! [`PlayerInventory`]: PlayerInventory
|
||||
//! [`SyncHandler`]: sync_handler::SyncHandler
|
||||
|
||||
pub mod brewing;
|
||||
pub mod container_click;
|
||||
pub mod crafting;
|
||||
@@ -20,6 +52,13 @@ use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
|
||||
use crate::player::player_inventory::PlayerInventory;
|
||||
|
||||
/// Builds a map of slot indices to equipment slots for the player's inventory.
|
||||
///
|
||||
/// This creates the mapping between UI slot indices and equipment slots
|
||||
/// (head, chest, legs, feet, off-hand) used by the player screen handler.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `HashMap` where keys are slot indices and values are the corresponding [`EquipmentSlot`]s.
|
||||
#[must_use]
|
||||
pub fn build_equipment_slots() -> HashMap<usize, EquipmentSlot> {
|
||||
let mut equipment_slots = HashMap::new();
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
//! Ender chest inventory implementation.
|
||||
//!
|
||||
//! Ender chests are player-specific storage that persist across dimensions.
|
||||
//! Each player has their own ender chest contents that is accessible from
|
||||
//! any ender chest block. The inventory syncs across all ender chests
|
||||
//! for that player.
|
||||
//!
|
||||
//! # Viewer Tracking
|
||||
//!
|
||||
//! Ender chests track when players open and close them to properly
|
||||
//! manage the viewer count for animation purposes.
|
||||
|
||||
use std::{any::Any, array::from_fn, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
@@ -7,8 +19,17 @@ use pumpkin_world::{
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// A player's ender chest inventory.
|
||||
///
|
||||
/// Stores 27 slots (like a single chest) that are private to each player.
|
||||
/// Contents persist across dimensions and are accessible from any
|
||||
/// ender chest block.
|
||||
pub struct EnderChestInventory {
|
||||
/// The 27 item slots in the ender chest.
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
/// Viewer count tracker for lid animation.
|
||||
///
|
||||
/// Tracks how many players have the ender chest open to animate the lid.
|
||||
pub tracker: Mutex<Option<Arc<ViewerCountTracker>>>,
|
||||
}
|
||||
|
||||
@@ -19,8 +40,10 @@ impl Default for EnderChestInventory {
|
||||
}
|
||||
|
||||
impl EnderChestInventory {
|
||||
/// The size of an ender chest inventory (27 slots).
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
|
||||
/// Creates a new empty ender chest inventory.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -29,14 +52,19 @@ impl EnderChestInventory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the viewer count tracker for this inventory.
|
||||
///
|
||||
/// Used to animate the ender chest lid based on viewers.
|
||||
pub async fn set_tracker(&self, tracker: Arc<ViewerCountTracker>) {
|
||||
self.tracker.lock().await.replace(tracker);
|
||||
}
|
||||
|
||||
/// Checks if this inventory has a tracker set.
|
||||
pub async fn has_tracker(&self) -> bool {
|
||||
self.tracker.lock().await.is_some()
|
||||
}
|
||||
|
||||
/// Checks if the given tracker is associated with this inventory.
|
||||
pub async fn is_tracker(&self, tracker: &Arc<ViewerCountTracker>) -> bool {
|
||||
if let Some(value) = self.tracker.lock().await.as_ref() {
|
||||
return Arc::ptr_eq(value, tracker);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//! Player inventory module.
|
||||
//!
|
||||
//! This module contains the player's inventory implementation, including:
|
||||
//! - [`PlayerInventory`] - The 36-slot main inventory plus equipment slots
|
||||
//! - [`PlayerScreenHandler`] - The screen handler for the player's inventory UI
|
||||
//! - [`EnderChestInventory`] - The ender chest storage
|
||||
|
||||
pub mod ender_chest_inventory;
|
||||
pub mod player_inventory;
|
||||
pub mod player_screen_handler;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! Player inventory implementation.
|
||||
//!
|
||||
//! This module implements the player's inventory, which consists of:
|
||||
//! - 36 main inventory slots (3 rows of 9 + hotbar)
|
||||
//! - Equipment slots (armor + off-hand)
|
||||
//!
|
||||
//! The first 9 slots of the main inventory are the hotbar (accessible with number keys).
|
||||
//! Slots 0-35 are the main inventory, with slot 40 being the off-hand slot.
|
||||
|
||||
use crate::entity_equipment::EntityEquipment;
|
||||
use crate::screen_handler::InventoryPlayer;
|
||||
|
||||
@@ -5,8 +14,8 @@ use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
|
||||
use pumpkin_util::Hand;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory};
|
||||
use pumpkin_world::inventory::{InventoryFuture, split_stack};
|
||||
use pumpkin_world::inventory::split_stack;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use std::any::Any;
|
||||
use std::array::from_fn;
|
||||
use std::collections::HashMap;
|
||||
@@ -16,18 +25,40 @@ use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::warn;
|
||||
|
||||
/// The player's inventory.
|
||||
///
|
||||
/// Contains 36 main inventory slots (hotbar + main storage) plus
|
||||
/// equipment slots accessed through [`EntityEquipment`].
|
||||
pub struct PlayerInventory {
|
||||
/// The 36 main inventory slots (slots 0-35).
|
||||
///
|
||||
/// The first 9 slots (0-8) are the hotbar, the remaining 27 (9-35) are the main storage.
|
||||
pub main_inventory: [Arc<Mutex<ItemStack>>; Self::MAIN_SIZE],
|
||||
/// Mapping of slot indices to equipment slot types.
|
||||
///
|
||||
/// Used to identify which slots correspond to armor and off-hand equipment.
|
||||
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
||||
/// The currently selected hotbar slot index (0-8).
|
||||
selected_slot: AtomicU8,
|
||||
/// The entity equipment storage for armor and off-hand items.
|
||||
///
|
||||
/// This is separate from the main inventory and is rendered on the player model.
|
||||
pub entity_equipment: Arc<Mutex<EntityEquipment>>,
|
||||
}
|
||||
|
||||
impl PlayerInventory {
|
||||
/// Size of the main inventory (36 slots: 27 storage + 9 hotbar).
|
||||
pub const MAIN_SIZE: usize = 36;
|
||||
/// Size of the hotbar (9 slots).
|
||||
const HOTBAR_SIZE: usize = 9;
|
||||
/// Slot index for the off-hand (40).
|
||||
pub const OFF_HAND_SLOT: usize = 40;
|
||||
|
||||
/// Creates a new player inventory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `entity_equipment` - The entity equipment storage for armor/off-hand
|
||||
/// - `equipment_slots` - Mapping of slot indices to equipment slots
|
||||
// TODO: Add inventory load from nbt
|
||||
pub fn new(
|
||||
entity_equipment: Arc<Mutex<EntityEquipment>>,
|
||||
@@ -42,7 +73,11 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
/// getSelectedStack in source
|
||||
/// Gets the item in the currently selected hotbar slot.
|
||||
///
|
||||
/// This is the item the player is currently holding in their main hand.
|
||||
///
|
||||
/// Mojang name: `getSelectedStack`
|
||||
pub fn held_item(&self) -> Arc<Mutex<ItemStack>> {
|
||||
self.main_inventory
|
||||
.get(self.get_selected_slot() as usize)
|
||||
@@ -50,6 +85,10 @@ impl PlayerInventory {
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Gets the item in the specified hand.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `hand` - Which hand to get the item from
|
||||
pub async fn get_stack_in_hand(&self, hand: Hand) -> Arc<Mutex<ItemStack>> {
|
||||
match hand {
|
||||
Hand::Left => self.off_hand_item().await,
|
||||
@@ -57,12 +96,18 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
/// getOffHandStack in source
|
||||
/// Gets the item in the off-hand.
|
||||
///
|
||||
/// Mojang name: `getOffHandStack`
|
||||
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> {
|
||||
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
|
||||
self.entity_equipment.lock().await.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) {
|
||||
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
@@ -73,11 +118,13 @@ impl PlayerInventory {
|
||||
(main_hand_item.clone(), off_hand_item)
|
||||
}
|
||||
|
||||
/// Checks if a slot index is a valid hotbar slot.
|
||||
#[must_use]
|
||||
pub const fn is_valid_hotbar_index(slot: usize) -> bool {
|
||||
slot < Self::HOTBAR_SIZE
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
@@ -88,10 +135,13 @@ impl PlayerInventory {
|
||||
if slot_index == -1 {
|
||||
stack.item_count as usize
|
||||
} else {
|
||||
return self.add_stack_to_slot(slot_index as usize, stack).await;
|
||||
self.add_stack_to_slot(slot_index as usize, stack).await
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let mut stack_count = stack.item_count;
|
||||
let binding = self.get_stack(slot).await;
|
||||
@@ -112,6 +162,10 @@ impl PlayerInventory {
|
||||
stack_count as usize
|
||||
}
|
||||
|
||||
/// Finds an empty slot in the inventory.
|
||||
///
|
||||
/// # Returns
|
||||
/// The slot index or -1 if inventory is full.
|
||||
async fn get_empty_slot(&self) -> i16 {
|
||||
for i in 0..Self::MAIN_SIZE {
|
||||
if self.main_inventory[i].lock().await.is_empty() {
|
||||
@@ -122,6 +176,7 @@ impl PlayerInventory {
|
||||
-1
|
||||
}
|
||||
|
||||
/// Checks if a stack can be added to an existing stack.
|
||||
fn can_stack_add_more(existing_stack: &ItemStack, stack: &ItemStack) -> bool {
|
||||
!existing_stack.is_empty()
|
||||
&& existing_stack.are_items_and_components_equal(stack)
|
||||
@@ -129,6 +184,9 @@ impl PlayerInventory {
|
||||
&& existing_stack.item_count < existing_stack.get_max_stack_size()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if Self::can_stack_add_more(
|
||||
&*self
|
||||
@@ -155,10 +213,25 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a stack into any available slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `stack` - The stack to insert (modified in place)
|
||||
///
|
||||
/// # 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
|
||||
}
|
||||
|
||||
/// Inserts a stack into a specific slot or any slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `slot` - The slot index, or -1 for any slot
|
||||
/// - `stack` - The stack to insert (modified in place)
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if any items were inserted, `false` otherwise.
|
||||
pub async fn insert_stack(&self, slot: i16, stack: &mut ItemStack) -> bool {
|
||||
if stack.is_empty() {
|
||||
return false;
|
||||
@@ -186,6 +259,10 @@ impl PlayerInventory {
|
||||
stack.item_count < i
|
||||
}
|
||||
|
||||
/// Finds the first slot containing a matching stack.
|
||||
///
|
||||
/// # Returns
|
||||
/// The slot index or -1 if not found.
|
||||
pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
|
||||
for i in 0..Self::MAIN_SIZE {
|
||||
if !self.main_inventory[i].lock().await.is_empty()
|
||||
@@ -201,7 +278,10 @@ impl PlayerInventory {
|
||||
-1
|
||||
}
|
||||
|
||||
pub async fn get_swappable_hotbar_slot(&self) -> usize {
|
||||
/// 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 selected_slot = self.get_selected_slot() as usize;
|
||||
for i in 0..Self::HOTBAR_SIZE {
|
||||
let check_index = (i + selected_slot) % 9;
|
||||
@@ -210,18 +290,17 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..Self::HOTBAR_SIZE {
|
||||
if let Some(i) = (0..Self::HOTBAR_SIZE).next() {
|
||||
let check_index = (i + selected_slot) % 9;
|
||||
if true
|
||||
/*TODO: If item has an enchantment skip it */
|
||||
{
|
||||
return check_index;
|
||||
}
|
||||
return check_index;
|
||||
}
|
||||
|
||||
self.get_selected_slot() as usize
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8);
|
||||
|
||||
@@ -247,6 +326,7 @@ impl PlayerInventory {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Swaps the items at two slot indices.
|
||||
pub async fn swap_slot_with_hotbar(&self, slot: usize) {
|
||||
self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8);
|
||||
let stack = self.main_inventory[self.get_selected_slot() as usize]
|
||||
@@ -261,10 +341,17 @@ impl PlayerInventory {
|
||||
self.set_stack(slot, stack).await;
|
||||
}
|
||||
|
||||
/// Gives a stack to the player or drops it if inventory is full.
|
||||
pub async fn offer_or_drop_stack(&self, stack: ItemStack, player: &dyn InventoryPlayer) {
|
||||
self.offer(stack, true, player).await;
|
||||
}
|
||||
|
||||
/// Gives a stack to the player, optionally notifying the client.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `stack` - The stack to give
|
||||
/// - `notify_client` - Whether to send inventory update packets
|
||||
/// - `player` - The player to give the stack to
|
||||
pub async fn offer(&self, stack: ItemStack, notify_client: bool, player: &dyn InventoryPlayer) {
|
||||
let mut stack = stack;
|
||||
while !stack.is_empty() {
|
||||
@@ -361,6 +448,24 @@ impl Inventory for PlayerInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.main_inventory[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
} else {
|
||||
let slot = self.equipment_slots.get(&slot).unwrap();
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.put(slot, ItemStack::EMPTY.clone())
|
||||
.await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
@@ -380,24 +485,6 @@ impl Inventory for PlayerInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.main_inventory[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
} else {
|
||||
let slot = self.equipment_slots.get(&slot).unwrap();
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.put(slot, ItemStack::EMPTY.clone())
|
||||
.await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
@@ -418,6 +505,10 @@ impl Inventory for PlayerInventory {
|
||||
}
|
||||
|
||||
impl PlayerInventory {
|
||||
/// Sets the selected hotbar slot.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the slot index is not a valid hotbar index.
|
||||
pub fn set_selected_slot(&self, slot: u8) {
|
||||
if Self::is_valid_hotbar_index(slot as usize) {
|
||||
self.selected_slot.store(slot, Ordering::Relaxed);
|
||||
@@ -426,6 +517,7 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the currently selected hotbar slot index.
|
||||
pub fn get_selected_slot(&self) -> u8 {
|
||||
self.selected_slot.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
//! Player inventory screen handler.
|
||||
//!
|
||||
//! This module handles the player's inventory screen (opened with E key).
|
||||
//! It includes:
|
||||
//! - The 2x2 crafting grid (inventory crafting)
|
||||
//! - Armor slots (head, chest, legs, feet)
|
||||
//! - Main inventory (27 slots)
|
||||
//! - Hotbar (9 slots)
|
||||
//! - Offhand slot
|
||||
//!
|
||||
//! # Slot Layout
|
||||
//!
|
||||
//! The player screen handler uses the following slot indices:
|
||||
//! - 0: Crafting result
|
||||
//! - 1-4: Crafting grid (2x2)
|
||||
//! - 5-8: Armor slots (head, chest, legs, feet)
|
||||
//! - 9-35: Main inventory
|
||||
//! - 36-44: Hotbar
|
||||
//! - 45: Offhand
|
||||
|
||||
use super::player_inventory::PlayerInventory;
|
||||
use crate::crafting::crafting_inventory::CraftingInventory;
|
||||
use crate::crafting::crafting_screen_handler::CraftingScreenHandler;
|
||||
@@ -13,8 +33,14 @@ use pumpkin_world::inventory::Inventory;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Screen handler for the player's inventory.
|
||||
///
|
||||
/// Manages the player's inventory UI including crafting, armor, and
|
||||
/// the main inventory. This is the default screen shown when pressing E.
|
||||
pub struct PlayerScreenHandler {
|
||||
/// Core screen handler behavior (slots, sync ID, listeners).
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
/// The 2x2 crafting grid inventory.
|
||||
crafting_inventory: Arc<dyn RecipeInputInventory>,
|
||||
}
|
||||
|
||||
@@ -24,6 +50,7 @@ impl CraftingScreenHandler<CraftingInventory> for PlayerScreenHandler {}
|
||||
|
||||
// TODO: Fully implement this
|
||||
impl PlayerScreenHandler {
|
||||
/// Equipment slot order for armor display.
|
||||
const EQUIPMENT_SLOT_ORDER: [EquipmentSlot; 4] = [
|
||||
EquipmentSlot::HEAD,
|
||||
EquipmentSlot::CHEST,
|
||||
@@ -31,15 +58,25 @@ impl PlayerScreenHandler {
|
||||
EquipmentSlot::FEET,
|
||||
];
|
||||
|
||||
/// Checks if a slot index is in the hotbar.
|
||||
///
|
||||
/// Hotbar slots are 36-44 in the protocol (0-indexed 36-44).
|
||||
#[must_use]
|
||||
pub fn is_in_hotbar(slot: u8) -> bool {
|
||||
(36..=45).contains(&slot)
|
||||
}
|
||||
|
||||
/// Gets a slot by its index.
|
||||
pub fn get_slot(&self, slot: usize) -> Arc<dyn Slot> {
|
||||
self.behaviour.slots[slot].clone()
|
||||
}
|
||||
|
||||
/// Creates a new player screen handler.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `player_inventory` - The player's inventory
|
||||
/// - `window_type` - The window type (usually None for player inventory)
|
||||
/// - `sync_id` - The synchronization ID
|
||||
pub async fn new(
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
window_type: Option<WindowType>,
|
||||
@@ -57,6 +94,7 @@ impl PlayerScreenHandler {
|
||||
.add_recipe_slots(crafting_inventory)
|
||||
.await;
|
||||
|
||||
// Add armor slots (head, chest, legs, feet)
|
||||
for i in 0..4 {
|
||||
player_screen_handler.add_slot(Arc::new(ArmorSlot::new(
|
||||
player_inventory.clone(),
|
||||
@@ -67,10 +105,11 @@ impl PlayerScreenHandler {
|
||||
|
||||
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
|
||||
|
||||
// Add main inventory and hotbar
|
||||
player_screen_handler.add_player_slots(&player_inventory);
|
||||
|
||||
// Offhand
|
||||
// TODO: public void setStack(ItemStack stack, ItemStack previousStack) { owner.onEquipStack(EquipmentSlot.OFFHAND, previousStack, stack);
|
||||
// Offhand slot (index 40 in player inventory, 45 in screen handler)
|
||||
// TODO: onEquipStack callback for offhand
|
||||
player_screen_handler.add_slot(Arc::new(NormalSlot::new(player_inventory.clone(), 40)));
|
||||
|
||||
player_screen_handler
|
||||
@@ -78,15 +117,6 @@ impl PlayerScreenHandler {
|
||||
}
|
||||
|
||||
impl ScreenHandler for PlayerScreenHandler {
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
//TODO: this.craftingResultInventory.clear();
|
||||
self.drop_inventory(player, self.crafting_inventory.clone())
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -99,9 +129,25 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
/// Do quick move (Shift + Click) for the given slot index.
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
//TODO: this.craftingResultInventory.clear();
|
||||
self.drop_inventory(player, self.crafting_inventory.clone())
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs quick move (shift-click) for the given slot.
|
||||
///
|
||||
/// Returns the moved stack if successful, or `ItemStack::EMPTY` if nothing changed.
|
||||
/// The quick move logic depends on the source slot:
|
||||
/// - Crafting result (0) -> Player inventory (from end)
|
||||
/// - Crafting grid (1-4) -> Player inventory (from start)
|
||||
/// - Armor slots (5-8) -> Player inventory, unequips
|
||||
/// - Armor items -> Armor slots if empty
|
||||
/// - Offhand items -> Offhand slot if empty
|
||||
/// - Main inventory (9-35) -> Hotbar
|
||||
/// - Hotbar (36-44) -> Main inventory
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
@@ -121,7 +167,7 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
.get_data_component::<EquippableImpl>()
|
||||
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
|
||||
|
||||
// Quick move logic
|
||||
// Quick move logic based on source slot
|
||||
let success = if slot_index == 0 {
|
||||
// From crafting result slot (0) -> Player Inventory (9-45, from end)
|
||||
self.insert_item(&mut slot_stack, 9, 45, true).await
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
//! Screen handler module.
|
||||
//!
|
||||
//! This module defines the core screen handler system for container UIs.
|
||||
//! A screen handler manages the server-side state of a container interface,
|
||||
//! handling slot layout, click processing, item transfer, and synchronization
|
||||
//! with the client.
|
||||
//!
|
||||
//! # Core Components
|
||||
//!
|
||||
//! - [`ScreenHandler`] - The main trait for container screen handlers
|
||||
//! - [`ScreenHandlerBehaviour`] - Shared state for all screen handlers
|
||||
//! - [`InventoryPlayer`] - Interface for player interactions with containers
|
||||
//! - [`ScreenProperty`] - Container UI properties (progress bars, etc.)
|
||||
//!
|
||||
//! # Screen Handler Lifecycle
|
||||
//!
|
||||
//! 1. Creation - Screen handler is created with slots and sync ID
|
||||
//! 2. Opening - Player opens the container, sync handler attaches
|
||||
//! 3. Interaction - Click packets are processed, items move between slots
|
||||
//! 4. Closing - Container closes, cursor item is dropped/given to player
|
||||
//!
|
||||
//! # Slot Indexing
|
||||
//!
|
||||
//! Slots are indexed from 0 within each screen handler. Special values:
|
||||
//! - `-1` - Cursor slot (held item)
|
||||
//! - `-999` - Outside inventory (drop to world)
|
||||
|
||||
use crate::{
|
||||
container_click::MouseClick,
|
||||
player::player_inventory::PlayerInventory,
|
||||
@@ -30,8 +57,13 @@ use std::{cmp::max, pin::Pin};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::warn;
|
||||
|
||||
/// Slot index indicating a click outside the inventory.
|
||||
const SLOT_INDEX_OUTSIDE: i32 = -999;
|
||||
|
||||
/// A tracked property for container UI elements.
|
||||
///
|
||||
/// Properties are used to synchronize UI state like furnace progress bars,
|
||||
/// enchantment levels, and other visual indicators between server and client.
|
||||
pub struct ScreenProperty {
|
||||
old_value: i32,
|
||||
index: u8,
|
||||
@@ -39,6 +71,11 @@ pub struct ScreenProperty {
|
||||
}
|
||||
|
||||
impl ScreenProperty {
|
||||
/// Creates a new screen property.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `value` - The property delegate that holds the actual value
|
||||
/// - `index` - The property index for multi-value delegates
|
||||
pub fn new(value: Arc<dyn PropertyDelegate>, index: u8) -> Self {
|
||||
Self {
|
||||
old_value: value.get_property(i32::from(index)),
|
||||
@@ -47,15 +84,20 @@ impl ScreenProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current property value.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> i32 {
|
||||
self.value.get_property(i32::from(self.index))
|
||||
}
|
||||
|
||||
/// Sets the property value.
|
||||
pub fn set(&mut self, value: i32) {
|
||||
self.value.set_property(i32::from(self.index), value);
|
||||
}
|
||||
|
||||
/// Checks if the value has changed since the last check.
|
||||
///
|
||||
/// Updates the old value to the current value.
|
||||
pub fn has_changed(&mut self) -> bool {
|
||||
let value = self.get();
|
||||
let has_changed = !value.eq(&self.old_value);
|
||||
@@ -64,31 +106,64 @@ impl ScreenProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for async player operations.
|
||||
/// Type alias for async player operations.
|
||||
pub type PlayerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
/// Interface for player interactions with containers.
|
||||
///
|
||||
/// This trait abstracts the player's ability to:
|
||||
/// - Drop items into the world
|
||||
/// - Receive inventory packets
|
||||
/// - Change equipment
|
||||
/// - Receive experience
|
||||
///
|
||||
/// Implementors are typically player entities that can open containers.
|
||||
pub trait InventoryPlayer: Send + Sync {
|
||||
/// Drops an item into the world.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `item` - The item to drop
|
||||
/// - `retain_ownership` - If true, the player keeps ownership (for pickup delay)
|
||||
fn drop_item(&self, item: ItemStack, retain_ownership: bool) -> PlayerFuture<'_, ()>;
|
||||
|
||||
/// Gets the player's inventory.
|
||||
fn get_inventory(&self) -> Arc<PlayerInventory>;
|
||||
|
||||
/// Checks if the player has infinite materials (creative mode).
|
||||
fn has_infinite_materials(&self) -> bool;
|
||||
|
||||
/// Sends a full container content packet.
|
||||
fn enqueue_inventory_packet<'a>(
|
||||
&'a self,
|
||||
packet: &'a CSetContainerContent,
|
||||
) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends a single slot update packet.
|
||||
fn enqueue_slot_packet<'a>(&'a self, packet: &'a CSetContainerSlot) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends a cursor item update packet.
|
||||
fn enqueue_cursor_packet<'a>(&'a self, packet: &'a CSetCursorItem) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends a property update packet.
|
||||
fn enqueue_property_packet<'a>(
|
||||
&'a self,
|
||||
packet: &'a CSetContainerProperty,
|
||||
) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends a player inventory slot update.
|
||||
fn enqueue_slot_set_packet<'a>(
|
||||
&'a self,
|
||||
packet: &'a CSetPlayerInventory,
|
||||
) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends a selected slot update.
|
||||
fn enqueue_set_held_item_packet<'a>(
|
||||
&'a self,
|
||||
packet: &'a CSetSelectedSlot,
|
||||
) -> PlayerFuture<'a, ()>;
|
||||
|
||||
/// Sends an equipment change packet.
|
||||
fn enqueue_equipment_change<'a>(
|
||||
&'a self,
|
||||
slot: &'a EquipmentSlot,
|
||||
@@ -99,6 +174,10 @@ pub trait InventoryPlayer: Send + Sync {
|
||||
fn award_experience(&self, amount: i32) -> PlayerFuture<'_, ()>;
|
||||
}
|
||||
|
||||
/// Gives a stack to the player or drops it if inventory is full.
|
||||
///
|
||||
/// Tries to insert the stack into the player's inventory first,
|
||||
/// and drops it in the world if there's no room.
|
||||
pub async fn offer_or_drop_stack(player: &dyn InventoryPlayer, stack: ItemStack) {
|
||||
// TODO: Super weird disconnect logic in vanilla, investigate this later
|
||||
player
|
||||
@@ -107,37 +186,62 @@ pub async fn offer_or_drop_stack(player: &dyn InventoryPlayer, stack: ItemStack)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Type alias for async screen handler operations.
|
||||
pub type ScreenHandlerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
// Future returning ItemStack (used by quick_move)
|
||||
/// Future type that returns an `ItemStack` (used by `quick_move`).
|
||||
pub type ItemStackFuture<'a> = ScreenHandlerFuture<'a, ItemStack>;
|
||||
|
||||
// Future returning Option<usize>
|
||||
/// Future type that returns an optional slot index.
|
||||
pub type OptionUsizeFuture<'a> = ScreenHandlerFuture<'a, Option<usize>>;
|
||||
|
||||
//ScreenHandler.java
|
||||
/// The main trait for container screen handlers.
|
||||
///
|
||||
/// Screen handlers manage the server-side state of container UIs like chests,
|
||||
/// furnaces, crafting tables, etc. They handle:
|
||||
/// - Slot layout and management
|
||||
/// - Click processing
|
||||
/// - Item transfer logic (shift-click)
|
||||
/// - Client synchronization
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// Implementors must provide:
|
||||
/// - [`get_behaviour`](ScreenHandler::get_behaviour) and [`get_behaviour_mut`](ScreenHandler::get_behaviour_mut)
|
||||
/// - [`quick_move`](ScreenHandler::quick_move) for shift-click behavior
|
||||
/// - [`as_any`](ScreenHandler::as_any) for downcasting
|
||||
// ScreenHandler.java
|
||||
// TODO: Fully implement this
|
||||
pub trait ScreenHandler: Send + Sync {
|
||||
// --- Synchronous Methods (Unchanged) ---
|
||||
// --- Synchronous Methods ---
|
||||
|
||||
/// Gets the window type for this screen handler.
|
||||
fn window_type(&self) -> Option<WindowType> {
|
||||
self.get_behaviour().window_type
|
||||
}
|
||||
|
||||
/// Returns this screen handler as an Any reference.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Gets the sync ID for this screen handler.
|
||||
fn sync_id(&self) -> u8 {
|
||||
self.get_behaviour().sync_id
|
||||
}
|
||||
|
||||
/// Checks if the player can use this container.
|
||||
fn can_use(&self, _player: &dyn InventoryPlayer) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Gets a reference to the screen handler behaviour.
|
||||
fn get_behaviour(&self) -> &ScreenHandlerBehaviour;
|
||||
|
||||
/// Gets a mutable reference to the screen handler behaviour.
|
||||
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour;
|
||||
|
||||
/// Adds a slot to this screen handler.
|
||||
///
|
||||
/// Assigns an ID and sets up tracking for the slot.
|
||||
fn add_slot(&mut self, slot: Arc<dyn Slot>) -> Arc<dyn Slot> {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
slot.set_id(behaviour.slots.len());
|
||||
@@ -148,12 +252,14 @@ pub trait ScreenHandler: Send + Sync {
|
||||
slot
|
||||
}
|
||||
|
||||
/// Adds hotbar slots (0-8) from the player inventory.
|
||||
fn add_player_hotbar_slots(&mut self, player_inventory: &Arc<dyn Inventory>) {
|
||||
for i in 0..9 {
|
||||
self.add_slot(Arc::new(NormalSlot::new(player_inventory.clone(), i)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds main inventory slots (9-35) from the player inventory.
|
||||
fn add_player_inventory_slots(&mut self, player_inventory: &Arc<dyn Inventory>) {
|
||||
for i in 0..3 {
|
||||
for j in 0..9 {
|
||||
@@ -165,11 +271,13 @@ pub trait ScreenHandler: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds all player inventory slots (main + hotbar).
|
||||
fn add_player_slots(&mut self, player_inventory: &Arc<dyn Inventory>) {
|
||||
self.add_player_inventory_slots(player_inventory);
|
||||
self.add_player_hotbar_slots(player_inventory);
|
||||
}
|
||||
|
||||
/// Records a received hash for a slot (for sync tracking).
|
||||
fn set_received_hash(&mut self, slot: usize, hash: OptionalItemStackHash) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
if slot < behaviour.previous_tracked_stacks.len() {
|
||||
@@ -183,36 +291,44 @@ pub trait ScreenHandler: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Records a received stack for a slot (for sync tracking).
|
||||
fn set_received_stack(&mut self, slot: usize, stack: ItemStack) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
behaviour.previous_tracked_stacks[slot].set_received_stack(stack);
|
||||
}
|
||||
|
||||
/// Records a received cursor hash (for sync tracking).
|
||||
fn set_received_cursor_hash(&mut self, hash: OptionalItemStackHash) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
behaviour.previous_cursor_stack.set_received_hash(hash);
|
||||
}
|
||||
|
||||
/// Adds a property to track.
|
||||
fn add_property(&mut self, property: ScreenProperty) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
behaviour.properties.push(property);
|
||||
behaviour.tracked_property_values.push(0);
|
||||
}
|
||||
|
||||
/// Adds multiple properties to track.
|
||||
fn add_properties(&mut self, properties: Vec<ScreenProperty>) {
|
||||
for property in properties {
|
||||
self.add_property(property);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Asynchronous Methods (Refactored) ---
|
||||
// --- Asynchronous Methods ---
|
||||
|
||||
/// Called when the container is closed by the player.
|
||||
///
|
||||
/// Default implementation drops the cursor item.
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Default close behavior - drops the cursor item.
|
||||
fn default_on_closed<'a>(
|
||||
&'a mut self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
@@ -230,6 +346,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Drops all items from an inventory into the world.
|
||||
fn drop_inventory<'a>(
|
||||
&'a self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
@@ -242,6 +359,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Copies tracked slot state from another screen handler.
|
||||
///
|
||||
/// Used when reopening a container to restore previous state.
|
||||
fn copy_shared_slots(
|
||||
&mut self,
|
||||
other: Arc<Mutex<dyn ScreenHandler>>,
|
||||
@@ -278,6 +398,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Synchronizes the full state to the client.
|
||||
///
|
||||
/// Captures current slot states and sends a full update packet.
|
||||
fn sync_state(&mut self) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
@@ -315,6 +438,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds a listener for slot and property changes.
|
||||
fn add_listener(
|
||||
&mut self,
|
||||
listener: Arc<dyn ScreenHandlerListener>,
|
||||
@@ -325,6 +449,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Attaches a sync handler and performs initial sync.
|
||||
fn update_sync_handler(
|
||||
&mut self,
|
||||
sync_handler: Arc<SyncHandler>,
|
||||
@@ -336,6 +461,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sends all updates to the client.
|
||||
///
|
||||
/// Updates tracked slots and properties.
|
||||
fn update_to_client(&mut self) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
for i in 0..self.get_behaviour().slots.len() {
|
||||
@@ -363,6 +491,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates a tracked property value.
|
||||
fn update_tracked_properties(&mut self, idx: i32, value: i32) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
@@ -377,6 +506,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if a property needs to be synced to the client.
|
||||
fn check_property_updates(&mut self, idx: i32, value: i32) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
@@ -396,6 +526,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the tracked state of a slot.
|
||||
fn update_tracked_slot(
|
||||
&mut self,
|
||||
slot: usize,
|
||||
@@ -416,6 +547,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if a slot needs to be synced to the client.
|
||||
fn check_slot_updates(&mut self, slot: usize, stack: ItemStack) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
@@ -435,6 +567,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the cursor stack needs to be synced.
|
||||
fn check_cursor_stack_updates(&mut self) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
@@ -454,6 +587,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sends all content updates to listeners and sync handler.
|
||||
fn send_content_updates(&mut self) -> ScreenHandlerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let slots_len = self.get_behaviour().slots.len();
|
||||
@@ -484,22 +618,26 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if a slot index is valid.
|
||||
fn is_slot_valid(&self, slot: i32) -> ScreenHandlerFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
slot == -1 || slot == -999 || slot < self.get_behaviour().slots.len() as i32
|
||||
})
|
||||
}
|
||||
|
||||
/// Disables synchronization (for batch operations).
|
||||
fn disable_sync(&mut self) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
behaviour.disable_sync = true;
|
||||
}
|
||||
|
||||
/// Re-enables synchronization.
|
||||
fn enable_sync(&mut self) {
|
||||
let behaviour = self.get_behaviour_mut();
|
||||
behaviour.disable_sync = false;
|
||||
}
|
||||
|
||||
/// Gets the screen handler slot index for an inventory slot.
|
||||
fn get_slot_index<'a>(
|
||||
&'a self,
|
||||
inventory: &'a Arc<dyn Inventory>,
|
||||
@@ -513,12 +651,19 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs a quick move (shift-click) from a slot.
|
||||
///
|
||||
/// Must be implemented by concrete screen handlers to define
|
||||
/// where items go when shift-clicked from specific slots.
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
slot_index: i32,
|
||||
) -> ItemStackFuture<'a>;
|
||||
|
||||
/// Inserts an item into a range of slots.
|
||||
///
|
||||
/// First tries to stack with existing items, then fills empty slots.
|
||||
fn insert_item<'a>(
|
||||
&'a mut self,
|
||||
stack: &'a mut ItemStack,
|
||||
@@ -610,6 +755,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles a slot click event.
|
||||
///
|
||||
/// Override for custom click handling. Return true to prevent default handling.
|
||||
fn handle_slot_click<'a>(
|
||||
&'a self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
@@ -624,6 +772,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Public entry point for slot click handling.
|
||||
fn on_slot_click<'a>(
|
||||
&'a mut self,
|
||||
slot_index: i32,
|
||||
@@ -637,6 +786,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Internal slot click handling implementation.
|
||||
///
|
||||
/// Handles all click types: pickup, quick move, swap, throw, drag, clone.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn internal_on_slot_click<'a>(
|
||||
&'a mut self,
|
||||
@@ -1053,20 +1205,34 @@ pub trait ScreenHandlerFactory: Send + Sync {
|
||||
}
|
||||
|
||||
pub struct ScreenHandlerBehaviour {
|
||||
/// Slots in this screen handler (includes both container and player slots).
|
||||
pub slots: Vec<Arc<dyn Slot>>,
|
||||
/// Sync ID for client-server matching (matches the window ID in protocol).
|
||||
pub sync_id: u8,
|
||||
/// Registered listeners for slot/property changes.
|
||||
pub listeners: Vec<Arc<dyn ScreenHandlerListener>>,
|
||||
/// Sync handler for sending updates to the client.
|
||||
pub sync_handler: Option<Arc<SyncHandler>>,
|
||||
/// Current tracked stacks for comparison with previous state.
|
||||
//TODO: Check if this is needed
|
||||
pub tracked_stacks: Vec<ItemStack>,
|
||||
/// The item currently held by the player's cursor (held item).
|
||||
pub cursor_stack: Arc<Mutex<ItemStack>>,
|
||||
/// Previous tracked stacks for detecting changes that need syncing.
|
||||
pub previous_tracked_stacks: Vec<TrackedStack>,
|
||||
/// Previous cursor stack for detecting cursor changes.
|
||||
pub previous_cursor_stack: TrackedStack,
|
||||
/// Revision counter for sync tracking (increments on each change).
|
||||
pub revision: AtomicU32,
|
||||
/// Whether sync is temporarily disabled (for batch operations).
|
||||
pub disable_sync: bool,
|
||||
/// Container properties (furnace progress, enchantment levels, etc.).
|
||||
pub properties: Vec<ScreenProperty>,
|
||||
/// Tracked property values for detecting changes.
|
||||
pub tracked_property_values: Vec<i32>,
|
||||
/// The window type for this container ( determines client UI).
|
||||
pub window_type: Option<WindowType>,
|
||||
/// Slots selected during a drag operation (for multi-slot distribution).
|
||||
pub drag_slots: Vec<u32>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
//! Inventory slot implementations.
|
||||
//!
|
||||
//! This module defines the [`Slot`] trait and its implementations. Slots represent
|
||||
//! individual positions in an inventory that can hold items.
|
||||
//!
|
||||
//! # Slot Types
|
||||
//!
|
||||
//! - [`NormalSlot`] - A basic inventory slot with no restrictions
|
||||
//! - [`ArmorSlot`] - An armor slot that only accepts appropriate item types
|
||||
//! (helmets in head slot, chestplates in chest slot, etc.)
|
||||
//!
|
||||
//! # Slot Operations
|
||||
//!
|
||||
//! Slots support various operations:
|
||||
//! - Getting/setting the item stack
|
||||
//! - Checking if items can be inserted
|
||||
//! - Taking items from the slot
|
||||
//! - Marking the slot as changed (dirty)
|
||||
//! - Callbacks for slot interaction events
|
||||
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{
|
||||
@@ -15,22 +35,35 @@ use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use tokio::{sync::Mutex, time::timeout};
|
||||
|
||||
/// Type alias for async slot operations.
|
||||
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
/// A slot in an inventory.
|
||||
///
|
||||
/// The slot trait defines how individual inventory positions behave.
|
||||
/// Different slot types (normal, armor, result slots) implement this
|
||||
/// trait to enforce their specific restrictions.
|
||||
// Slot.java
|
||||
// This is a trait due to crafting slots being a thing
|
||||
pub trait Slot: Send + Sync {
|
||||
/// Returns the inventory containing this slot.
|
||||
fn get_inventory(&self) -> Arc<dyn Inventory>;
|
||||
|
||||
/// Returns the index of this slot within its inventory.
|
||||
fn get_index(&self) -> usize;
|
||||
|
||||
/// Sets the protocol ID of this slot.
|
||||
fn set_id(&self, index: usize);
|
||||
|
||||
/// Used to notify result slots that they need to update their contents. (e.g. refill)
|
||||
/// Note that you **MUST** call this after changing the stack in the slot, and releasing any
|
||||
/// locks to the stack to avoid deadlocks.
|
||||
/// Callback for when an item is quick-moved from this slot.
|
||||
///
|
||||
/// Also see: `ScreenHandler::quick_move`
|
||||
/// Used to notify result slots (like crafting output) that they
|
||||
/// need to refill their contents.
|
||||
///
|
||||
/// # Note
|
||||
/// You **MUST** call this after changing the stack and releasing
|
||||
/// any locks to avoid deadlocks.
|
||||
///
|
||||
/// Also see: [`ScreenHandler::quick_move`](crate::screen_handler::ScreenHandler::quick_move)
|
||||
fn on_quick_move_crafted(
|
||||
&self,
|
||||
_stack: ItemStack,
|
||||
@@ -39,9 +72,9 @@ pub trait Slot: Send + Sync {
|
||||
Box::pin(async {}) // Default implementation
|
||||
}
|
||||
|
||||
/// Callback for when an item is taken from the slot.
|
||||
/// Callback for when an item is taken from this slot.
|
||||
///
|
||||
/// Also see: `safe_take`
|
||||
/// Also see: [`safe_take`]
|
||||
fn on_take_item<'a>(
|
||||
&'a self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
@@ -53,21 +86,29 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
// Used for plugins
|
||||
/// Plugin callback for slot clicks.
|
||||
///
|
||||
/// Called when a player clicks on this slot. Can be used by
|
||||
/// plugins to intercept or modify click behavior.
|
||||
fn on_click(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async {}) // Default implementation
|
||||
}
|
||||
|
||||
/// Checks if the given stack can be inserted into this slot.
|
||||
fn can_insert<'a>(&'a self, _stack: &'a ItemStack) -> BoxFuture<'a, bool> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move { true })
|
||||
}
|
||||
|
||||
/// Gets the stack in this slot.
|
||||
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await })
|
||||
}
|
||||
|
||||
/// Gets a copy of the stack in this slot.
|
||||
///
|
||||
/// Acquires a lock and returns a clone of the stack.
|
||||
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -80,6 +121,7 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if this slot has a non-empty stack.
|
||||
fn has_stack(&self) -> BoxFuture<'_, bool> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -92,7 +134,10 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Make sure to drop any locks to the slot stack before calling this
|
||||
/// Sets the stack in this slot.
|
||||
///
|
||||
/// # Note
|
||||
/// Make sure to drop any locks to the slot stack before calling this.
|
||||
fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -100,7 +145,9 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Changes the stack in the slot with the given `stack`.
|
||||
/// Sets the stack with previous stack reference.
|
||||
///
|
||||
/// Some slots (like armor) need to know the previous stack for callbacks.
|
||||
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -108,6 +155,7 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the stack without calling callbacks.
|
||||
fn set_stack_no_callbacks(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -117,13 +165,18 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()>; // This method must be implemented by concrete types
|
||||
/// Marks this slot as changed.
|
||||
///
|
||||
/// Must be implemented by concrete types.
|
||||
fn mark_dirty(&self) -> BoxFuture<'_, ()>;
|
||||
|
||||
/// Gets the maximum item count for this slot.
|
||||
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move { self.get_inventory().get_max_count_per_stack() })
|
||||
}
|
||||
|
||||
/// Gets the maximum item count for the given stack in this slot.
|
||||
fn get_max_item_count_for_stack<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, u8> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -133,7 +186,7 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes a specific amount of items from the slot.
|
||||
/// Removes a specific amount of items from this slot.
|
||||
///
|
||||
/// Mojang name: `remove`
|
||||
fn take_stack(&self, amount: u8) -> BoxFuture<'_, ItemStack> {
|
||||
@@ -144,12 +197,16 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the player can take items from this slot.
|
||||
///
|
||||
/// Mojang name: `mayPickup`
|
||||
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move { true })
|
||||
}
|
||||
|
||||
/// Checks if this slot can be modified by the player.
|
||||
///
|
||||
/// Mojang name: `allowModification`
|
||||
fn allow_modification<'a>(&'a self, player: &'a dyn InventoryPlayer) -> BoxFuture<'a, bool> {
|
||||
// Default implementation logic:
|
||||
@@ -159,6 +216,11 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to take a stack in the given range.
|
||||
///
|
||||
/// Returns `None` if can't take items or if slot is empty.
|
||||
/// For result slots, cannot take partial stacks.
|
||||
///
|
||||
/// Mojang name: `tryRemove`
|
||||
fn try_take_stack_range<'a>(
|
||||
&'a self,
|
||||
@@ -193,8 +255,9 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Safely tries to take a stack of items from the slot, returning `None` if the stack is empty.
|
||||
/// Considering such as result slots, as their stacks cannot split.
|
||||
/// Safely tries to take a stack of items from the slot.
|
||||
///
|
||||
/// Returns an empty stack if can't take. Triggers callbacks.
|
||||
///
|
||||
/// Mojang name: `safeTake`
|
||||
fn safe_take<'a>(
|
||||
@@ -214,6 +277,9 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts a stack into this slot.
|
||||
///
|
||||
/// Returns any leftover items that couldn't fit.
|
||||
fn insert_stack(&self, stack: ItemStack) -> BoxFuture<'_, ItemStack> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -222,6 +288,9 @@ pub trait Slot: Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts a specific count from a stack.
|
||||
///
|
||||
/// Returns any leftover items.
|
||||
fn insert_stack_count(&self, mut stack: ItemStack, count: u8) -> BoxFuture<'_, ItemStack> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
@@ -254,14 +323,25 @@ pub trait Slot: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Just called Slot in Vanilla
|
||||
/// A normal inventory slot.
|
||||
///
|
||||
/// Just called `Slot` in vanilla Minecraft. This is the basic
|
||||
/// slot implementation with no special restrictions.
|
||||
pub struct NormalSlot {
|
||||
/// The inventory containing this slot.
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
/// Index of this slot within its inventory.
|
||||
pub index: usize,
|
||||
/// Protocol ID for this slot (assigned by screen handler).
|
||||
pub id: AtomicU8,
|
||||
}
|
||||
|
||||
impl NormalSlot {
|
||||
/// Creates a new normal slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `inventory` - The containing inventory
|
||||
/// - `index` - The slot index within the inventory
|
||||
pub fn new(inventory: Arc<dyn Inventory>, index: usize) -> Self {
|
||||
Self {
|
||||
inventory,
|
||||
@@ -270,6 +350,7 @@ impl NormalSlot {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Slot for NormalSlot {
|
||||
fn get_inventory(&self) -> Arc<dyn Inventory> {
|
||||
self.inventory.clone()
|
||||
@@ -290,15 +371,32 @@ impl Slot for NormalSlot {
|
||||
}
|
||||
}
|
||||
|
||||
/// An armor equipment slot.
|
||||
///
|
||||
/// Restricts which items can be placed based on the equipment slot type:
|
||||
/// - Head: Helmets, skulls, carved pumpkins
|
||||
/// - Chest: Chestplates, elytra
|
||||
/// - Legs: Leggings
|
||||
/// - Feet: Boots
|
||||
// ArmorSlot.java
|
||||
pub struct ArmorSlot {
|
||||
/// The inventory containing this slot (usually player inventory).
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
/// Index of this slot within its inventory.
|
||||
pub index: usize,
|
||||
/// Protocol ID for this slot (assigned by screen handler).
|
||||
pub id: AtomicU8,
|
||||
/// The equipment slot type (head, chest, legs, feet, or off-hand).
|
||||
pub equipment_slot: EquipmentSlot,
|
||||
}
|
||||
|
||||
impl ArmorSlot {
|
||||
/// Creates a new armor slot.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `inventory` - The containing inventory
|
||||
/// - `index` - The slot index
|
||||
/// - `equipment_slot` - The equipment slot type (head, chest, legs, feet)
|
||||
pub fn new(inventory: Arc<dyn Inventory>, index: usize, equipment_slot: EquipmentSlot) -> Self {
|
||||
Self {
|
||||
inventory,
|
||||
@@ -322,17 +420,7 @@ impl Slot for ArmorSlot {
|
||||
self.id.store(id as u8, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
|
||||
Box::pin(async move { 1 })
|
||||
}
|
||||
|
||||
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
//TODO: this.entity.onEquipStack(this.equipmentSlot, previousStack, stack);
|
||||
self.set_stack_no_callbacks(stack).await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Restricts inserts to appropriate armor types.
|
||||
fn can_insert<'a>(&'a self, stack: &'a ItemStack) -> BoxFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
match self.equipment_slot {
|
||||
@@ -347,10 +435,10 @@ impl Slot for ArmorSlot {
|
||||
})
|
||||
}
|
||||
|
||||
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> {
|
||||
fn set_stack_prev(&self, stack: ItemStack, _previous_stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO: Check enchantments
|
||||
true
|
||||
//TODO: this.entity.onEquipStack(this.equipmentSlot, previousStack, stack);
|
||||
self.set_stack_no_callbacks(stack).await;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,4 +447,17 @@ impl Slot for ArmorSlot {
|
||||
self.inventory.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
/// Armor slots can only hold one item.
|
||||
fn get_max_item_count(&self) -> BoxFuture<'_, u8> {
|
||||
Box::pin(async move { 1 })
|
||||
}
|
||||
|
||||
/// TODO: Check for curse of binding enchantment.
|
||||
fn can_take_items(&self, _player: &dyn InventoryPlayer) -> BoxFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
// TODO: Check enchantments
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
//! Inventory synchronization handler.
|
||||
//!
|
||||
//! This module handles the synchronization of inventory state between the server
|
||||
//! and connected clients. It ensures that players see the correct items in slots,
|
||||
//! cursor items, and container properties (like furnace progress).
|
||||
//!
|
||||
//! # Synchronization
|
||||
//!
|
||||
//! The sync handler manages:
|
||||
//! - Full container content updates (sent when opening a container or on major changes)
|
||||
//! - Individual slot updates (sent when a single slot changes)
|
||||
//! - Cursor item updates (the item being held by the mouse cursor)
|
||||
//! - Property updates (container-specific data like furnace burn time)
|
||||
//!
|
||||
//! # Revision Tracking
|
||||
//!
|
||||
//! Each synchronization message includes a revision number to ensure the client
|
||||
//! and server stay in sync. If the client detects a desync, it can request a full
|
||||
//! resynchronization.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
@@ -14,7 +34,18 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::screen_handler::{InventoryPlayer, ScreenHandlerBehaviour};
|
||||
|
||||
/// Handles inventory synchronization to a specific player.
|
||||
///
|
||||
/// The sync handler stores a reference to the player and sends inventory
|
||||
/// update packets when container state changes. It manages:
|
||||
/// - Full content synchronization
|
||||
/// - Incremental slot updates
|
||||
/// - Cursor item tracking
|
||||
/// - Property (UI element) updates
|
||||
pub struct SyncHandler {
|
||||
/// The player to synchronize inventory updates with.
|
||||
///
|
||||
/// None until `store_player` is called to attach a player.
|
||||
player: Mutex<Option<Arc<dyn InventoryPlayer>>>,
|
||||
}
|
||||
|
||||
@@ -25,6 +56,7 @@ impl Default for SyncHandler {
|
||||
}
|
||||
|
||||
impl SyncHandler {
|
||||
/// Creates a new sync handler with no player attached.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -32,10 +64,24 @@ impl SyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the player to synchronize with.
|
||||
///
|
||||
/// Must be called before any sync operations.
|
||||
pub async fn store_player(&self, player: Arc<dyn InventoryPlayer>) {
|
||||
self.player.lock().await.replace(player);
|
||||
}
|
||||
|
||||
/// Sends a full container content update.
|
||||
///
|
||||
/// This sends all slots, the cursor item, and properties to the client.
|
||||
/// Used for initial sync and recovery from desync.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `screen_handler` - The screen handler to sync
|
||||
/// - `stacks` - All slot contents
|
||||
/// - `cursor_stack` - The item held by the cursor
|
||||
/// - `properties` - Container property values
|
||||
/// - `next_revision` - The new revision number
|
||||
pub async fn update_state(
|
||||
&self,
|
||||
screen_handler: &ScreenHandlerBehaviour,
|
||||
@@ -70,6 +116,15 @@ impl SyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates a single slot on the client.
|
||||
///
|
||||
/// More efficient than full sync for single-slot changes.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `screen_handler` - The screen handler
|
||||
/// - `slot` - The slot index that changed
|
||||
/// - `stack` - The new stack in that slot
|
||||
/// - `next_revision` - The new revision number
|
||||
pub async fn update_slot(
|
||||
&self,
|
||||
screen_handler: &ScreenHandlerBehaviour,
|
||||
@@ -89,6 +144,13 @@ impl SyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the cursor item on the client.
|
||||
///
|
||||
/// Sent when the player's held (cursor) item changes.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `screen_handler` - The screen handler
|
||||
/// - `stack` - The new cursor item
|
||||
pub async fn update_cursor_stack(
|
||||
&self,
|
||||
_screen_handler: &ScreenHandlerBehaviour,
|
||||
@@ -103,6 +165,14 @@ impl SyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates a container property on the client.
|
||||
///
|
||||
/// Used for UI elements like furnace progress bars.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `screen_handler` - The screen handler
|
||||
/// - `property` - The property index
|
||||
/// - `value` - The new property value
|
||||
pub async fn update_property(
|
||||
&self,
|
||||
screen_handler: &ScreenHandlerBehaviour,
|
||||
@@ -121,29 +191,45 @@ impl SyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// TrackedSlot in vanilla
|
||||
/// Tracks the last known state of a slot for sync purposes.
|
||||
///
|
||||
/// Used to detect when a slot has changed and needs to be synced to the client.
|
||||
/// Stores either the full stack or a hash for comparison.
|
||||
#[derive(Clone)]
|
||||
pub struct TrackedStack {
|
||||
/// The full item stack last sent to the client.
|
||||
///
|
||||
/// Set when sending full stack data. Cleared when only sending a hash.
|
||||
pub received_stack: Option<ItemStack>,
|
||||
/// The hash of the item stack last sent to the client.
|
||||
///
|
||||
/// Used for lightweight comparison to detect changes.
|
||||
pub received_hash: Option<OptionalItemStackHash>,
|
||||
}
|
||||
|
||||
impl TrackedStack {
|
||||
/// An empty tracked stack with no known state.
|
||||
pub const EMPTY: Self = Self {
|
||||
received_stack: None,
|
||||
received_hash: None,
|
||||
};
|
||||
|
||||
/// Records that we sent this stack to the client.
|
||||
pub fn set_received_stack(&mut self, stack: ItemStack) {
|
||||
self.received_stack = Some(stack);
|
||||
self.received_hash = None;
|
||||
}
|
||||
|
||||
/// Records that we sent this hash to the client.
|
||||
pub fn set_received_hash(&mut self, hash: OptionalItemStackHash) {
|
||||
self.received_hash = Some(hash);
|
||||
self.received_stack = None;
|
||||
}
|
||||
|
||||
/// Checks if the actual stack matches our tracked state.
|
||||
///
|
||||
/// Updates the tracked state to the actual stack if they match.
|
||||
//FIX Methods named `is_*` normally take self by reference or no self. Consider choosing a less ambiguous name.
|
||||
pub fn is_in_sync(&mut self, actual_stack: &ItemStack) -> bool {
|
||||
if let Some(stack) = &self.received_stack {
|
||||
return stack.are_equal(actual_stack);
|
||||
|
||||
@@ -1,13 +1,43 @@
|
||||
//! Window property definitions.
|
||||
//!
|
||||
//! This module defines container-specific UI properties that need to be synchronized
|
||||
//! between server and client. These include progress bars, fuel indicators, and
|
||||
//! other visual elements in container screens.
|
||||
//!
|
||||
//! # Window Properties
|
||||
//!
|
||||
//! Properties are identified by a unique ID and sent to the client to update
|
||||
//! the container's visual state:
|
||||
//! - Furnace: Fire icon animation, smelting progress
|
||||
//! - Enchantment table: Level requirements, available enchantments
|
||||
//! - Brewing stand: Brew time, fuel level
|
||||
//! - Anvil: Repair cost
|
||||
//!
|
||||
//! See the Minecraft wiki for property ID mappings.
|
||||
|
||||
/// Trait for types that can be converted to window property IDs.
|
||||
pub trait WindowPropertyTrait {
|
||||
/// Converts this property to its protocol ID.
|
||||
fn to_id(self) -> i16;
|
||||
}
|
||||
|
||||
/// A window property with a specific value.
|
||||
///
|
||||
/// Used to send property updates to the client (e.g., furnace progress bar).
|
||||
pub struct WindowProperty<T: WindowPropertyTrait> {
|
||||
/// The property type being tracked (e.g., furnace fire icon, progress arrow).
|
||||
window_property: T,
|
||||
/// The current value of the property.
|
||||
value: i16,
|
||||
}
|
||||
|
||||
impl<T: WindowPropertyTrait> WindowProperty<T> {
|
||||
/// Creates a new window property.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `window_property` - The property type
|
||||
/// - `value` - The property value
|
||||
#[must_use]
|
||||
pub const fn new(window_property: T, value: i16) -> Self {
|
||||
Self {
|
||||
window_property,
|
||||
@@ -15,21 +45,34 @@ impl<T: WindowPropertyTrait> WindowProperty<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this property to a tuple of (id, value).
|
||||
#[must_use]
|
||||
pub fn into_tuple(self) -> (i16, i16) {
|
||||
(self.window_property.to_id(), self.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Furnace window properties.
|
||||
pub enum Furnace {
|
||||
/// Fire icon animation level (0-250).
|
||||
FireIcon,
|
||||
/// Maximum fuel burn time.
|
||||
MaximumFuelBurnTime,
|
||||
/// Arrow progress animation (0-250).
|
||||
ProgressArrow,
|
||||
/// Maximum smelting progress time.
|
||||
MaximumProgress,
|
||||
}
|
||||
|
||||
/// Enchantment table window properties.
|
||||
pub enum EnchantmentTable {
|
||||
/// Experience level requirement for a specific slot.
|
||||
LevelRequirement { slot: u8 },
|
||||
/// Random seed for enchantment generation.
|
||||
EnchantmentSeed,
|
||||
/// Enchantment ID for a specific slot.
|
||||
EnchantmentId { slot: u8 },
|
||||
/// Enchantment level for a specific slot.
|
||||
EnchantmentLevel { slot: u8 },
|
||||
}
|
||||
|
||||
@@ -48,29 +91,45 @@ impl WindowPropertyTrait for EnchantmentTable {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Beacon window properties.
|
||||
pub enum Beacon {
|
||||
/// Effect power level (1-4).
|
||||
PowerLevel,
|
||||
/// First selected potion effect ID.
|
||||
FirstPotionEffect,
|
||||
/// Second selected potion effect ID.
|
||||
SecondPotionEffect,
|
||||
}
|
||||
|
||||
/// Anvil window properties.
|
||||
pub enum Anvil {
|
||||
/// Total repair cost in experience levels.
|
||||
RepairCost,
|
||||
}
|
||||
|
||||
/// Brewing stand window properties.
|
||||
pub enum BrewingStand {
|
||||
/// Brewing progress (0-400).
|
||||
BrewTime,
|
||||
/// Fuel time remaining (0-20).
|
||||
FuelTime,
|
||||
}
|
||||
|
||||
/// Stonecutter window properties.
|
||||
pub enum Stonecutter {
|
||||
/// ID of the selected recipe.
|
||||
SelectedRecipe,
|
||||
}
|
||||
|
||||
/// Loom window properties.
|
||||
pub enum Loom {
|
||||
/// ID of the selected pattern.
|
||||
SelectedPattern,
|
||||
}
|
||||
|
||||
/// Lectern window properties.
|
||||
pub enum Lectern {
|
||||
/// Current page number being viewed.
|
||||
PageNumber,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user