fix: allow spectatos to inspect containers

This commit is contained in:
Alexander Medvedev 2026-09-02 18:10:50 +02:00
parent 015091de12
commit 4e6d380bbc
30 changed files with 688 additions and 263 deletions

View File

@ -27,6 +27,7 @@ pub fn create_generic_9x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
player: &dyn InventoryPlayer,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Generic9x3,
@ -35,6 +36,7 @@ pub fn create_generic_9x3(
inventory,
3,
9,
player.is_spectator(),
)
}
@ -45,6 +47,7 @@ pub fn create_generic_9x6(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
player: &dyn InventoryPlayer,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Generic9x6,
@ -53,6 +56,7 @@ pub fn create_generic_9x6(
inventory,
6,
9,
player.is_spectator(),
)
}
@ -63,6 +67,7 @@ pub fn create_generic_3x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
player: &dyn InventoryPlayer,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Generic3x3,
@ -71,6 +76,7 @@ pub fn create_generic_3x3(
inventory,
3,
3,
player.is_spectator(),
)
}
@ -79,6 +85,7 @@ pub fn create_crafter_3x3(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
player: &dyn InventoryPlayer,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Crafter3x3,
@ -87,6 +94,7 @@ pub fn create_crafter_3x3(
inventory,
3,
3,
player.is_spectator(),
)
}
@ -97,6 +105,7 @@ pub fn create_hopper(
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
inventory: Arc<dyn Inventory>,
player: &dyn InventoryPlayer,
) -> GenericContainerScreenHandler {
GenericContainerScreenHandler::new(
WindowType::Hopper,
@ -105,6 +114,7 @@ pub fn create_hopper(
inventory,
1,
5,
player.is_spectator(),
)
}
@ -119,6 +129,8 @@ pub struct GenericContainerScreenHandler {
pub rows: u8,
/// Number of columns in the container grid.
pub columns: u8,
/// Whether the opener is in spectator mode.
pub is_spectator: bool,
/// Core screen handler behavior (slots, sync ID, listeners).
behaviour: ScreenHandlerBehaviour,
}
@ -133,6 +145,7 @@ impl GenericContainerScreenHandler {
/// - `inventory` - The container's inventory
/// - `rows` - Number of rows in the container
/// - `columns` - Number of columns in the container
/// - `is_spectator` - Whether the opener is a spectator
fn new(
screen_type: WindowType,
sync_id: u8,
@ -140,16 +153,19 @@ impl GenericContainerScreenHandler {
inventory: Arc<dyn Inventory>,
rows: u8,
columns: u8,
is_spectator: bool,
) -> Self {
let mut handler = Self {
inventory,
rows,
columns,
is_spectator,
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(screen_type)),
};
// TODO: Add player entity as a parameter
if !is_spectator {
handler.inventory.on_open();
}
handler.add_inventory_slots();
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
@ -190,8 +206,10 @@ impl ScreenHandler for GenericContainerScreenHandler {
fn on_closed(&mut self, player: &dyn InventoryPlayer) {
self.default_on_closed(player);
if !self.is_spectator && !player.is_spectator() {
self.inventory.on_close();
}
}
/// Quick move logic for generic containers.
///

View File

@ -135,6 +135,11 @@ pub trait InventoryPlayer: Send + Sync {
/// Checks if the player is in creative mode.
fn is_creative(&self) -> bool;
/// Checks if the player is in spectator mode.
fn is_spectator(&self) -> bool {
false
}
/// Gets the player's experience level.
fn experience_level(&self) -> i32;

View File

@ -1,8 +1,8 @@
use crate::block::blocks::falling::FallingBlock;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs,
PathComputationType, PlacedArgs,
BlockBehaviour, GetScreenHandlerFactoryArgs, GetStateForNeighborUpdateArgs, NormalUseArgs,
OnPlaceArgs, OnScheduledTickArgs, PathComputationType, PlacedArgs,
};
use pumpkin_data::block_properties::WallTorchLikeProperties;
@ -40,17 +40,32 @@ impl AnvilBlock {
impl BlockBehaviour for AnvilBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithAnvil as i32,
1,
);
args.player
.open_handled_screen(&AnvilScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(AnvilScreenFactory))
}
fn placed(&self, args: PlacedArgs<'_>) {
FallingBlock::placed(&FallingBlock, args);
}

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, OnPlaceArgs, PlacedArgs};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -28,9 +28,9 @@ impl ScreenHandlerFactory for BarrelScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone());
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone(), player);
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
@ -55,21 +55,34 @@ impl BlockBehaviour for BarrelBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenBarrel as i32,
1,
);
args.player
.open_handled_screen(&BarrelScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(BarrelScreenFactory(inventory)))
}
fn placed(&self, args: PlacedArgs<'_>) {
let barrel_block_entity = BarrelBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(barrel_block_entity));

