Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pick item (Middle Mouse Click) #418

Merged
merged 12 commits into from
Dec 29, 2024
29 changes: 29 additions & 0 deletions pumpkin-inventory/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
2 changes: 2 additions & 0 deletions pumpkin-protocol/src/server/play/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down
17 changes: 17 additions & 0 deletions pumpkin-protocol/src/server/play/s_pick_item.rs
Original file line number Diff line number Diff line change
@@ -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,
}
12 changes: 9 additions & 3 deletions pumpkin/src/entity/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -675,6 +676,7 @@ impl Player {
}
}

#[allow(clippy::too_many_lines)]
pub async fn handle_play_packet(
self: &Arc<Self>,
server: &Arc<Server>,
Expand Down Expand Up @@ -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;
Expand Down
105 changes: 102 additions & 3 deletions pumpkin/src/net/packet/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Loading