Skip to content

Commit

Permalink
Fix dupe, doubble click and shift click
Browse files Browse the repository at this point in the history
  • Loading branch information
4lve committed Feb 22, 2025
1 parent 2af2fbb commit 15b0849
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 63 deletions.
9 changes: 5 additions & 4 deletions pumpkin-inventory/src/container_click.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::InventoryError;
use pumpkin_protocol::server::play::SlotActionType;
use pumpkin_world::item::ItemStack;

#[derive(Debug)]
pub struct Click {
pub slot: Slot,
pub click_type: ClickType,
Expand All @@ -18,7 +19,7 @@ impl Click {
click_type: ClickType::CreativePickItem,
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?),
}),
SlotActionType::Throw => Self::new_drop_item(button),
SlotActionType::Throw => Self::new_drop_item(button, slot),
SlotActionType::QuickCraft => Self::new_drag_item(button, slot),
SlotActionType::PickupAll => Ok(Self {
click_type: ClickType::DoubleClick,
Expand Down Expand Up @@ -66,15 +67,15 @@ impl Click {
})
}

fn new_drop_item(button: i8) -> Result<Self, InventoryError> {
fn new_drop_item(button: i8, slot: i16) -> Result<Self, InventoryError> {
let drop_type = match button {
0 => DropType::SingleItem,
1 => DropType::FullStack,
_ => Err(InventoryError::InvalidPacket)?,
};
Ok(Self {
click_type: ClickType::DropType(drop_type),
slot: Slot::OutsideInventory,
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?),
})
}