View File

@ -1,7 +1,6 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::entities::BlockEntity;
use pumpkin_data::translation;
use pumpkin_inventory::beacon_screen_handler::create_beacon_handler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
@ -13,7 +12,7 @@ use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::Inventory;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs};
use crate::block::{BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs};
// Create the factory just like ChestScreenFactory
struct BeaconScreenFactory(Arc<dyn Inventory>);
@ -44,13 +43,14 @@ pub struct BeaconBlock;
impl BlockBehaviour for BeaconBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let block_entity = args.world.get_block_entity(args.position);
// Extract the inventory from the entity
let Some(inventory) = block_entity.and_then(BlockEntity::get_inventory) else {
return BlockActionResult::Fail;
};
self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
})
.map_or(BlockActionResult::Fail, |factory| {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBeacon as i32,
@ -59,8 +59,18 @@ impl BlockBehaviour for BeaconBlock {
// Open the screen using the factory
args.player
.open_handled_screen(&BeaconScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
BlockActionResult::Success
})
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(BeaconScreenFactory(inventory)))
}
}

View File

@ -18,8 +18,8 @@ use pumpkin_world::inventory::Inventory;
use crate::{
block::{
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, registry::BlockActionResult,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs,
NormalUseArgs, OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -78,25 +78,37 @@ pub struct BlastFurnaceBlock;
impl BlockBehaviour for BlastFurnaceBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBlastFurnace as i32,
1,
);
let blasting_furnace_screen_factory = BlastingFurnaceScreenFactory::new(
args.player
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.clone().get_inventory()?;
let property_delegate = block_entity.clone().to_property_delegate()?;
let experience_container = block_entity.to_experience_container()?;
Some(Box::new(BlastingFurnaceScreenFactory::new(
inventory,
property_delegate,
experience_container,
);
args.player
.open_handled_screen(&blasting_furnace_screen_factory, Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {

View File

@ -1,7 +1,9 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, PathComputationType, PlacedArgs};
use crate::block::{
GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, PathComputationType, PlacedArgs,
};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -48,22 +50,35 @@ pub struct BrewingStandBlock;
impl BlockBehaviour for BrewingStandBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(pd) = block_entity.clone().to_property_delegate()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithBrewingstand as i32,
1,
);
args.player
.open_handled_screen(&BrewingScreenFactory(inventory, pd), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.clone().get_inventory()?;
let pd = block_entity.to_property_delegate()?;
Some(Box::new(BrewingScreenFactory(inventory, pd)))
}
fn placed(&self, args: PlacedArgs<'_>) {
let be = BrewingStandBlockEntity::new(*args.position);
args.world.add_block_entity(Arc::new(be));

View File

@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs};
use crate::block::{BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::cartography_table_screen_handler::CartographyTableScreenHandler;
@ -17,16 +17,31 @@ pub struct CartographyTableBlock;
impl BlockBehaviour for CartographyTableBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCartographyTable as i32,
1,
);
args.player
.open_handled_screen(&CartographyTableScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(CartographyTableScreenFactory))
}
}
struct CartographyTableScreenFactory;

View File

@ -23,8 +23,9 @@ use std::sync::Mutex;
use crate::block::{
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
GetRedstonePowerArgs, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PathComputationType,
PlacedArgs, PlayerPlacedArgs, RandomTickArgs, registry::BlockActionResult,
GetRedstonePowerArgs, GetScreenHandlerFactoryArgs, NormalUseArgs, OnPlaceArgs,
OnSyncedBlockEventArgs, PathComputationType, PlacedArgs, PlayerPlacedArgs, RandomTickArgs,
registry::BlockActionResult,
};
use crate::entity::EntityBase;
use crate::entity::player::Player;
@ -39,12 +40,12 @@ impl ScreenHandlerFactory for ChestScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = if self.0.size() > 27 {
create_generic_9x6(sync_id, player_inventory, self.0.clone())
create_generic_9x6(sync_id, player_inventory, self.0.clone(), player)
} else {
create_generic_9x3(sync_id, player_inventory, self.0.clone())
create_generic_9x3(sync_id, player_inventory, self.0.clone(), player)
};
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
@ -165,32 +166,17 @@ fn get_chest_comparator_output(args: &GetComparatorOutputArgs<'_>) -> Option<u8>
}
}
fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
let stat = if args.block.id == Block::TRAPPED_CHEST.id {
pumpkin_data::statistic::CustomStatistic::TriggerTrappedChest
} else {
pumpkin_data::statistic::CustomStatistic::OpenChest
};
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
stat as i32,
1,
);
fn get_chest_screen_handler_factory(
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let state = args.world.get_block_state_id(args.position);
let first_chest = args.world.get_block_entity(args.position);
// Spectators cannot open chests with a pending loot table.
// The loot is only generated on first open by a non-spectator.
let player_is_spectator = args.player.gamemode.load() == GameMode::Spectator;
if player_is_spectator
&& let Some(ref entity) = first_chest
&& entity.has_loot_table()
{
return BlockActionResult::Success;
}
// Unpack deferred loot table on first open (non-spectator only).
if let Some(ref entity) = first_chest
if !player_is_spectator
&& let Some(ref entity) = first_chest
&& let Some((loot_key, seed)) = entity.take_loot_table()
&& let Some(table) = get_loot_table(&loot_key)
&& let Some(inv) = entity.clone().get_inventory()
@ -199,9 +185,7 @@ fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
inv.mark_dirty();
}
let Some(first_inventory) = first_chest.and_then(BlockEntity::get_inventory) else {
return BlockActionResult::Fail;
};
let first_inventory = first_chest.and_then(BlockEntity::get_inventory)?;
let chest_props = ChestLikeProperties::from_state_id(state);
let connected_towards = match chest_props.r#type {
@ -211,13 +195,13 @@ fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
};
if is_chest_blocked(args.world, args.position) {
return BlockActionResult::Success;
return None;
}
if let Some(direction) = connected_towards {
let neighbor_pos = args.position.offset(direction.to_offset());
if is_chest_blocked(args.world, &neighbor_pos) {
return BlockActionResult::Success;
return None;
}
}
@ -237,8 +221,31 @@ fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
first_inventory
};
Some(Box::new(ChestScreenFactory(inventory)))
}
fn normal_use_chest_impl(args: &NormalUseArgs<'_>) -> BlockActionResult {
let stat = if args.block.id == Block::TRAPPED_CHEST.id {
pumpkin_data::statistic::CustomStatistic::TriggerTrappedChest
} else {
pumpkin_data::statistic::CustomStatistic::OpenChest
};
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
stat as i32,
1,
);
if let Some(factory) = get_chest_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player
.open_handled_screen(&ChestScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
@ -293,6 +300,13 @@ impl BlockBehaviour for ChestBlock {
normal_use_chest_impl(&args)
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
get_chest_screen_handler_factory(args)
}
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}
@ -365,6 +379,13 @@ impl BlockBehaviour for CopperChestBlock {
normal_use_chest_impl(&args)
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
get_chest_screen_handler_factory(args)
}
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}
@ -428,6 +449,13 @@ impl BlockBehaviour for TrappedChestBlock {
normal_use_chest_impl(&args)
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
get_chest_screen_handler_factory(args)
}
fn broken(&self, args: BrokenArgs<'_>) {
broken_chest_impl(&args);
}

