From c61572d776afc0a5826f48c3bb021fe2661d7f0d Mon Sep 17 00:00:00 2001 From: Rafael <60099368+HttpRafa@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:56:34 +0200 Subject: [PATCH] feat: Implement ChiseledBookshelf (#1003) * feat: Add basic ChiseledBookshelf block * feat: We now know what slot the player pressed * feat: Add block entity * feat: Implement logic * fix: Make clippy happy * feat: Refactor item interact system to be more like vanilla * feat: Add ActionResult to non item function * fix: Some cleanup * fix: Make rust-fmt happy * feat: Remove useless use_with_item functions * fix: Broken block placement * fix: Crash * fix: Make clippy happy * fix: Remove unused function * fix: Noteblocks can be placed on eachother without sneaking * feat: Add check for creative --------- Co-authored-by: Alexander Medvedev --- .../src/player/player_inventory.rs | 9 + pumpkin-util/src/math/vector2.rs | 8 +- .../src/block/entities/chiseled_bookshelf.rs | 167 ++++++++++++ pumpkin-world/src/block/entities/mod.rs | 9 +- pumpkin-world/src/item/mod.rs | 10 + pumpkin/src/block/blocks/barrel.rs | 15 +- pumpkin/src/block/blocks/bed.rs | 17 +- .../src/block/blocks/chiseled_bookshelf.rs | 252 ++++++++++++++++++ pumpkin/src/block/blocks/composter.rs | 4 +- pumpkin/src/block/blocks/crafting_table.rs | 11 +- pumpkin/src/block/blocks/doors.rs | 11 +- pumpkin/src/block/blocks/fence_gates.rs | 8 +- pumpkin/src/block/blocks/flower_pots.rs | 20 +- pumpkin/src/block/blocks/jukebox.rs | 12 +- pumpkin/src/block/blocks/mod.rs | 1 + pumpkin/src/block/blocks/note.rs | 6 +- pumpkin/src/block/blocks/redstone/buttons.rs | 8 +- .../src/block/blocks/redstone/comparator.rs | 12 +- pumpkin/src/block/blocks/redstone/lever.rs | 9 +- .../block/blocks/redstone/redstone_wire.rs | 12 +- pumpkin/src/block/blocks/redstone/repeater.rs | 11 +- pumpkin/src/block/blocks/trapdoor.rs | 12 +- pumpkin/src/block/mod.rs | 2 + pumpkin/src/block/pumpkin_block.rs | 14 +- pumpkin/src/block/registry.rs | 27 +- pumpkin/src/net/java/play.rs | 96 +++++-- 26 files changed, 602 insertions(+), 161 deletions(-) create mode 100644 pumpkin-world/src/block/entities/chiseled_bookshelf.rs create mode 100644 pumpkin/src/block/blocks/chiseled_bookshelf.rs diff --git a/pumpkin-inventory/src/player/player_inventory.rs b/pumpkin-inventory/src/player/player_inventory.rs index 9efdb6570..cc3332082 100644 --- a/pumpkin-inventory/src/player/player_inventory.rs +++ b/pumpkin-inventory/src/player/player_inventory.rs @@ -44,6 +44,15 @@ impl PlayerInventory { .clone() } + /// getOffHandStack in source + pub async fn off_hand_item(&self) -> Arc> { + let slot = self + .equipment_slots + .get(&PlayerInventory::OFF_HAND_SLOT) + .unwrap(); + self.entity_equipment.lock().await.get(slot) + } + pub async fn swap_item(&self) -> (ItemStack, ItemStack) { let slot = self .equipment_slots diff --git a/pumpkin-util/src/math/vector2.rs b/pumpkin-util/src/math/vector2.rs index 74b789bf9..feb9e438e 100644 --- a/pumpkin-util/src/math/vector2.rs +++ b/pumpkin-util/src/math/vector2.rs @@ -12,8 +12,8 @@ pub struct Vector2 { } impl Vector2 { - pub const fn new(x: T, z: T) -> Self { - Vector2 { x, y: z } + pub const fn new(x: T, y: T) -> Self { + Vector2 { x, y } } pub fn length_squared(&self) -> T { @@ -34,10 +34,10 @@ impl Vector2 { } } - pub fn multiply(self, x: T, z: T) -> Self { + pub fn multiply(self, x: T, y: T) -> Self { Self { x: self.x * x, - y: self.y * z, + y: self.y * y, } } } diff --git a/pumpkin-world/src/block/entities/chiseled_bookshelf.rs b/pumpkin-world/src/block/entities/chiseled_bookshelf.rs new file mode 100644 index 000000000..4d02ac595 --- /dev/null +++ b/pumpkin-world/src/block/entities/chiseled_bookshelf.rs @@ -0,0 +1,167 @@ +use std::{ + array::from_fn, + sync::{ + Arc, + atomic::{AtomicBool, AtomicI8, Ordering}, + }, +}; + +use async_trait::async_trait; +use log::warn; +use pumpkin_data::block_properties::{BlockProperties, ChiseledBookshelfLikeProperties}; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::math::position::BlockPos; +use tokio::sync::Mutex; + +use crate::{ + block::entities::BlockEntity, + inventory::{Clearable, Inventory, split_stack}, + item::ItemStack, + world::{BlockFlags, SimpleWorld}, +}; + +#[derive(Debug)] +pub struct ChiseledBookshelfBlockEntity { + pub position: BlockPos, + pub items: [Arc>; 6], + pub last_interacted_slot: AtomicI8, + pub dirty: AtomicBool, +} + +const LAST_INTERACTED_SLOT: &str = "last_interacted_slot"; + +#[async_trait] +impl BlockEntity for ChiseledBookshelfBlockEntity { + fn resource_location(&self) -> &'static str { + Self::ID + } + + fn get_position(&self) -> BlockPos { + self.position + } + + fn from_nbt(nbt: &NbtCompound, position: BlockPos) -> Self + where + Self: Sized, + { + let chiseled_bookshelf = Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY))), + last_interacted_slot: AtomicI8::new( + nbt.get_int(LAST_INTERACTED_SLOT).unwrap_or(-1) as i8 + ), + dirty: AtomicBool::new(false), + }; + + chiseled_bookshelf.read_data(nbt, &chiseled_bookshelf.items); + + chiseled_bookshelf + } + + async fn write_nbt(&self, nbt: &mut NbtCompound) { + self.write_data(nbt, &self.items, true).await; + nbt.put_int( + LAST_INTERACTED_SLOT, + self.last_interacted_slot.load(Ordering::Relaxed).into(), + ); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl ChiseledBookshelfBlockEntity { + pub const ID: &'static str = "minecraft:chiseled_bookshelf"; + + pub fn new(position: BlockPos) -> Self { + Self { + position, + items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY))), + last_interacted_slot: AtomicI8::new(-1), + dirty: AtomicBool::new(false), + } + } + + pub async fn update_state( + &self, + mut properties: ChiseledBookshelfLikeProperties, + world: Arc, + slot: i8, + ) { + if slot >= 0 && slot < self.items.len() as i8 { + self.last_interacted_slot.store(slot, Ordering::Relaxed); + + let block = world.get_block(&self.position).await; + + properties.slot_0_occupied = !self.items[0].lock().await.is_empty(); + properties.slot_1_occupied = !self.items[1].lock().await.is_empty(); + properties.slot_2_occupied = !self.items[2].lock().await.is_empty(); + properties.slot_3_occupied = !self.items[3].lock().await.is_empty(); + properties.slot_4_occupied = !self.items[4].lock().await.is_empty(); + properties.slot_5_occupied = !self.items[5].lock().await.is_empty(); + + world + .set_block_state( + &self.position, + properties.to_state_id(block), + BlockFlags::NOTIFY_ALL, + ) + .await; + } else { + warn!( + "Invalid interacted slot: {} for chiseled bookshelf at position {:?}", + slot, self.position + ); + } + } +} + +#[async_trait] +impl Inventory for ChiseledBookshelfBlockEntity { + fn size(&self) -> usize { + self.items.len() + } + + async fn is_empty(&self) -> bool { + for slot in self.items.iter() { + if !slot.lock().await.is_empty() { + return false; + } + } + + true + } + + async fn get_stack(&self, slot: usize) -> Arc> { + self.items[slot].clone() + } + + async fn remove_stack(&self, slot: usize) -> ItemStack { + let mut removed = ItemStack::EMPTY; + let mut guard = self.items[slot].lock().await; + std::mem::swap(&mut removed, &mut *guard); + removed + } + + async fn remove_stack_specific(&self, slot: usize, amount: u8) -> ItemStack { + split_stack(&self.items, slot, amount).await + } + + async fn set_stack(&self, slot: usize, stack: ItemStack) { + *self.items[slot].lock().await = stack; + } + + fn mark_dirty(&self) { + self.dirty.store(true, Ordering::Relaxed); + } +} + +#[async_trait] +impl Clearable for ChiseledBookshelfBlockEntity { + async fn clear(&self) { + for slot in self.items.iter() { + *slot.lock().await = ItemStack::EMPTY; + } + } +} diff --git a/pumpkin-world/src/block/entities/mod.rs b/pumpkin-world/src/block/entities/mod.rs index c9526a316..41d1fb93b 100644 --- a/pumpkin-world/src/block/entities/mod.rs +++ b/pumpkin-world/src/block/entities/mod.rs @@ -12,11 +12,15 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::math::position::BlockPos; use sign::SignBlockEntity; -use crate::{inventory::Inventory, world::SimpleWorld}; +use crate::{ + block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity, inventory::Inventory, + world::SimpleWorld, +}; pub mod barrel; pub mod bed; pub mod chest; +pub mod chiseled_bookshelf; pub mod command_block; pub mod comparator; pub mod end_portal; @@ -86,6 +90,9 @@ pub fn block_entity_from_nbt(nbt: &NbtCompound) -> Option> EndPortalBlockEntity::ID => Some(Arc::new( block_entity_from_generic::(nbt), )), + ChiseledBookshelfBlockEntity::ID => Some(Arc::new(block_entity_from_generic::< + ChiseledBookshelfBlockEntity, + >(nbt))), _ => None, } } diff --git a/pumpkin-world/src/item/mod.rs b/pumpkin-world/src/item/mod.rs index c98801113..a9232c889 100644 --- a/pumpkin-world/src/item/mod.rs +++ b/pumpkin-world/src/item/mod.rs @@ -2,6 +2,7 @@ use pumpkin_data::item::Item; use pumpkin_data::recipes::RecipeResultStruct; use pumpkin_data::tag::{RegistryKey, get_tag_values}; use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::GameMode; use std::hash::Hash; mod categories; @@ -73,6 +74,15 @@ impl ItemStack { stack } + pub fn split_unless_creative(&mut self, gamemode: GameMode, amount: u8) -> Self { + let min = amount.min(self.item_count); + let stack = self.copy_with_count(min); + if gamemode != GameMode::Creative { + self.decrement(min); + } + stack + } + pub fn copy_with_count(&self, count: u8) -> Self { let mut stack = *self; stack.item_count = count; diff --git a/pumpkin/src/block/blocks/barrel.rs b/pumpkin/src/block/blocks/barrel.rs index e33a118b5..be4d7207a 100644 --- a/pumpkin/src/block/blocks/barrel.rs +++ b/pumpkin/src/block/blocks/barrel.rs @@ -10,7 +10,7 @@ use pumpkin_world::block::entities::barrel::BarrelBlockEntity; use pumpkin_world::inventory::Inventory; use tokio::sync::Mutex; -use crate::block::pumpkin_block::{OnStateReplacedArgs, PlacedArgs, UseWithItemArgs}; +use crate::block::pumpkin_block::{OnStateReplacedArgs, PlacedArgs}; use crate::block::{ pumpkin_block::{NormalUseArgs, PumpkinBlock}, registry::BlockActionResult, @@ -44,7 +44,7 @@ pub struct BarrelBlock; #[async_trait] impl PumpkinBlock for BarrelBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { if let Some(block_entity) = args.world.get_block_entity(args.position).await { if let Some(inventory) = block_entity.1.get_inventory() { args.player @@ -52,17 +52,8 @@ impl PumpkinBlock for BarrelBlock { .await; } } - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - if let Some(block_entity) = args.world.get_block_entity(args.position).await { - if let Some(inventory) = block_entity.1.get_inventory() { - args.player - .open_handled_screen(&BarrelScreenFactory(inventory)) - .await; - } - } - BlockActionResult::Consume + BlockActionResult::Success } async fn placed(&self, args: PlacedArgs<'_>) { diff --git a/pumpkin/src/block/blocks/bed.rs b/pumpkin/src/block/blocks/bed.rs index 2288403eb..c26ee3941 100644 --- a/pumpkin/src/block/blocks/bed.rs +++ b/pumpkin/src/block/blocks/bed.rs @@ -17,6 +17,7 @@ use pumpkin_world::world::BlockFlags; use crate::block::pumpkin_block::{ BlockMetadata, BrokenArgs, CanPlaceAtArgs, NormalUseArgs, OnPlaceArgs, PlacedArgs, PumpkinBlock, }; +use crate::block::registry::BlockActionResult; use crate::entity::{Entity, EntityBase}; use crate::world::World; @@ -105,7 +106,7 @@ impl PumpkinBlock for BedBlock { } #[allow(clippy::too_many_lines)] - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let state_id = args.world.get_block_state_id(args.position).await; let bed_props = BedProperties::from_state_id(state_id, args.block); @@ -135,7 +136,7 @@ impl PumpkinBlock for BedBlock { .explode(args.server, bed_head_pos.to_centered_f64(), 5.0) .await; - return; + return BlockActionResult::Success; } // Make sure the bed is not obstructed @@ -156,7 +157,7 @@ impl PumpkinBlock for BedBlock { true, ) .await; - return; + return BlockActionResult::Success; } // Make sure the bed is not occupied @@ -169,7 +170,7 @@ impl PumpkinBlock for BedBlock { true, ) .await; - return; + return BlockActionResult::Success; } // Make sure player is close enough @@ -188,7 +189,7 @@ impl PumpkinBlock for BedBlock { true, ) .await; - return; + return BlockActionResult::Success; } // Set respawn point @@ -214,7 +215,7 @@ impl PumpkinBlock for BedBlock { true, ) .await; - return; + return BlockActionResult::Success; } // Make sure there are no monsters nearby @@ -233,12 +234,14 @@ impl PumpkinBlock for BedBlock { true, ) .await; - return; + return BlockActionResult::Continue; } } args.player.sleep(bed_head_pos).await; Self::set_occupied(true, args.world, args.block, args.position, state_id).await; + + BlockActionResult::Success } } diff --git a/pumpkin/src/block/blocks/chiseled_bookshelf.rs b/pumpkin/src/block/blocks/chiseled_bookshelf.rs new file mode 100644 index 000000000..336d467f5 --- /dev/null +++ b/pumpkin/src/block/blocks/chiseled_bookshelf.rs @@ -0,0 +1,252 @@ +use std::sync::{Arc, atomic::Ordering}; + +use async_trait::async_trait; +use pumpkin_data::{ + block_properties::{BlockProperties, ChiseledBookshelfLikeProperties, HorizontalFacing}, + item::Item, + sound::{Sound, SoundCategory}, + tag::Tagable, +}; +use pumpkin_inventory::screen_handler::InventoryPlayer; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; +use pumpkin_world::{ + BlockStateId, block::entities::chiseled_bookshelf::ChiseledBookshelfBlockEntity, + inventory::Inventory, item::ItemStack, +}; +use tokio::sync::Mutex; + +use crate::{ + block::{ + pumpkin_block::{ + BlockHitResult, GetComparatorOutputArgs, NormalUseArgs, OnPlaceArgs, + OnStateReplacedArgs, PlacedArgs, PumpkinBlock, UseWithItemArgs, + }, + registry::BlockActionResult, + }, + entity::{EntityBase, player::Player}, + world::World, +}; + +#[pumpkin_block("minecraft:chiseled_bookshelf")] +pub struct ChiseledBookshelfBlock; + +#[async_trait] +impl PumpkinBlock for ChiseledBookshelfBlock { + async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId { + let mut properties = ChiseledBookshelfLikeProperties::default(args.block); + + // Face in the opposite direction the player is facing + properties.facing = args.player.get_entity().get_horizontal_facing().opposite(); + + properties.to_state_id(args.block) + } + + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { + let state = args.world.get_block_state(args.position).await; + let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block); + + if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) { + if Self::is_slot_used(properties, slot) { + if let Some((_, block_entity)) = args.world.get_block_entity(args.position).await { + if let Some(block_entity) = block_entity + .as_any() + .downcast_ref::() + { + Self::try_remove_book( + args.world, + args.player, + args.position, + block_entity, + properties, + slot, + ) + .await; + return BlockActionResult::Success; + } + } + } else { + return BlockActionResult::Consume; + } + } + BlockActionResult::Continue + } + + async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + let state = args.world.get_block_state(args.position).await; + let properties = ChiseledBookshelfLikeProperties::from_state_id(state.id, args.block); + + if !args + .item_stack + .lock() + .await + .get_item() + .is_tagged_with("minecraft:bookshelf_books") + .unwrap_or(false) + { + return BlockActionResult::PassToDefault; + } + if let Some(slot) = Self::get_slot_for_hit(args.hit, properties.facing) { + if Self::is_slot_used(properties, slot) { + return BlockActionResult::PassToDefault; + } else if let Some((_, block_entity)) = args.world.get_block_entity(args.position).await + { + if let Some(block_entity) = block_entity + .as_any() + .downcast_ref::() + { + Self::try_add_book( + args.world, + args.player, + args.position, + block_entity, + properties, + slot, + args.item_stack, + ) + .await; + return BlockActionResult::Success; + } + } + } + + BlockActionResult::Continue + } + + async fn placed(&self, args: PlacedArgs<'_>) { + let block_entity = ChiseledBookshelfBlockEntity::new(*args.position); + args.world.add_block_entity(Arc::new(block_entity)).await; + } + + async fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) { + args.world.remove_block_entity(args.position).await; + } + + async fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option { + if let Some((_, block_entity)) = args.world.get_block_entity(args.position).await { + if let Some(block_entity) = block_entity + .as_any() + .downcast_ref::() + { + return Some((block_entity.last_interacted_slot.load(Ordering::Relaxed) + 1) as u8); + } + } + None + } +} + +impl ChiseledBookshelfBlock { + async fn try_add_book( + world: &Arc, + player: &Player, + position: &BlockPos, + entity: &ChiseledBookshelfBlockEntity, + properties: ChiseledBookshelfLikeProperties, + slot: i8, + item: &Arc>, + ) { + // TODO: Increment used stats for chiseled bookshelf on the player + + let mut item = item.lock().await; + let sound = if *item.get_item() == Item::ENCHANTED_BOOK { + Sound::BlockChiseledBookshelfPickupEnchanted + } else { + Sound::BlockChiseledBookshelfPickup + }; + + entity + .set_stack( + slot as usize, + item.split_unless_creative(player.gamemode.load(), 1), + ) + .await; + entity.update_state(properties, world.clone(), slot).await; + + world + .play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64()) + .await; + } + + async fn try_remove_book( + world: &Arc, + player: &Player, + position: &BlockPos, + entity: &ChiseledBookshelfBlockEntity, + properties: ChiseledBookshelfLikeProperties, + slot: i8, + ) { + let mut stack = entity.remove_stack_specific(slot as usize, 1).await; + + let sound = if *stack.get_item() == Item::ENCHANTED_BOOK { + Sound::BlockChiseledBookshelfPickupEnchanted + } else { + Sound::BlockChiseledBookshelfPickup + }; + + if !player + .get_inventory() + .insert_stack_anywhere(&mut stack) + .await + { + // Drop the item on the ground if the player cannot hold it because of a full inventory + player.drop_item(stack).await; + } + entity.update_state(properties, world.clone(), slot).await; + + world + .play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64()) + .await; + } + + fn get_slot_for_hit(hit: &BlockHitResult<'_>, facing: HorizontalFacing) -> Option { + Self::get_hit_pos(hit, facing).map(|position| { + let i = i8::from(position.y < 0.5); + let j = Self::get_column(position.x); + j + i * 3 + }) + } + + fn get_hit_pos(hit: &BlockHitResult<'_>, facing: HorizontalFacing) -> Option> { + // If the direction is not horizontal, we cannot hit a slot + let direction = hit.side.to_horizontal_facing()?; + + // If the facing direction does not match the block's facing, we cannot hit a slot + if facing != direction { + return None; + } + + match direction { + HorizontalFacing::North => Some(Vector2::new(1.0 - hit.cursor_pos.x, hit.cursor_pos.y)), + HorizontalFacing::South => Some(Vector2::new(hit.cursor_pos.x, hit.cursor_pos.y)), + HorizontalFacing::West => Some(Vector2::new(hit.cursor_pos.z, hit.cursor_pos.y)), + HorizontalFacing::East => Some(Vector2::new(1.0 - hit.cursor_pos.z, hit.cursor_pos.y)), + } + } + + // Magic numbers for the slots + // These are based on the vanilla chiseled bookshelf implementation + const OFFSET_SLOT_0: f32 = 0.375; + const OFFSET_SLOT_1: f32 = 0.6875; + + fn get_column(x: f32) -> i8 { + if x < Self::OFFSET_SLOT_0 { + 0 + } else if x < Self::OFFSET_SLOT_1 { + 1 + } else { + 2 + } + } + + fn is_slot_used(properties: ChiseledBookshelfLikeProperties, slot: i8) -> bool { + match slot { + 0 => properties.slot_0_occupied, + 1 => properties.slot_1_occupied, + 2 => properties.slot_2_occupied, + 3 => properties.slot_3_occupied, + 4 => properties.slot_4_occupied, + 5 => properties.slot_5_occupied, + _ => false, + } + } +} diff --git a/pumpkin/src/block/blocks/composter.rs b/pumpkin/src/block/blocks/composter.rs index bc95ef5bd..fceaf72d5 100644 --- a/pumpkin/src/block/blocks/composter.rs +++ b/pumpkin/src/block/blocks/composter.rs @@ -32,13 +32,15 @@ pub struct ComposterBlock; #[async_trait] impl PumpkinBlock for ComposterBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let state_id = args.world.get_block_state_id(args.position).await; let props = ComposterLikeProperties::from_state_id(state_id, args.block); if props.get_level() == 8 { self.clear_composter(args.world, args.position, state_id, args.block) .await; } + + BlockActionResult::Continue } async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 4065dbbd5..e55d6af65 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -1,4 +1,4 @@ -use crate::block::pumpkin_block::{NormalUseArgs, PumpkinBlock, UseWithItemArgs}; +use crate::block::pumpkin_block::{NormalUseArgs, PumpkinBlock}; use crate::block::registry::BlockActionResult; use async_trait::async_trait; use pumpkin_inventory::crafting::crafting_screen_handler::CraftingTableScreenHandler; @@ -14,17 +14,12 @@ pub struct CraftingTableBlock; #[async_trait] impl PumpkinBlock for CraftingTableBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { args.player .open_handled_screen(&CraftingTableScreenFactory) .await; - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - args.player - .open_handled_screen(&CraftingTableScreenFactory) - .await; - BlockActionResult::Consume + BlockActionResult::Success } } diff --git a/pumpkin/src/block/blocks/doors.rs b/pumpkin/src/block/blocks/doors.rs index 288548bbb..92226d83e 100644 --- a/pumpkin/src/block/blocks/doors.rs +++ b/pumpkin/src/block/blocks/doors.rs @@ -25,7 +25,6 @@ use crate::block::pumpkin_block::NormalUseArgs; use crate::block::pumpkin_block::OnNeighborUpdateArgs; use crate::block::pumpkin_block::OnPlaceArgs; use crate::block::pumpkin_block::PlacedArgs; -use crate::block::pumpkin_block::UseWithItemArgs; use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::block::registry::BlockActionResult; use crate::entity::player::Player; @@ -209,20 +208,14 @@ impl PumpkinBlock for DoorBlock { .await; } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { if !can_open_door(args.block) { return BlockActionResult::Continue; } toggle_door(args.player, args.world, args.position).await; - BlockActionResult::Consume - } - - async fn normal_use(&self, args: NormalUseArgs<'_>) { - if can_open_door(args.block) { - toggle_door(args.player, args.world, args.position).await; - } + BlockActionResult::Success } async fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) { diff --git a/pumpkin/src/block/blocks/fence_gates.rs b/pumpkin/src/block/blocks/fence_gates.rs index 5c9411017..1fe19739e 100644 --- a/pumpkin/src/block/blocks/fence_gates.rs +++ b/pumpkin/src/block/blocks/fence_gates.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use crate::block::pumpkin_block::GetStateForNeighborUpdateArgs; use crate::block::pumpkin_block::NormalUseArgs; use crate::block::pumpkin_block::OnPlaceArgs; -use crate::block::pumpkin_block::UseWithItemArgs; use crate::entity::player::Player; use async_trait::async_trait; use pumpkin_data::block_properties::BlockProperties; @@ -80,13 +79,10 @@ impl PumpkinBlock for FenceGateBlock { fence_props.to_state_id(args.block) } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { toggle_fence_gate(args.world, args.position, args.player).await; - BlockActionResult::Consume - } - async fn normal_use(&self, args: NormalUseArgs<'_>) { - toggle_fence_gate(args.world, args.position, args.player).await; + BlockActionResult::Success } } diff --git a/pumpkin/src/block/blocks/flower_pots.rs b/pumpkin/src/block/blocks/flower_pots.rs index 9b9edc6c4..939049f35 100644 --- a/pumpkin/src/block/blocks/flower_pots.rs +++ b/pumpkin/src/block/blocks/flower_pots.rs @@ -1,6 +1,4 @@ -use crate::block::pumpkin_block::{ - BlockMetadata, NormalUseArgs, PumpkinBlock, RandomTickArgs, UseWithItemArgs, -}; +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock, RandomTickArgs, UseWithItemArgs}; use crate::block::registry::BlockActionResult; use async_trait::async_trait; use pumpkin_data::Block; @@ -23,18 +21,6 @@ impl BlockMetadata for FlowerPotBlock { #[async_trait] impl PumpkinBlock for FlowerPotBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { - if !args.block.eq(&Block::FLOWER_POT) { - args.world - .set_block_state( - args.position, - Block::FLOWER_POT.default_state.id, - BlockFlags::NOTIFY_ALL, - ) - .await; - } - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { let item = args.item_stack.lock().await.item; //Place the flower inside the pot @@ -48,7 +34,7 @@ impl PumpkinBlock for FlowerPotBlock { ) .await; } - return BlockActionResult::Consume; + return BlockActionResult::Success; } //if the player have an item that can be potted in his hand, nothing happens @@ -64,7 +50,7 @@ impl PumpkinBlock for FlowerPotBlock { BlockFlags::NOTIFY_ALL, ) .await; - BlockActionResult::Consume + BlockActionResult::Success } async fn random_tick(&self, args: RandomTickArgs<'_>) { diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index ddc808aa3..89f58362a 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::block::pumpkin_block::{BrokenArgs, NormalUseArgs, PumpkinBlock, UseWithItemArgs}; +use crate::block::pumpkin_block::{BrokenArgs, PumpkinBlock, UseWithItemArgs}; use crate::block::registry::BlockActionResult; use crate::world::World; use async_trait::async_trait; @@ -46,19 +46,13 @@ impl JukeboxBlock { #[async_trait] impl PumpkinBlock for JukeboxBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { - // For now just stop the music at this position - let world = &args.player.living_entity.entity.world.read().await; - self.stop_music(args.block, args.position, world).await; - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { let world = &args.player.living_entity.entity.world.read().await; // if the jukebox already has a record, stop playing if self.has_record(args.block, args.position, world).await { self.stop_music(args.block, args.position, world).await; - return BlockActionResult::Consume; + return BlockActionResult::Success; } let Some(jukebox_playable) = &args @@ -93,7 +87,7 @@ impl PumpkinBlock for JukeboxBlock { ) .await; - BlockActionResult::Consume + BlockActionResult::Success } async fn broken(&self, args: BrokenArgs<'_>) { diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 772466cc3..8fe0a8e37 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -6,6 +6,7 @@ pub mod cactus; pub mod campfire; pub mod carpet; pub mod chest; +pub mod chiseled_bookshelf; pub mod command; pub mod composter; pub mod crafting_table; diff --git a/pumpkin/src/block/blocks/note.rs b/pumpkin/src/block/blocks/note.rs index 4a68b9654..4929ba486 100644 --- a/pumpkin/src/block/blocks/note.rs +++ b/pumpkin/src/block/blocks/note.rs @@ -83,7 +83,7 @@ impl PumpkinBlock for NoteBlock { } } - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let block_state = args.world.get_block_state(args.position).await; let mut note_props = NoteBlockLikeProperties::from_state_id(block_state.id, args.block); let next_index = note_props.note.to_index() + 1; @@ -101,11 +101,13 @@ impl PumpkinBlock for NoteBlock { ) .await; Self::play_note(¬e_props, args.world, args.position).await; + + BlockActionResult::Success } async fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult { // TODO - BlockActionResult::Continue + BlockActionResult::PassToDefault } async fn on_synced_block_event(&self, args: OnSyncedBlockEventArgs<'_>) -> bool { diff --git a/pumpkin/src/block/blocks/redstone/buttons.rs b/pumpkin/src/block/blocks/redstone/buttons.rs index f0b5a4fea..e0f2e2b96 100644 --- a/pumpkin/src/block/blocks/redstone/buttons.rs +++ b/pumpkin/src/block/blocks/redstone/buttons.rs @@ -24,7 +24,6 @@ use crate::block::pumpkin_block::GetStateForNeighborUpdateArgs; use crate::block::pumpkin_block::OnPlaceArgs; use crate::block::pumpkin_block::OnScheduledTickArgs; use crate::block::pumpkin_block::OnStateReplacedArgs; -use crate::block::pumpkin_block::UseWithItemArgs; use crate::block::pumpkin_block::{BlockMetadata, NormalUseArgs, PumpkinBlock}; use crate::block::registry::BlockActionResult; use crate::world::World; @@ -68,13 +67,10 @@ impl BlockMetadata for ButtonBlock { #[async_trait] impl PumpkinBlock for ButtonBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { click_button(args.world, args.position).await; - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - click_button(args.world, args.position).await; - BlockActionResult::Consume + BlockActionResult::Success } async fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) { diff --git a/pumpkin/src/block/blocks/redstone/comparator.rs b/pumpkin/src/block/blocks/redstone/comparator.rs index f4f8ca52a..2b9206257 100644 --- a/pumpkin/src/block/blocks/redstone/comparator.rs +++ b/pumpkin/src/block/blocks/redstone/comparator.rs @@ -24,7 +24,7 @@ use crate::{ BrokenArgs, CanPlaceAtArgs, EmitsRedstonePowerArgs, GetComparatorOutputArgs, GetRedstonePowerArgs, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs, - PlacedArgs, PlayerPlacedArgs, PumpkinBlock, UseWithItemArgs, + PlacedArgs, PlayerPlacedArgs, PumpkinBlock, }, registry::BlockActionResult, }, @@ -42,19 +42,13 @@ impl PumpkinBlock for ComparatorBlock { RedstoneGateBlock::on_place(self, args.player, args.block).await } - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let state = args.world.get_block_state(args.position).await; let props = ComparatorLikeProperties::from_state_id(state.id, args.block); self.on_use(props, args.world, *args.position, args.block) .await; - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - let state = args.world.get_block_state(args.position).await; - let props = ComparatorLikeProperties::from_state_id(state.id, args.block); - self.on_use(props, args.world, *args.position, args.block) - .await; - BlockActionResult::Consume + BlockActionResult::Success } async fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool { diff --git a/pumpkin/src/block/blocks/redstone/lever.rs b/pumpkin/src/block/blocks/redstone/lever.rs index 637459eb5..7bfd6a42e 100644 --- a/pumpkin/src/block/blocks/redstone/lever.rs +++ b/pumpkin/src/block/blocks/redstone/lever.rs @@ -4,7 +4,7 @@ use crate::block::{ blocks::abstruct_wall_mounting::WallMountedBlock, pumpkin_block::{ CanPlaceAtArgs, EmitsRedstonePowerArgs, GetRedstonePowerArgs, - GetStateForNeighborUpdateArgs, OnPlaceArgs, OnStateReplacedArgs, UseWithItemArgs, + GetStateForNeighborUpdateArgs, OnPlaceArgs, OnStateReplacedArgs, }, }; use async_trait::async_trait; @@ -45,13 +45,10 @@ pub struct LeverBlock; #[async_trait] impl PumpkinBlock for LeverBlock { - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { toggle_lever(args.world, args.position).await; - BlockActionResult::Consume - } - async fn normal_use(&self, args: NormalUseArgs<'_>) { - toggle_lever(args.world, args.position).await; + BlockActionResult::Success } async fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool { diff --git a/pumpkin/src/block/blocks/redstone/redstone_wire.rs b/pumpkin/src/block/blocks/redstone/redstone_wire.rs index e87aba53f..7af12bb60 100644 --- a/pumpkin/src/block/blocks/redstone/redstone_wire.rs +++ b/pumpkin/src/block/blocks/redstone/redstone_wire.rs @@ -14,7 +14,7 @@ use pumpkin_world::world::{BlockAccessor, BlockFlags}; use crate::block::pumpkin_block::{ BrokenArgs, CanPlaceAtArgs, GetRedstonePowerArgs, GetStateForNeighborUpdateArgs, - OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs, PrepareArgs, UseWithItemArgs, + OnNeighborUpdateArgs, OnPlaceArgs, PlacedArgs, PrepareArgs, }; use crate::block::registry::BlockActionResult; use crate::{ @@ -134,17 +134,11 @@ impl PumpkinBlock for RedstoneWireBlock { } } - async fn normal_use(&self, args: NormalUseArgs<'_>) { - let state = args.world.get_block_state(args.position).await; - let wire = RedstoneWireProperties::from_state_id(state.id, args.block); - on_use(wire, args.world, args.position).await; - } - - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let state = args.world.get_block_state(args.position).await; let wire = RedstoneWireProperties::from_state_id(state.id, args.block); if on_use(wire, args.world, args.position).await { - BlockActionResult::Consume + BlockActionResult::Success } else { BlockActionResult::Continue } diff --git a/pumpkin/src/block/blocks/redstone/repeater.rs b/pumpkin/src/block/blocks/redstone/repeater.rs index 12510bf31..f209e50e5 100644 --- a/pumpkin/src/block/blocks/redstone/repeater.rs +++ b/pumpkin/src/block/blocks/redstone/repeater.rs @@ -18,7 +18,6 @@ use crate::{ CanPlaceAtArgs, EmitsRedstonePowerArgs, GetRedstonePowerArgs, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs, OnStateReplacedArgs, PlacedArgs, PlayerPlacedArgs, PumpkinBlock, - UseWithItemArgs, }, registry::BlockActionResult, }, @@ -115,19 +114,13 @@ impl PumpkinBlock for RepeaterBlock { } } - async fn normal_use(&self, args: NormalUseArgs<'_>) { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { let state = args.world.get_block_state(args.position).await; let props = RepeaterProperties::from_state_id(state.id, args.block); self.on_use(props, args.world, *args.position, args.block) .await; - } - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - let state = args.world.get_block_state(args.position).await; - let props = RepeaterProperties::from_state_id(state.id, args.block); - self.on_use(props, args.world, *args.position, args.block) - .await; - BlockActionResult::Consume + BlockActionResult::Success } async fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 { diff --git a/pumpkin/src/block/blocks/trapdoor.rs b/pumpkin/src/block/blocks/trapdoor.rs index d61a74962..fa6c6ec54 100644 --- a/pumpkin/src/block/blocks/trapdoor.rs +++ b/pumpkin/src/block/blocks/trapdoor.rs @@ -1,6 +1,6 @@ use crate::block::blocks::redstone::block_receives_redstone_power; use crate::block::pumpkin_block::{ - BlockMetadata, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, PumpkinBlock, UseWithItemArgs, + BlockMetadata, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs, PumpkinBlock, }; use crate::block::registry::BlockActionResult; use crate::entity::player::Player; @@ -79,20 +79,14 @@ impl BlockMetadata for TrapDoorBlock { #[async_trait] impl PumpkinBlock for TrapDoorBlock { - async fn normal_use(&self, args: NormalUseArgs<'_>) { - if can_open_trapdoor(args.block) { - toggle_trapdoor(args.player, args.world, args.position).await; - } - } - - async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { + async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { if !can_open_trapdoor(args.block) { return BlockActionResult::Continue; } toggle_trapdoor(args.player, args.world, args.position).await; - BlockActionResult::Consume + BlockActionResult::Success } async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId { diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 296d3469d..ce8f0c6fc 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -76,6 +76,7 @@ use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro}; use pumpkin_world::BlockStateId; use crate::block::blocks::campfire::CampfireBlock; +use crate::block::blocks::chiseled_bookshelf::ChiseledBookshelfBlock; use crate::block::blocks::flower_pots::FlowerPotBlock; use crate::block::blocks::glazed_terracotta::GlazedTerracottaBlock; use crate::block::blocks::plant::roots::RootsBlock; @@ -153,6 +154,7 @@ pub fn default_registry() -> Arc { manager.register(EndPortalFrameBlock); manager.register(SeaPickleBlock); manager.register(SkullBlock); + manager.register(ChiseledBookshelfBlock); // Fire manager.register(SoulFireBlock); diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index 65c5a0d6d..d5835652e 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -7,6 +7,7 @@ use async_trait::async_trait; use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_protocol::java::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector3::Vector3; use pumpkin_world::BlockStateId; use pumpkin_world::item::ItemStack; use pumpkin_world::world::{BlockAccessor, BlockFlags}; @@ -28,10 +29,12 @@ pub trait BlockMetadata { #[async_trait] pub trait PumpkinBlock: Send + Sync { - async fn normal_use(&self, _args: NormalUseArgs<'_>) {} + async fn normal_use(&self, _args: NormalUseArgs<'_>) -> BlockActionResult { + BlockActionResult::Continue + } async fn use_with_item(&self, _args: UseWithItemArgs<'_>) -> BlockActionResult { - BlockActionResult::Continue + BlockActionResult::PassToDefault } async fn on_entity_collision(&self, _args: OnEntityCollisionArgs<'_>) {} @@ -113,6 +116,7 @@ pub struct NormalUseArgs<'a> { pub block: &'a Block, pub position: &'a BlockPos, pub player: &'a Player, + pub hit: &'a BlockHitResult<'a>, } pub struct UseWithItemArgs<'a> { @@ -121,9 +125,15 @@ pub struct UseWithItemArgs<'a> { pub block: &'a Block, pub position: &'a BlockPos, pub player: &'a Player, + pub hit: &'a BlockHitResult<'a>, pub item_stack: &'a Arc>, } +pub struct BlockHitResult<'a> { + pub side: &'a BlockDirection, + pub cursor_pos: &'a Vector3, +} + pub struct OnEntityCollisionArgs<'a> { pub server: &'a Server, pub world: &'a Arc, diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index 59ac2ee0c..f33ddbbdf 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -1,4 +1,6 @@ -use crate::block::pumpkin_block::{BlockMetadata, OnEntityCollisionArgs, PumpkinBlock}; +use crate::block::pumpkin_block::{ + BlockHitResult, BlockMetadata, OnEntityCollisionArgs, PumpkinBlock, +}; use crate::entity::EntityBase; use crate::entity::player::Player; use crate::server::Server; @@ -25,11 +27,18 @@ use super::pumpkin_block::{ }; use super::pumpkin_fluid::PumpkinFluid; +// ActionResult.java pub enum BlockActionResult { - /// Allow other actions to be executed - Continue, - /// Block other actions + /// Action was successful and we should swing the hand | Same as SUCCESS in vanilla + Success, + /// Block other actions from being executed and we should swing the hand | Same as CONSUME in vanilla Consume, + /// Block other actions from being executed | Same as FAIL in vanilla + Fail, + /// Allow other actions to be executed | Same as PASS in vanilla + Continue, + /// Use default action for the block | Same as `PASS_TO_DEFAULT_BLOCK_ACTION` in vanilla + PassToDefault, } #[derive(Default)] @@ -137,21 +146,24 @@ impl BlockRegistry { block: &Block, player: &Player, position: &BlockPos, + hit: &BlockHitResult<'_>, server: &Server, world: &Arc, - ) { + ) -> BlockActionResult { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block + return pumpkin_block .normal_use(NormalUseArgs { server, world, block, position, player, + hit, }) .await; } + BlockActionResult::Continue } pub async fn explode(&self, block: &Block, world: &Arc, position: &BlockPos) { @@ -167,11 +179,13 @@ impl BlockRegistry { } } + #[allow(clippy::too_many_arguments)] pub async fn use_with_item( &self, block: &Block, player: &Player, position: &BlockPos, + hit: &BlockHitResult<'_>, item_stack: &Arc>, server: &Server, world: &Arc, @@ -185,6 +199,7 @@ impl BlockRegistry { block, position, player, + hit, item_stack, }) .await; diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index 8e4289045..ff24bcc81 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -49,6 +49,7 @@ use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; use uuid::Uuid; +use crate::block::pumpkin_block::BlockHitResult; use crate::block::registry::BlockActionResult; use crate::block::{self, BlockIsReplacing}; use crate::command::CommandSender; @@ -1403,6 +1404,7 @@ impl JavaClientPlatform { .await; } + #[allow(clippy::too_many_lines)] pub async fn handle_use_item_on( &self, player: &Player, @@ -1415,6 +1417,8 @@ impl JavaClientPlatform { self.update_sequence(player, use_item_on.sequence.0); let position = use_item_on.position; + let cursor_pos = use_item_on.cursor_pos; + let mut should_try_decrement = false; if !player.can_interact_with_block_at(&position, 1.0) { @@ -1428,6 +1432,7 @@ impl JavaClientPlatform { let inventory = player.inventory(); let held_item = inventory.held_item(); + let off_hand_item = inventory.off_hand_item().await; let entity = &player.living_entity.entity; let world = &entity.world.read().await; @@ -1438,41 +1443,74 @@ impl JavaClientPlatform { .entity .sneaking .load(std::sync::atomic::Ordering::Relaxed); - if held_item.lock().await.is_empty() { - if !sneaking { - // Using block with empty hand - server - .block_registry - .on_use(block, player, &position, server, world) - .await; - } - return Ok(()); - } - if !sneaking { - let action_result = server + // Code based on the java class ServerPlayerInteractionManager + if !(sneaking + && (!held_item.lock().await.is_empty() || !off_hand_item.lock().await.is_empty())) + { + match match server .block_registry - .use_with_item(block, player, &position, &held_item, server, world) - .await; - match action_result { - BlockActionResult::Continue => {} - BlockActionResult::Consume => { + .use_with_item( + block, + player, + &position, + &BlockHitResult { + side: &face, + cursor_pos: &cursor_pos, + }, + &held_item, + server, + world, + ) + .await + { + BlockActionResult::PassToDefault => { + server + .block_registry + .on_use( + block, + player, + &position, + &BlockHitResult { + side: &face, + cursor_pos: &cursor_pos, + }, + server, + world, + ) + .await + } + BlockActionResult::Fail => BlockActionResult::Fail, + BlockActionResult::Consume => BlockActionResult::Consume, + BlockActionResult::Continue => BlockActionResult::Continue, + BlockActionResult::Success => BlockActionResult::Success, + } { + BlockActionResult::Fail => return Ok(()), + BlockActionResult::Success | BlockActionResult::Consume => { + /* TODO: Swing hand */ return Ok(()); } + BlockActionResult::Continue | BlockActionResult::PassToDefault => {} // Do nothing, } - server - .item_registry - .use_on_block( - held_item.lock().await.item, - player, - position, - face, - block, - server, - ) - .await; - self.update_sequence(player, use_item_on.sequence.0); } + if held_item.lock().await.is_empty() { + // If the hand is empty we stop here + return Ok(()); + } + + server + .item_registry + .use_on_block( + held_item.lock().await.item, + player, + position, + face, + block, + server, + ) + .await; + self.update_sequence(player, use_item_on.sequence.0); + // Check if the item is a block, because not every item can be placed :D if let Some(block) = get_block_by_item(held_item.lock().await.item.id) { should_try_decrement = self