mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
Add Pick block (Middle Mouse Click) (#418)
* Pick Item Changes * Changed some logging * Pick Item working for hotbar slots * Basic functionality, changes needed to adhere to expected minecraft behaviour * Clean up code * Added todo for pick from entity * Added item swapping * Swapping and cleanup * Fix for rebase * Remove unused function * Bugfix * remove todo --------- Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
@@ -116,6 +116,35 @@ impl PlayerInventory {
|
||||
&mut self.items[self.selected + 36 - 9]
|
||||
}
|
||||
|
||||
pub fn get_slot_with_item(&self, item_id: u16) -> Option<usize> {
|
||||
for slot in 9..=44 {
|
||||
match &self.items[slot - 9] {
|
||||
Some(item) if item.item_id == item_id => return Some(slot),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_pick_item_hotbar_slot(&self) -> usize {
|
||||
if self.items[self.selected + 36 - 9].is_none() {
|
||||
return self.selected;
|
||||
}
|
||||
|
||||
for slot in 0..9 {
|
||||
if self.items[slot + 36 - 9].is_none() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
self.selected
|
||||
}
|
||||
|
||||
pub fn get_empty_slot(&self) -> Option<usize> {
|
||||
(9..=44).find(|&slot| self.items[slot - 9].is_none())
|
||||
}
|
||||
|
||||
pub fn slots(&self) -> Vec<Option<&ItemStack>> {
|
||||
let mut slots = vec![self.crafting_output.as_ref()];
|
||||
slots.extend(self.crafting.iter().map(|c| c.as_ref()));
|
||||
|
||||
@@ -10,6 +10,7 @@ mod s_confirm_teleport;
|
||||
mod s_cookie_response;
|
||||
mod s_interact;
|
||||
mod s_keep_alive;
|
||||
mod s_pick_item;
|
||||
mod s_ping_request;
|
||||
mod s_player_abilities;
|
||||
mod s_player_action;
|
||||
@@ -37,6 +38,7 @@ pub use s_confirm_teleport::*;
|
||||
pub use s_cookie_response::*;
|
||||
pub use s_interact::*;
|
||||
pub use s_keep_alive::*;
|
||||
pub use s_pick_item::*;
|
||||
pub use s_ping_request::*;
|
||||
pub use s_player_abilities::*;
|
||||
pub use s_player_action::*;
|
||||
|
||||
17
pumpkin-protocol/src/server/play/s_pick_item.rs
Normal file
17
pumpkin-protocol/src/server/play/s_pick_item.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use pumpkin_core::math::position::WorldPosition;
|
||||
use pumpkin_macros::server_packet;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[server_packet("play:pick_item_from_block")]
|
||||
pub struct SPickItemFromBlock {
|
||||
pub pos: WorldPosition,
|
||||
pub include_data: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[server_packet("play:pick_item_from_entity")]
|
||||
pub struct SPickItemFromEntity {
|
||||
pub id: i32,
|
||||
pub include_data: bool,
|
||||
}
|
||||
@@ -37,9 +37,10 @@ use pumpkin_protocol::{
|
||||
},
|
||||
server::play::{
|
||||
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
|
||||
SCommandSuggestion, SConfirmTeleport, SInteract, SPlayerAbilities, SPlayerAction,
|
||||
SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation,
|
||||
SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn,
|
||||
SCommandSuggestion, SConfirmTeleport, SInteract, SPickItemFromBlock, SPlayerAbilities,
|
||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation,
|
||||
SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem,
|
||||
SUseItemOn,
|
||||
},
|
||||
RawPacket, ServerPacket, SoundCategory,
|
||||
};
|
||||
@@ -675,6 +676,7 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn handle_play_packet(
|
||||
self: &Arc<Self>,
|
||||
server: &Arc<Server>,
|
||||
@@ -726,6 +728,10 @@ impl Player {
|
||||
SSetPlayerGround::PACKET_ID => {
|
||||
self.handle_player_ground(&SSetPlayerGround::read(bytebuf)?);
|
||||
}
|
||||
SPickItemFromBlock::PACKET_ID => {
|
||||
self.handle_pick_item_from_block(SPickItemFromBlock::read(bytebuf)?)
|
||||
.await;
|
||||
}
|
||||
SPlayerAbilities::PACKET_ID => {
|
||||
self.handle_player_abilities(SPlayerAbilities::read(bytebuf)?)
|
||||
.await;
|
||||
|
||||
@@ -18,7 +18,10 @@ use pumpkin_core::{
|
||||
text::TextComponent,
|
||||
GameMode,
|
||||
};
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
use pumpkin_inventory::InventoryError;
|
||||
use pumpkin_protocol::client::play::{CSetContainerSlot, CSetHeldItem};
|
||||
use pumpkin_protocol::codec::slot::Slot;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse;
|
||||
use pumpkin_protocol::{
|
||||
@@ -32,13 +35,14 @@ use pumpkin_protocol::{
|
||||
},
|
||||
server::play::{
|
||||
Action, ActionType, SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay,
|
||||
SConfirmTeleport, SInteract, SPlayPingRequest, SPlayerAbilities, SPlayerAction,
|
||||
SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation,
|
||||
SSetCreativeSlot, SSetHeldItem, SSwingArm, SUseItemOn, Status,
|
||||
SConfirmTeleport, SInteract, SPickItemFromBlock, SPickItemFromEntity, SPlayPingRequest,
|
||||
SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation,
|
||||
SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm, SUseItemOn, Status,
|
||||
},
|
||||
};
|
||||
use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace};
|
||||
use pumpkin_world::item::item_registry::get_item_by_id;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use thiserror::Error;
|
||||
|
||||
fn modulus(a: f32, b: f32) -> f32 {
|
||||
@@ -310,6 +314,101 @@ impl Player {
|
||||
.store(ground.on_ground, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
async fn update_single_slot(
|
||||
&self,
|
||||
inventory: &mut tokio::sync::MutexGuard<'_, PlayerInventory>,
|
||||
slot: usize,
|
||||
slot_data: Slot,
|
||||
) {
|
||||
inventory.state_id += 1;
|
||||
let dest_packet = CSetContainerSlot::new(0, inventory.state_id as i32, slot, &slot_data);
|
||||
self.client.send_packet(&dest_packet).await;
|
||||
|
||||
if inventory
|
||||
.set_slot(slot, slot_data.to_item(), false)
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Pick item set slot error!");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_pick_item_from_block(&self, pick_item: SPickItemFromBlock) {
|
||||
if !self.can_interact_with_block_at(&pick_item.pos, 1.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(block) = self.world().get_block(pick_item.pos).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
if block.item_id == 0 {
|
||||
// Invalid block id (blocks such as tall seagrass)
|
||||
return;
|
||||
}
|
||||
|
||||
let mut inventory = self.inventory().lock().await;
|
||||
|
||||
let source_slot = inventory.get_slot_with_item(block.item_id);
|
||||
let mut dest_slot = inventory.get_pick_item_hotbar_slot();
|
||||
|
||||
let dest_slot_data = match inventory.get_slot(dest_slot + 36) {
|
||||
Ok(Some(stack)) => Slot::from(&*stack),
|
||||
_ => Slot::from(None),
|
||||
};
|
||||
|
||||
// Early return if no source slot and not in creative mode
|
||||
if source_slot.is_none() && self.gamemode.load() != GameMode::Creative {
|
||||
return;
|
||||
}
|
||||
|
||||
match source_slot {
|
||||
Some(slot_index) if (36..=44).contains(&slot_index) => {
|
||||
// Case where item is in hotbar
|
||||
dest_slot = slot_index - 36;
|
||||
}
|
||||
Some(slot_index) => {
|
||||
// Case where item is in inventory
|
||||
|
||||
// Update destination slot
|
||||
let source_slot_data = match inventory.get_slot(slot_index) {
|
||||
Ok(Some(stack)) => Slot::from(&*stack),
|
||||
_ => return,
|
||||
};
|
||||
self.update_single_slot(&mut inventory, dest_slot + 36, source_slot_data)
|
||||
.await;
|
||||
|
||||
// Update source slot
|
||||
self.update_single_slot(&mut inventory, slot_index, dest_slot_data)
|
||||
.await;
|
||||
}
|
||||
None if self.gamemode.load() == GameMode::Creative => {
|
||||
// Case where item is not present, if in creative mode create the item
|
||||
let item_stack = ItemStack::new(1, block.item_id);
|
||||
let slot_data = Slot::from(&item_stack);
|
||||
self.update_single_slot(&mut inventory, dest_slot + 36, slot_data)
|
||||
.await;
|
||||
|
||||
// Check if there is any empty slot in the player inventory
|
||||
if let Some(slot_index) = inventory.get_empty_slot() {
|
||||
inventory.state_id += 1;
|
||||
self.update_single_slot(&mut inventory, slot_index, dest_slot_data)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// Update held item
|
||||
inventory.set_selected(dest_slot);
|
||||
self.client
|
||||
.send_packet(&CSetHeldItem::new(dest_slot as i8))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn handle_pick_item_from_entity(&self, _pick_item: SPickItemFromEntity) {
|
||||
// TODO: Implement and merge any redundant code with pick_item_from_block
|
||||
}
|
||||
|
||||
pub async fn handle_player_command(&self, command: SPlayerCommand) {
|
||||
if command.entity_id != self.entity_id().into() {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user