View File

@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs};
use crate::block::{BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler;
@ -17,19 +17,32 @@ pub struct CraftingTableBlock;
impl BlockBehaviour for CraftingTableBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithCraftingTable as i32,
1,
);
let recipe_manager = args.server.recipe_manager.clone();
args.player.open_handled_screen(
&CraftingTableScreenFactory(recipe_manager),
Some(*args.position),
);
args.player
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let recipe_manager = args.server.recipe_manager.clone();
Some(Box::new(CraftingTableScreenFactory(recipe_manager)))
}
}
struct CraftingTableScreenFactory(Arc<crate::server::RecipeManager>);

View File

@ -3,7 +3,9 @@ use std::sync::Mutex;
use crate::block::entities::enchanting_table::EnchantingTableBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs, PathComputationType, PlacedArgs};
use crate::block::{
BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs, PathComputationType, PlacedArgs,
};
use pumpkin_data::{Block, BlockState, BlockStateId, translation};
use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
@ -25,6 +27,23 @@ impl BlockBehaviour for EnchantingTableBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let mut bookshelf_count = 0;
for off_z in -1..=1 {
@ -70,14 +89,10 @@ impl BlockBehaviour for EnchantingTableBlock {
let bookshelf_count = bookshelf_count.min(15);
let seed = args.player.enchantment_seed();
args.player.open_handled_screen(
&EnchantingTableScreenFactory {
Some(Box::new(EnchantingTableScreenFactory {
bookshelf_count,
seed,
},
Some(*args.position),
);
BlockActionResult::Success
}))
}
fn is_pathfindable(&self, _state: &BlockState, _computation_type: PathComputationType) -> bool {

View File

@ -3,8 +3,8 @@ use std::sync::Mutex;
use crate::block::entities::ender_chest::EnderChestBlockEntity;
use crate::block::{
BlockBehaviour, NormalUseArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PathComputationType,
PlacedArgs, registry::BlockActionResult,
BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs, OnPlaceArgs,
OnSyncedBlockEventArgs, PathComputationType, PlacedArgs, registry::BlockActionResult,
};
use crate::world::World;
use pumpkin_data::block_properties::LadderLikeProperties;
@ -30,12 +30,14 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
if let Some(tracker) = &self.tracker {
if !player.is_spectator()
&& let Some(tracker) = &self.tracker
{
self.inventory.set_tracker(tracker.clone());
}
let handler = create_generic_9x3(sync_id, player_inventory, self.inventory.clone());
let handler = create_generic_9x3(sync_id, player_inventory, self.inventory.clone(), player);
let concrete_arc = Arc::new(Mutex::new(handler));
Some(concrete_arc as SharedScreenHandler)
@ -71,8 +73,32 @@ impl BlockBehaviour for EnderChestBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
1,
);
args.player
.open_handled_screen(factory.as_ref(), Some(*args.position));
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
if is_chest_blocked(args.world, args.position) {
return BlockActionResult::Success;
return None;
}
let block_entity = if let Some(be) = args.world.get_block_entity(args.position) {
@ -83,28 +109,16 @@ impl BlockBehaviour for EnderChestBlock {
be
};
if let Some(block_entity) = block_entity
let block_entity = block_entity
.as_any()
.downcast_ref::<EnderChestBlockEntity>()
{
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenEnderchest as i32,
1,
);
.downcast_ref::<EnderChestBlockEntity>()?;
let tracker = block_entity.get_tracker();
let inventory = args.player.ender_chest_inventory();
args.player.open_handled_screen(
&EnderChestScreenFactory {
Some(Box::new(EnderChestScreenFactory {
inventory: inventory.clone(),
tracker: Some(tracker),
},
Some(*args.position),
);
// TODO: PiglinBrain.onGuardedBlockInteracted(serverWorld, player, true);
}
BlockActionResult::Success
}))
}
fn placed(&self, args: PlacedArgs<'_>) {

View File

@ -17,8 +17,8 @@ use pumpkin_world::inventory::Inventory;
use crate::{
block::{
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, registry::BlockActionResult,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs,
NormalUseArgs, OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -77,24 +77,39 @@ pub struct FurnaceBlock;
impl BlockBehaviour for FurnaceBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithFurnace as i32,
1,
);
let furnace_screen_factory =
FurnaceScreenFactory::new(inventory, property_delegate, experience_container);
args.player
.open_handled_screen(&furnace_screen_factory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.clone().get_inventory()?;
let property_delegate = block_entity.clone().to_property_delegate()?;
let experience_container = block_entity.to_experience_container()?;
Some(Box::new(FurnaceScreenFactory::new(
inventory,
property_delegate,
experience_container,
)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args

View File

@ -19,8 +19,8 @@ use pumpkin_world::world::BlockAccessor;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs,
PathComputationType,
BlockBehaviour, CanPlaceAtArgs, GetScreenHandlerFactoryArgs, GetStateForNeighborUpdateArgs,
NormalUseArgs, OnPlaceArgs, PathComputationType,
};
use pumpkin_data::BlockState;
@ -31,17 +31,32 @@ pub struct GrindstoneBlock;
impl BlockBehaviour for GrindstoneBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithGrindstone as i32,
1,
);
args.player
.open_handled_screen(&GrindstoneScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(GrindstoneScreenFactory))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = GrindstoneLikeProperties::from_state_id(args.block.default_state.id);
(props.face, props.facing) =

View File

@ -3,7 +3,8 @@ use std::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::{
GetComparatorOutputArgs, OnNeighborUpdateArgs, OnPlaceArgs, PathComputationType, PlacedArgs,
GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, OnNeighborUpdateArgs, OnPlaceArgs,
PathComputationType, PlacedArgs,
};
use crate::block::{
registry::BlockActionResult,
@ -32,9 +33,9 @@ impl ScreenHandlerFactory for HopperBlockScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let concrete_handler = create_hopper(sync_id, player_inventory, self.0.clone());
let concrete_handler = create_hopper(sync_id, player_inventory, self.0.clone(), player);
let concrete_arc = Arc::new(Mutex::new(concrete_handler));
Some(concrete_arc as SharedScreenHandler)
@ -55,21 +56,34 @@ type HopperLikeProperties = pumpkin_data::block_properties::HopperLikeProperties
impl BlockBehaviour for HopperBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InspectHopper as i32,
1,
);
args.player
.open_handled_screen(&HopperBlockScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(HopperBlockScreenFactory(inventory)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = HopperLikeProperties::default(args.block);
props.facing = match args.direction {

View File

@ -5,8 +5,8 @@ use crate::block::entities::lectern::LecternBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, BrokenArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs,
GetRedstonePowerArgs, NormalUseArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs,
PathComputationType, PlacedArgs, UseWithItemArgs,
GetRedstonePowerArgs, GetScreenHandlerFactoryArgs, NormalUseArgs, OnPlaceArgs,
OnScheduledTickArgs, OnStateReplacedArgs, PathComputationType, PlacedArgs, UseWithItemArgs,
};
use crate::entity::Entity;
use crate::entity::item::ItemEntity;
@ -162,39 +162,50 @@ impl BlockBehaviour for LecternBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let props =
LecternLikeProperties::from_state_id(args.world.get_block_state(args.position).id);
if !props.has_book {
return BlockActionResult::Pass;
}
let Some(block_entity) = args.world.get_block_entity(args.position) else {
return BlockActionResult::Pass;
};
let Some(inventory) = block_entity.get_inventory() else {
return BlockActionResult::Pass;
};
self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
})
.map_or(BlockActionResult::Pass, |factory| {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLectern as i32,
1,
);
args.player
.open_handled_screen(factory.as_ref(), Some(*args.position));
BlockActionResult::Success
})
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let props =
LecternLikeProperties::from_state_id(args.world.get_block_state(args.position).id);
if !props.has_book {
return None;
}
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
let controller = Arc::new(LecternPageController {
world: args.world.clone(),
position: *args.position,
inventory: inventory.clone(),
});
args.player.open_handled_screen(
&LecternScreenFactory {
Some(Box::new(LecternScreenFactory {
inventory,
controller,
},
Some(*args.position),
);
BlockActionResult::Success
}))
}
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {

View File

@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs};
use crate::block::{BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs, OnPlaceArgs};
use crate::entity::EntityBase;
use pumpkin_data::block_properties::WallTorchLikeProperties;
@ -34,16 +34,31 @@ impl BlockBehaviour for LoomBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithLoom as i32,
1,
);
args.player
.open_handled_screen(&LoomScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(LoomScreenFactory))
}
}
struct LoomScreenFactory;

