Implementation of QuickCraft, PickupAll, Clone and Throw in inventory (based on #862) (#940)

* initial test for pickupall

(cherry picked from commit a939804eff)

* added quickcraft right click and throw

(cherry picked from commit cdcbccd962)

* comments

(cherry picked from commit f23bc8fd4d)

* fix: QuickCraft handling and add drag slot support

* fix: enhance quick craft item handling in inventory

* fix

* fix clippy

* fix: drag

* fix: improve item throwing logic in screen handler

* fix: fix drag & throw

* fix: throw

* fix

* fix

* fix

* fix: PickupAll

* fix

* fix

---------

Co-authored-by: azomDev <dev@azom.ca>
This commit is contained in:
Liyan Zhao
2025-06-28 05:08:45 +08:00
committed by GitHub
parent 8089421f89
commit d16b0f98c2
2 changed files with 153 additions and 6 deletions

View File

@@ -1,5 +1,3 @@
use std::{any::Any, collections::HashMap, sync::Arc};
use async_trait::async_trait;
use log::warn;
use pumpkin_data::screen::WindowType;
@@ -14,6 +12,8 @@ use pumpkin_protocol::{
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{ComparableInventory, Inventory};
use pumpkin_world::item::ItemStack;
use std::cmp::max;
use std::{any::Any, collections::HashMap, sync::Arc};
use tokio::sync::Mutex;
use crate::{
@@ -45,6 +45,7 @@ impl ScreenProperty {
pub trait InventoryPlayer: Send + Sync {
async fn drop_item(&self, item: ItemStack, retain_ownership: bool);
fn get_inventory(&self) -> Arc<PlayerInventory>;
fn has_infinite_materials(&self) -> bool;
async fn enqueue_inventory_packet(&self, packet: &CSetContainerContent);
async fn enqueue_slot_packet(&self, packet: &CSetContainerSlot);
async fn enqueue_cursor_packet(&self, packet: &CSetCursorItem);
@@ -466,8 +467,150 @@ pub trait ScreenHandler: Send + Sync {
action_type: SlotActionType,
player: &dyn InventoryPlayer,
) {
//TODO: Implement quickcraft, Clone, PickupAll, Throw
if (action_type == SlotActionType::Pickup || action_type == SlotActionType::QuickMove)
if action_type == SlotActionType::PickupAll && button == 0 {
let behavior = self.get_behaviour_mut();
let mut cursor_stack = behavior.cursor_stack.lock().await;
let mut to_pick_up = cursor_stack.get_max_stack_size() - cursor_stack.item_count;
for slot in behavior.slots.iter() {
if to_pick_up == 0 {
break;
}
let stack_lock = slot.get_stack().await;
let item_stack = stack_lock.lock().await;
if !item_stack.are_items_and_components_equal(&cursor_stack) {
continue;
}
if !slot.can_take_items(player).await {
continue;
}
let take = item_stack.item_count.min(to_pick_up);
to_pick_up -= take;
if item_stack.item_count == take {
drop(item_stack);
slot.set_stack(ItemStack::EMPTY).await;
} else {
let mut stack_clone = *item_stack;
drop(item_stack);
stack_clone.decrement(take);
slot.set_stack(stack_clone).await;
}
cursor_stack.increment(take);
}
} else if action_type == SlotActionType::QuickCraft {
let drag_type = button & 3;
let drag_button = (button >> 2) & 3;
let behaviour = self.get_behaviour_mut();
if drag_type == 0 {
behaviour.drag_slots.clear();
} else if drag_type == 1 {
if slot_index < 0 {
warn!("Invalid slot index for drag action: {slot_index}. Must be >= 0");
return;
}
let cursor_stack = behaviour.cursor_stack.lock().await;
let slot = &behaviour.slots[slot_index as usize];
let stack_lock = slot.get_stack().await;
let stack = stack_lock.lock().await;
if !cursor_stack.is_empty()
&& slot.can_insert(&cursor_stack).await
&& (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
&& slot.get_max_item_count_for_stack(&stack).await > stack.item_count
{
behaviour.drag_slots.push(slot_index as u32);
}
} else if drag_type == 2 && !behaviour.drag_slots.is_empty() {
// process drag end
if behaviour.drag_slots.len() == 1 {
let slot = behaviour.drag_slots[0] as i32;
behaviour.drag_slots.clear();
let _ = behaviour;
self.internal_on_slot_click(slot, drag_button, SlotActionType::Pickup, player)
.await;
return;
}
if drag_button == 2 && !player.has_infinite_materials() {
return; // Only creative
}
let mut cursor_stack = behaviour.cursor_stack.lock().await;
let initial_count = cursor_stack.item_count;
for slot_index in behaviour.drag_slots.iter() {
let slot = behaviour.slots[*slot_index as usize].clone();
let stack_lock = slot.get_stack().await;
let stack = stack_lock.lock().await;
if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
&& slot.can_insert(&cursor_stack).await
{
let mut inserting_count = if drag_button == 0 {
initial_count / behaviour.drag_slots.len() as u8
} else if drag_button == 1 {
1
} else if drag_button == 2 {
cursor_stack.get_max_stack_size()
} else {
panic!("Invalid drag button: {drag_button}");
};
inserting_count = inserting_count
.min(max(
0,
slot.get_max_item_count_for_stack(&stack).await - stack.item_count,
))
.min(cursor_stack.item_count);
if inserting_count > 0 {
let mut stack_clone = *stack;
drop(stack);
if stack_clone.is_empty() {
stack_clone = cursor_stack.copy_with_count(0);
}
stack_clone.increment(inserting_count);
slot.set_stack(stack_clone).await;
if drag_button != 2 {
cursor_stack.decrement(inserting_count);
}
if cursor_stack.is_empty() {
break;
}
}
}
}
behaviour.drag_slots.clear();
}
} else if action_type == SlotActionType::Throw {
if slot_index >= 0 && self.get_behaviour().cursor_stack.lock().await.is_empty() {
let slot = self.get_behaviour().slots[slot_index as usize].clone();
let stack_lock = slot.get_stack().await;
let mut target_stack = stack_lock.lock().await;
if !target_stack.is_empty() {
if button == 1 {
let stack_clone = *target_stack;
player.drop_item(stack_clone, true).await;
drop(target_stack);
slot.set_stack(ItemStack::EMPTY).await;
} else {
player.drop_item(target_stack.split(1), true).await;
}
}
}
} else if action_type == SlotActionType::Clone {
if player.has_infinite_materials() && slot_index >= 0 {
let behaviour = self.get_behaviour_mut();
let slot = behaviour.slots[slot_index as usize].clone();
let stack_lock = slot.get_stack().await;
let stack = stack_lock.lock().await;
let mut cursor_stack = behaviour.cursor_stack.lock().await;
*cursor_stack = stack.copy_with_count(stack.get_max_stack_size());
}
} else if (action_type == SlotActionType::Pickup
|| action_type == SlotActionType::QuickMove)
&& (button == 0 || button == 1)
{
let click_type = if button == 0 {
@@ -538,7 +681,6 @@ pub trait ScreenHandler: Send + Sync {
if slot_stack.is_empty() {
if !cursor_stack.is_empty() {
//println!("Cursor -> Slot");
let transfer_count = if click_type == MouseClick::Left {
cursor_stack.item_count
} else {
@@ -549,7 +691,6 @@ pub trait ScreenHandler: Send + Sync {
}
} else if slot.can_take_items(player).await {
if cursor_stack.is_empty() {
//println!("Slot -> Cursor");
let take_count = if click_type == MouseClick::Left {
slot_stack.item_count
} else {
@@ -717,6 +858,7 @@ pub struct ScreenHandlerBehaviour {
pub properties: Vec<ScreenProperty>,
pub tracked_property_values: Vec<i32>,
pub window_type: Option<WindowType>,
pub drag_slots: Vec<u32>,
}
impl ScreenHandlerBehaviour {
@@ -735,6 +877,7 @@ impl ScreenHandlerBehaviour {
properties: Vec::new(),
tracked_property_values: Vec::new(),
window_type,
drag_slots: Vec::new(),
}
}

View File

@@ -2481,6 +2481,10 @@ impl InventoryPlayer for Player {
self.drop_item(item).await;
}
fn has_infinite_materials(&self) -> bool {
self.gamemode.load() == GameMode::Creative
}
fn get_inventory(&self) -> Arc<PlayerInventory> {
self.inventory.clone()
}