Expand Down Expand Up @@ -120,7 +121,7 @@ pub enum KeyClick {
Slot(u8),
Offhand,
}
#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub enum Slot {
Normal(usize),
OutsideInventory,
Expand Down
202 changes: 143 additions & 59 deletions pumpkin/src/net/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,18 +277,32 @@ impl Player {
.await
}
ClickType::DropType(drop_type) => {
let carried_item = self.carried_item.load();
if let Some(item) = carried_item {
match drop_type {
DropType::FullStack => self.drop_item(server, item).await,
DropType::SingleItem => {
let mut item = item;
item.item_count = 1;
self.drop_item(server, item).await;
if let container_click::Slot::Normal(slot) = click.slot {
let mut inventory = self.inventory().lock().await;
let mut container =
OptionallyCombinedContainer::new(&mut inventory, opened_container);
let mut slots = container.all_slots();

if let Some(item) = slots[slot].as_mut() {
match drop_type {
DropType::FullStack => {
self.drop_item(server, item.to_owned()).await;
*slots[slot] = None;
}
DropType::SingleItem => {
self.drop_item(server, ItemStack::new(1, item.item)).await;
item.item_count -= 1;
if item.item_count == 0 {
*slots[slot] = None;
}
}
}
};
}

Ok(())
} else {
Err(InventoryError::InvalidPacket)
}
Ok(())
}
}
}
Expand Down Expand Up @@ -318,11 +332,19 @@ impl Player {
container_click::Slot::OutsideInventory => {
if let Some(item) = carried_item {
match mouse_click {
MouseClick::Left => self.drop_item(server, item).await,
MouseClick::Left => {
self.drop_item(server, item).await;
self.carried_item.store(None);
}
MouseClick::Right => {
let mut item = item;
item.item_count = 1;
self.drop_item(server, item).await;
self.drop_item(server, ItemStack::new(1, item.item)).await;
item.item_count -= 1;
if item.item_count == 0 {
self.carried_item.store(None);
} else {
self.carried_item.store(Some(item));
}
}
};
}
Expand All @@ -331,47 +353,108 @@ impl Player {
}
}

/// TODO: Allow equiping/de equiping armor and allow taking items from crafting grid
async fn shift_mouse_click(
&self,
opened_container: Option<&mut Box<dyn Container>>,
slot: container_click::Slot,
taking_crafted: bool,
_taking_crafted: bool,
) -> Result<(), InventoryError> {
let mut inventory = self.inventory().lock().await;
let has_container = opened_container.is_some();
let container_size = opened_container
.as_ref()
.map_or(0, |c| c.all_slots_ref().len());
let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container);

match slot {
container_click::Slot::Normal(slot) => {
let all_slots = container.all_slots();
if let Some(item_in_pressed_slot) = all_slots[slot].to_owned() {
let slots = all_slots.into_iter().enumerate();
// Hotbar
let find_condition = |(slot_number, slot): (usize, &mut Option<ItemStack>)| {
// TODO: Check for max item count here
match slot {
Some(item) => (item.item.id == item_in_pressed_slot.item.id
&& item.item_count != 64)
.then_some(slot_number),
None => Some(slot_number),
}
let mut all_slots = container.all_slots();

// Get the item data we need before any mutable operations
let (item, max_stack, initial_count) =
if let Some(item_in_pressed_slot) = all_slots[slot].as_ref() {
(
item_in_pressed_slot.item,
item_in_pressed_slot.item.components.max_stack_size,
item_in_pressed_slot.item_count,
)
} else {
return Ok(());
};

let slots = if slot > 35 {
slots.skip(9).find_map(find_condition)
let mut remaining_count = initial_count;

// Define the two inventories and determine which one contains the source slot
let (inv1_range, inv2_range) = if has_container {
// When container is open:
// Inv1 = Container slots (0 to container_size-1)
// Inv2 = Player inventory (container_size to end)
((0..container_size), (container_size..all_slots.len()))
} else {
// When no container:
// Inv1 = Hotbar (36-45)
// Inv2 = Main inventory (9-36)
((36..45), (9..36))
};

let inv_1_clone = inv1_range.clone();

// Determine which inventory we're moving from and to
let (source_inv, target_inv) = if inv1_range.contains(&slot) {
(inv1_range, inv2_range)
} else if inv2_range.contains(&slot) {
(inv2_range, inv1_range)
} else {
return Ok(());
};

// If moving to hotbar, reverse the order to fill from right to left
let target_slots: Vec<usize> =
if has_container && source_inv.contains(&slot) && source_inv == inv_1_clone {
target_inv.rev().collect()
} else {
slots.skip(36).rev().find_map(find_condition)
target_inv.collect()
};
if let Some(slot) = slots {
let mut item_slot = container.all_slots()[slot].map(|i| i);
container.handle_item_change(
&mut item_slot,
slot,
MouseClick::Left,
taking_crafted,
)?;
*container.all_slots()[slot] = item_slot;

// First try to stack with existing items
for target_idx in &target_slots {
if let Some(target_item) = all_slots[*target_idx].as_mut() {
if target_item.item.id == item.id && target_item.item_count < max_stack {
let space_in_stack = max_stack - target_item.item_count;
let amount_to_add = remaining_count.min(space_in_stack);
target_item.item_count += amount_to_add;
remaining_count -= amount_to_add;

if remaining_count == 0 {
*all_slots[slot] = None;
return Ok(());
}
}
}
}

// Then try to place in empty slots
for target_idx in &target_slots {
if all_slots[*target_idx].is_none()
|| all_slots[*target_idx].is_some_and(|item| item.item_count == 0)
{
*all_slots[*target_idx] = Some(ItemStack {
item,
item_count: remaining_count,
});
*all_slots[slot] = None;
return Ok(());
}
}

// Update original slot if we moved some but not all items
if remaining_count != initial_count {
*all_slots[slot] = Some(ItemStack {
item,
item_count: remaining_count,
});
}
}
container_click::Slot::OutsideInventory => (),
};
Expand Down Expand Up @@ -422,34 +505,35 @@ impl Player {
async fn double_click(
&self,
opened_container: Option<&mut Box<dyn Container>>,
slot: usize,
_slot: usize,
) -> Result<(), InventoryError> {
let mut inventory = self.inventory().lock().await;
let mut container = OptionallyCombinedContainer::new(&mut inventory, opened_container);
let mut slots = container.all_slots();

let Some(item) = slots.get_mut(slot) else {
let Some(mut carried_item) = self.carried_item.load() else {
return Ok(());
};
let Some(mut carried_item) = **item else {
return Ok(());
};
**item = None;

for slot in slots.iter_mut().filter_map(|slot| slot.as_mut()) {
if slot.item.id == carried_item.item.id {
// TODO: Check for max stack size
if slot.item_count + carried_item.item_count <= 64 {
slot.item_count = 0;
carried_item.item_count = 64;
} else {
let to_remove = slot.item_count - (64 - carried_item.item_count);
slot.item_count -= to_remove;
carried_item.item_count += to_remove;
}

if carried_item.item_count == 64 {
break;
// Iterate directly over container slots to modify them in place'
for slot in container.all_slots() {
if let Some(itemstack) = slot {
if itemstack.item.id == carried_item.item.id {
if itemstack.item_count + carried_item.item_count
<= carried_item.item.components.max_stack_size
{
carried_item.item_count += itemstack.item_count;
*slot = None;
} else {
let overflow = itemstack.item_count
- (carried_item.item.components.max_stack_size
- carried_item.item_count);
carried_item.item_count = carried_item.item.components.max_stack_size;
itemstack.item_count = overflow;
}

if carried_item.item_count == carried_item.item.components.max_stack_size {
break;
}
}
}
}
Expand Down

0 comments on commit 15b0849

Please sign in to comment.