View File

@ -7,8 +7,8 @@ use crate::block::entities::crafter::CrafterBlockEntity;
use crate::block::entities::hopper::HopperBlockEntity;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::Entity;
use crate::entity::item::ItemEntity;
@ -42,9 +42,9 @@ impl ScreenHandlerFactory for CrafterScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_crafter_3x3(sync_id, player_inventory, self.0.clone());
let handler = create_crafter_3x3(sync_id, player_inventory, self.0.clone(), player);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
@ -221,15 +221,28 @@ impl CrafterBlock {
impl BlockBehaviour for CrafterBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player
.open_handled_screen(&CrafterScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(CrafterScreenFactory(inventory)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = CrafterLikeProperties::default(args.block);
let facing = args.direction;

View File

@ -8,8 +8,8 @@ use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::blocks::tnt::TNTBlock;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::decoration::armor_stand::ArmorStandEntity;
use crate::entity::item::ItemEntity;
@ -69,9 +69,9 @@ impl ScreenHandlerFactory for DispenserScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone());
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone(), player);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
@ -124,20 +124,33 @@ const fn to_data3d(facing: Facing) -> i32 {
impl BlockBehaviour for DispenserBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InspectDispenser as i32,
1,
);
args.player
.open_handled_screen(&DispenserScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(DispenserScreenFactory(inventory)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = DispenserLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();

View File

@ -5,8 +5,8 @@ use std::sync::Mutex;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
use crate::block::{
BlockBehaviour, GetComparatorOutputArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
OnScheduledTickArgs, PlacedArgs,
BlockBehaviour, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, NormalUseArgs,
OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, PlacedArgs,
};
use crate::entity::item::ItemEntity;
use crate::entity::{Entity, EntityBase};
@ -37,9 +37,9 @@ impl ScreenHandlerFactory for DropperScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone());
let handler = create_generic_3x3(sync_id, player_inventory, self.0.clone(), player);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
@ -86,20 +86,33 @@ const fn to_data3d(facing: Facing) -> i32 {
impl BlockBehaviour for DropperBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InspectDropper as i32,
1,
);
args.player
.open_handled_screen(&DropperScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(DropperScreenFactory(inventory)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = DispenserLikeProperties::default(args.block);
props.facing = args.player.get_entity().get_facing().opposite();

View File

@ -1,7 +1,10 @@
use std::sync::Arc;
use std::sync::Mutex;
use crate::block::{GetComparatorOutputArgs, OnPlaceArgs, OnSyncedBlockEventArgs, PlacedArgs};
use crate::block::{
GetComparatorOutputArgs, GetScreenHandlerFactoryArgs, OnPlaceArgs, OnSyncedBlockEventArgs,
PlacedArgs,
};
use crate::block::{
registry::BlockActionResult,
{BlockBehaviour, NormalUseArgs},
@ -26,9 +29,9 @@ impl ScreenHandlerFactory for ShulkerBoxScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone());
let handler = create_generic_9x3(sync_id, player_inventory, self.0.clone(), player);
let screen_handler_arc = Arc::new(Mutex::new(handler));
Some(screen_handler_arc as SharedScreenHandler)
@ -68,21 +71,34 @@ impl BlockBehaviour for ShulkerBoxBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::OpenShulkerBox as i32,
1,
);
args.player
.open_handled_screen(&ShulkerBoxScreenFactory(inventory), Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.get_inventory()?;
Some(Box::new(ShulkerBoxScreenFactory(inventory)))
}
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.get_inventory()

View File

@ -1,5 +1,5 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs};
use crate::block::{BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs};
use pumpkin_data::translation;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
@ -17,16 +17,31 @@ pub struct SmithingTableBlock;
impl BlockBehaviour for SmithingTableBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithSmithingTable as i32,
1,
);
args.player
.open_handled_screen(&SmithingTableScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(SmithingTableScreenFactory))
}
}
struct SmithingTableScreenFactory;

