-
-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #60 from Bryntet/master
Add packet SClickContainer and abstractions
- Loading branch information
Showing
20 changed files
with
1,335 additions
and
152 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
use crate::InventoryError; | ||
use pumpkin_world::item::ItemStack; | ||
|
||
pub struct Click { | ||
pub slot: Slot, | ||
pub click_type: ClickType, | ||
} | ||
|
||
impl Click { | ||
pub fn new(mode: u8, button: i8, slot: i16) -> Result<Self, InventoryError> { | ||
match mode { | ||
0 => Self::new_normal_click(button, slot), | ||
// Both buttons do the same here, so we omit it | ||
1 => Self::new_shift_click(slot), | ||
2 => Self::new_key_click(button, slot), | ||
3 => Ok(Self { | ||
click_type: ClickType::CreativePickItem, | ||
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), | ||
}), | ||
4 => Self::new_drop_item(button), | ||
5 => Self::new_drag_item(button, slot), | ||
6 => Ok(Self { | ||
click_type: ClickType::DoubleClick, | ||
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), | ||
}), | ||
_ => Err(InventoryError::InvalidPacket), | ||
} | ||
} | ||
|
||
fn new_normal_click(button: i8, slot: i16) -> Result<Self, InventoryError> { | ||
let slot = match slot { | ||
-999 => Slot::OutsideInventory, | ||
_ => { | ||
let slot = slot.try_into().or(Err(InventoryError::InvalidSlot))?; | ||
Slot::Normal(slot) | ||
} | ||
}; | ||
let button = match button { | ||
0 => MouseClick::Left, | ||
1 => MouseClick::Right, | ||
_ => Err(InventoryError::InvalidPacket)?, | ||
}; | ||
Ok(Self { | ||
click_type: ClickType::MouseClick(button), | ||
slot, | ||
}) | ||
} | ||
|
||
fn new_shift_click(slot: i16) -> Result<Self, InventoryError> { | ||
Ok(Self { | ||
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), | ||
click_type: ClickType::ShiftClick, | ||
}) | ||
} | ||
|
||
fn new_key_click(button: i8, slot: i16) -> Result<Self, InventoryError> { | ||
let key = match button { | ||
0..9 => KeyClick::Slot(button.try_into().or(Err(InventoryError::InvalidSlot))?), | ||
40 => KeyClick::Offhand, | ||
_ => Err(InventoryError::InvalidSlot)?, | ||
}; | ||
|
||
Ok(Self { | ||
click_type: ClickType::KeyClick(key), | ||
slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), | ||
}) | ||
} | ||
|
||
fn new_drop_item(button: i8) -> 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, | ||
}) | ||
} | ||
|
||
fn new_drag_item(button: i8, slot: i16) -> Result<Self, InventoryError> { | ||
let state = match button { | ||
0 => MouseDragState::Start(MouseDragType::Left), | ||
4 => MouseDragState::Start(MouseDragType::Right), | ||
8 => MouseDragState::Start(MouseDragType::Middle), | ||
1 | 5 | 9 => { | ||
MouseDragState::AddSlot(slot.try_into().or(Err(InventoryError::InvalidSlot))?) | ||
} | ||
2 | 6 | 10 => MouseDragState::End, | ||
_ => Err(InventoryError::InvalidPacket)?, | ||
}; | ||
Ok(Self { | ||
slot: match &state { | ||
MouseDragState::AddSlot(slot) => Slot::Normal(*slot), | ||
_ => Slot::OutsideInventory, | ||
}, | ||
click_type: ClickType::MouseDrag { drag_state: state }, | ||
}) | ||
} | ||
} | ||
|
||
pub enum ClickType { | ||
MouseClick(MouseClick), | ||
ShiftClick, | ||
KeyClick(KeyClick), | ||
CreativePickItem, | ||
DropType(DropType), | ||
MouseDrag { drag_state: MouseDragState }, | ||
DoubleClick, | ||
} | ||
#[derive(Debug, PartialEq, Eq)] | ||
pub enum MouseClick { | ||
Left, | ||
Right, | ||
} | ||
|
||
pub enum KeyClick { | ||
Slot(u8), | ||
Offhand, | ||
} | ||
#[derive(Copy, Clone)] | ||
pub enum Slot { | ||
Normal(usize), | ||
OutsideInventory, | ||
} | ||
|
||
pub enum DropType { | ||
SingleItem, | ||
FullStack, | ||
} | ||
#[derive(Debug, PartialEq)] | ||
pub enum MouseDragType { | ||
Left, | ||
Right, | ||
Middle, | ||
} | ||
#[derive(PartialEq)] | ||
pub enum MouseDragState { | ||
Start(MouseDragType), | ||
AddSlot(usize), | ||
End, | ||
} | ||
|
||
pub enum ItemChange { | ||
Remove { slot: usize }, | ||
Add { slot: usize, item: ItemStack }, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
use crate::container_click::MouseDragType; | ||
use crate::{Container, InventoryError}; | ||
use itertools::Itertools; | ||
use num_traits::Euclid; | ||
use pumpkin_world::item::ItemStack; | ||
use std::collections::HashMap; | ||
use std::sync::{Arc, Mutex, RwLock}; | ||
#[derive(Debug, Default)] | ||
pub struct DragHandler(RwLock<HashMap<u64, Arc<Mutex<Drag>>>>); | ||
|
||
impl DragHandler { | ||
pub fn new() -> Self { | ||
Self(RwLock::new(HashMap::new())) | ||
} | ||
pub fn new_drag( | ||
&self, | ||
container_id: u64, | ||
player: i32, | ||
drag_type: MouseDragType, | ||
) -> Result<(), InventoryError> { | ||
let drag = Drag { | ||
player, | ||
drag_type, | ||
slots: vec![], | ||
}; | ||
let mut drags = match self.0.write() { | ||
Ok(drags) => drags, | ||
Err(_) => Err(InventoryError::LockError)?, | ||
}; | ||
drags.insert(container_id, Arc::new(Mutex::new(drag))); | ||
Ok(()) | ||
} | ||
|
||
pub fn add_slot( | ||
&self, | ||
container_id: u64, | ||
player: i32, | ||
slot: usize, | ||
) -> Result<(), InventoryError> { | ||
let drags = match self.0.read() { | ||
Ok(drags) => drags, | ||
Err(_) => Err(InventoryError::LockError)?, | ||
}; | ||
match drags.get(&container_id) { | ||
Some(drag) => { | ||
let mut drag = drag.lock().unwrap(); | ||
if drag.player != player { | ||
Err(InventoryError::MultiplePlayersDragging)? | ||
} | ||
if !drag.slots.contains(&slot) { | ||
drag.slots.push(slot); | ||
} | ||
} | ||
None => Err(InventoryError::OutOfOrderDragging)?, | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub fn apply_drag<T: Container>( | ||
&self, | ||
maybe_carried_item: &mut Option<ItemStack>, | ||
container: &mut T, | ||
container_id: &u64, | ||
player: i32, | ||
) -> Result<(), InventoryError> { | ||
// Minecraft client does still send dragging packets when not carrying an item! | ||
if maybe_carried_item.is_none() { | ||
return Ok(()); | ||
} | ||
|
||
let Ok(mut drags) = self.0.write() else { | ||
Err(InventoryError::LockError)? | ||
}; | ||
let Some((_, drag)) = drags.remove_entry(container_id) else { | ||
Err(InventoryError::OutOfOrderDragging)? | ||
}; | ||
let drag = drag.lock().unwrap(); | ||
|
||
if player != drag.player { | ||
Err(InventoryError::MultiplePlayersDragging)? | ||
} | ||
let mut slots = container.all_slots(); | ||
let slots_cloned = slots | ||
.iter() | ||
.map(|stack| stack.map(|item| item.to_owned())) | ||
.collect_vec(); | ||
let Some(carried_item) = maybe_carried_item else { | ||
return Ok(()); | ||
}; | ||
match drag.drag_type { | ||
// This is only valid in Creative GameMode. | ||
// Checked in any function that uses this function. | ||
MouseDragType::Middle => { | ||
for slot in &drag.slots { | ||
*slots[*slot] = *maybe_carried_item; | ||
} | ||
} | ||
MouseDragType::Right => { | ||
let mut single_item = *carried_item; | ||
single_item.item_count = 1; | ||
|
||
let changing_slots = | ||
drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); | ||
changing_slots.for_each(|slot| { | ||
if carried_item.item_count != 0 { | ||
carried_item.item_count -= 1; | ||
if let Some(stack) = &mut slots[slot] { | ||
// TODO: Check for stack max here | ||
if stack.item_count + 1 < 64 { | ||
stack.item_count += 1; | ||
} else { | ||
carried_item.item_count += 1; | ||
} | ||
} else { | ||
*slots[slot] = Some(single_item) | ||
} | ||
} | ||
}); | ||
|
||
if carried_item.item_count == 0 { | ||
*maybe_carried_item = None | ||
} | ||
} | ||
MouseDragType::Left => { | ||
// TODO: Handle dragging a stack with greater amount than item allows as max unstackable | ||
// In that specific case, follow MouseDragType::Right behaviours instead! | ||
|
||
let changing_slots = | ||
drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); | ||
let amount_of_slots = changing_slots.clone().count(); | ||
let (amount_per_slot, remainder) = | ||
(carried_item.item_count as usize).div_rem_euclid(&amount_of_slots); | ||
let mut item_in_each_slot = *carried_item; | ||
item_in_each_slot.item_count = amount_per_slot as u8; | ||
changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot)); | ||
|
||
if remainder > 0 { | ||
carried_item.item_count = remainder as u8; | ||
} else { | ||
*maybe_carried_item = None | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} | ||
#[derive(Debug)] | ||
struct Drag { | ||
player: i32, | ||
drag_type: MouseDragType, | ||
slots: Vec<usize>, | ||
} | ||
|
||
impl Drag { | ||
fn possibly_changing_slots<'a>( | ||
&'a self, | ||
slots: &'a [Option<ItemStack>], | ||
carried_item_id: u32, | ||
) -> impl Iterator<Item = usize> + 'a + Clone { | ||
self.slots.iter().filter_map(move |slot_index| { | ||
let slot = &slots[*slot_index]; | ||
|
||
match slot { | ||
Some(item_slot) => { | ||
if item_slot.item_id == carried_item_id { | ||
Some(*slot_index) | ||
} else { | ||
None | ||
} | ||
} | ||
None => Some(*slot_index), | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use thiserror::Error; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum InventoryError { | ||
#[error("Unable to lock")] | ||
LockError, | ||
#[error("Invalid slot")] | ||
InvalidSlot, | ||
#[error("Player '{0}' tried to interact with a closed container")] | ||
ClosedContainerInteract(i32), | ||
#[error("Multiple players dragging in a container at once")] | ||
MultiplePlayersDragging, | ||
#[error("Out of order dragging")] | ||
OutOfOrderDragging, | ||
#[error("Invalid inventory packet")] | ||
InvalidPacket, | ||
#[error("Player does not have enough permissions")] | ||
PermissionError, | ||
} | ||
|
||
impl InventoryError { | ||
pub fn should_kick(&self) -> bool { | ||
match self { | ||
InventoryError::InvalidSlot | ||
| InventoryError::ClosedContainerInteract(..) | ||
| InventoryError::InvalidPacket | ||
| InventoryError::PermissionError => true, | ||
InventoryError::LockError | ||
| InventoryError::OutOfOrderDragging | ||
| InventoryError::MultiplePlayersDragging => false, | ||
} | ||
} | ||
} |
Oops, something went wrong.