View File

@ -17,8 +17,8 @@ use pumpkin_world::inventory::Inventory;
use crate::{
block::{
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs,
PlacedArgs, registry::BlockActionResult,
BlockBehaviour, BrokenArgs, GetComparatorOutputArgs, GetScreenHandlerFactoryArgs,
NormalUseArgs, OnPlaceArgs, PlacedArgs, registry::BlockActionResult,
},
entity::experience_orb::ExperienceOrbEntity,
};
@ -76,24 +76,39 @@ pub struct SmokerBlock;
impl BlockBehaviour for SmokerBlock {
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(block_entity) = args.world.get_block_entity(args.position)
&& let Some(inventory) = block_entity.clone().get_inventory()
&& let Some(property_delegate) = block_entity.clone().to_property_delegate()
&& let Some(experience_container) = block_entity.to_experience_container()
{
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithSmoker as i32,
1,
);
let smoker_screen_factory =
SmokerScreenFactory::new(inventory, property_delegate, experience_container);
args.player
.open_handled_screen(&smoker_screen_factory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
crate::block::registry::BlockActionResult::Consume
}
fn get_screen_handler_factory(
&self,
args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let block_entity = args.world.get_block_entity(args.position)?;
let inventory = block_entity.clone().get_inventory()?;
let property_delegate = block_entity.clone().to_property_delegate()?;
let experience_container = block_entity.to_experience_container()?;
Some(Box::new(SmokerScreenFactory::new(
inventory,
property_delegate,
experience_container,
)))
}
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let mut props = FurnaceLikeProperties::default(args.block);
props.facing = args

View File

@ -1,5 +1,7 @@
use crate::block::registry::BlockActionResult;
use crate::block::{BlockBehaviour, NormalUseArgs, OnPlaceArgs, PathComputationType};
use crate::block::{
BlockBehaviour, GetScreenHandlerFactoryArgs, NormalUseArgs, OnPlaceArgs, PathComputationType,
};
use pumpkin_data::block_properties::WallTorchLikeProperties;
use pumpkin_data::{BlockState, BlockStateId, translation};
@ -30,17 +32,32 @@ impl BlockBehaviour for StonecutterBlock {
}
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if let Some(factory) = self.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server: args.server,
world: args.world,
block: args.block,
position: args.position,
player: args.player,
}) {
args.player.increment_stat(
pumpkin_data::statistic::StatisticCategory::Custom,
pumpkin_data::statistic::CustomStatistic::InteractWithStonecutter as i32,
1,
);
args.player
.open_handled_screen(&StonecutterScreenFactory, Some(*args.position));
.open_handled_screen(factory.as_ref(), Some(*args.position));
}
BlockActionResult::Success
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
Some(Box::new(StonecutterScreenFactory))
}
fn is_pathfindable(&self, _state: &BlockState, _computation_type: PathComputationType) -> bool {
false
}

View File

@ -25,6 +25,7 @@ use pumpkin_data::BlockDirection;
use pumpkin_data::block_rotation::{Mirror, Rotation};
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_inventory::screen_handler::ScreenHandlerFactory;
use pumpkin_protocol::java::server::play::SUseItemOn;
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::vector3::Vector3;
@ -85,6 +86,13 @@ pub trait BlockBehaviour: Send + Sync {
BlockActionResult::Pass
}
fn get_screen_handler_factory(
&self,
_args: GetScreenHandlerFactoryArgs<'_>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
None
}
fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult {
BlockActionResult::PassToDefaultBlockAction
}
@ -224,6 +232,15 @@ pub struct NormalUseArgs<'a> {
pub hit: &'a BlockHitResult<'a>,
}
#[derive(Clone, Copy)]
pub struct GetScreenHandlerFactoryArgs<'a> {
pub server: &'a Server,
pub world: &'a Arc<World>,
pub block: &'a Block,
pub position: &'a BlockPos,
pub player: &'a Arc<Player>,
}
pub struct UseWithItemArgs<'a> {
pub server: &'a Server,
pub world: &'a Arc<World>,

View File

@ -171,9 +171,9 @@ use crate::block::fluid::lava::FlowingLava;
use crate::block::fluid::water::FlowingWater;
use crate::block::{
BlockBehaviour, BlockHitResult, BlockMetadata, BonemealArgs, FluidMetadata,
GetInsideCollisionShapeArgs, OnEntityCollisionArgs, OnEntityStepArgs, OnLandedUponArgs,
OnProjectileHitArgs, PathComputationType, UpdateEntityMovementAfterFallOnArgs,
stop_vertical_movement_after_fall,
GetInsideCollisionShapeArgs, GetScreenHandlerFactoryArgs, OnEntityCollisionArgs,
OnEntityStepArgs, OnLandedUponArgs, OnProjectileHitArgs, PathComputationType,
UpdateEntityMovementAfterFallOnArgs, stop_vertical_movement_after_fall,
};
use crate::entity::EntityBase;
use crate::entity::player::Player;
@ -187,6 +187,7 @@ use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::tag::{self, Taggable};
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState};
use pumpkin_inventory::screen_handler::ScreenHandlerFactory;
use pumpkin_protocol::java::server::play::SUseItemOn;
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::position::BlockPos;
@ -905,6 +906,27 @@ impl BlockRegistry {
BlockActionResult::Pass
}
pub fn get_screen_handler_factory(
&self,
block: &Block,
player: &Arc<Player>,
position: &BlockPos,
server: &Server,
world: &Arc<World>,
) -> Option<Box<dyn ScreenHandlerFactory>> {
let pumpkin_block = self.get_pumpkin_block(block.id);
if let Some(pumpkin_block) = pumpkin_block {
return pumpkin_block.get_screen_handler_factory(GetScreenHandlerFactoryArgs {
server,
world,
block,
position,
player,
});
}
None
}
pub fn explode(&self, block: &Block, world: &Arc<World>, position: &BlockPos) {
let pumpkin_block = self.get_pumpkin_block(block.id);
if let Some(pumpkin_block) = pumpkin_block {

View File

@ -7135,6 +7135,10 @@ impl InventoryPlayer for Player {
self.gamemode.load() == GameMode::Creative
}
fn is_spectator(&self) -> bool {
self.gamemode.load() == GameMode::Spectator
}
fn experience_level(&self) -> i32 {
self.experience_level
.load(std::sync::atomic::Ordering::Relaxed)

View File

@ -182,13 +182,13 @@ impl ScreenHandlerFactory for MinecartScreenFactory {
&self,
sync_id: u8,
player_inventory: &Arc<PlayerInventory>,
_player: &dyn InventoryPlayer,
player: &dyn InventoryPlayer,
) -> Option<SharedScreenHandler> {
let inventory: Arc<dyn Inventory> = self.inventory.clone();
let handler = if self.hopper {
create_hopper(sync_id, player_inventory, inventory)
create_hopper(sync_id, player_inventory, inventory, player)
} else {
create_generic_9x3(sync_id, player_inventory, inventory)
create_generic_9x3(sync_id, player_inventory, inventory, player)
};
Some(Arc::new(Mutex::new(handler)) as SharedScreenHandler)
}

View File

@ -159,7 +159,15 @@ impl BedrockClient {
};
if player.gamemode.load() == GameMode::Spectator {
// TODO: openMenu ?
if let Some(factory) = server.block_registry.get_screen_handler_factory(
block,
player,
&data.block_position,
&server,
&world,
) {
player.open_handled_screen(factory.as_ref(), Some(data.block_position));
}
return;
}

View File

@ -34,7 +34,36 @@ impl JavaClient {
};
if player.gamemode.load() == GameMode::Spectator {
// TODO: openMenu
let entity = &player.get_entity();
let world = entity.world.load_full();
let block = world.get_block(&position);
let event = PlayerInteractEvent::new(
player,
InteractAction::RightClickBlock,
block,
Some(position),
);
send_cancellable_blocking! {{
server;
event;
'cancelled: {
let state_id = world.get_block_state_id(&position);
player.try_send_client_packet(&CBlockUpdate::new(
position,
VarInt(i32::from(state_id.as_u16())),
));
return Ok(());
}
}}
if let Some(factory) = server
.block_registry
.get_screen_handler_factory(block, player, &position, server, &world)
{
player.open_handled_screen(factory.as_ref(), Some(position));
}
return Ok(());
}