add drag and error handling for containers
This commit is contained in:
parent
28292439de
commit
bb6677dd19
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1967,9 +1967,11 @@ dependencies = [
|
|||||||
name = "pumpkin-inventory"
|
name = "pumpkin-inventory"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"itertools 0.13.0",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -6,6 +6,7 @@ edition.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
|
thiserror = "1.0.63"
|
||||||
|
itertools = "0.13.0"
|
||||||
# For items
|
# For items
|
||||||
pumpkin-world = { path = "../pumpkin-world"}
|
pumpkin-world = { path = "../pumpkin-world"}
|
||||||
|
|||||||
@ -83,63 +83,21 @@ impl Click {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_drag_item(button: i8, slot: i16) -> Self {
|
fn new_drag_item(button: i8, slot: i16) -> Self {
|
||||||
let (mouse_type, state, slot) = match button {
|
let state = match button {
|
||||||
0 => (
|
0 => MouseDragState::Start(MouseDragType::Left),
|
||||||
MouseDragType::Left,
|
4 => MouseDragState::Start(MouseDragType::Right),
|
||||||
MouseDragState::Start,
|
8 => MouseDragState::Start(MouseDragType::Middle),
|
||||||
Slot::OutsideInventory,
|
1 | 5 | 9 => MouseDragState::AddSlot(slot.try_into().unwrap()),
|
||||||
),
|
2 | 6 | 10 => MouseDragState::End,
|
||||||
1 => (
|
|
||||||
MouseDragType::Left,
|
|
||||||
MouseDragState::AddSlot,
|
|
||||||
Slot::Normal(slot.try_into().unwrap()),
|
|
||||||
),
|
|
||||||
2 => (
|
|
||||||
MouseDragType::Left,
|
|
||||||
MouseDragState::End,
|
|
||||||
Slot::OutsideInventory,
|
|
||||||
),
|
|
||||||
4 => (
|
|
||||||
MouseDragType::Right,
|
|
||||||
MouseDragState::Start,
|
|
||||||
Slot::OutsideInventory,
|
|
||||||
),
|
|
||||||
5 => (
|
|
||||||
MouseDragType::Right,
|
|
||||||
MouseDragState::AddSlot,
|
|
||||||
Slot::Normal(slot.try_into().unwrap()),
|
|
||||||
),
|
|
||||||
6 => (
|
|
||||||
MouseDragType::Right,
|
|
||||||
MouseDragState::End,
|
|
||||||
Slot::OutsideInventory,
|
|
||||||
),
|
|
||||||
|
|
||||||
// ONLY FOR CREATIVE
|
|
||||||
8 => (
|
|
||||||
MouseDragType::Middle,
|
|
||||||
MouseDragState::Start,
|
|
||||||
Slot::OutsideInventory,
|
|
||||||
),
|
|
||||||
9 => (
|
|
||||||
MouseDragType::Middle,
|
|
||||||
MouseDragState::AddSlot,
|
|
||||||
Slot::Normal(slot.try_into().unwrap()),
|
|
||||||
),
|
|
||||||
10 => (
|
|
||||||
MouseDragType::Middle,
|
|
||||||
MouseDragState::End,
|
|
||||||
Slot::OutsideInventory,
|
|
||||||
),
|
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
click_type: ClickType::MouseDrag {
|
slot: match &state {
|
||||||
drag_state: state,
|
MouseDragState::AddSlot(slot) => Slot::Normal(*slot),
|
||||||
drag_type: mouse_type,
|
_ => Slot::OutsideInventory,
|
||||||
},
|
},
|
||||||
slot,
|
click_type: ClickType::MouseDrag { drag_state: state },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,10 +108,7 @@ pub enum ClickType {
|
|||||||
KeyClick(KeyClick),
|
KeyClick(KeyClick),
|
||||||
CreativePickItem,
|
CreativePickItem,
|
||||||
DropType(DropType),
|
DropType(DropType),
|
||||||
MouseDrag {
|
MouseDrag { drag_state: MouseDragState },
|
||||||
drag_state: MouseDragState,
|
|
||||||
drag_type: MouseDragType,
|
|
||||||
},
|
|
||||||
DoubleClick,
|
DoubleClick,
|
||||||
}
|
}
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@ -176,7 +131,7 @@ pub enum DropType {
|
|||||||
SingleItem,
|
SingleItem,
|
||||||
FullStack,
|
FullStack,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum MouseDragType {
|
pub enum MouseDragType {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
@ -184,8 +139,8 @@ pub enum MouseDragType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum MouseDragState {
|
pub enum MouseDragState {
|
||||||
Start,
|
Start(MouseDragType),
|
||||||
AddSlot,
|
AddSlot(usize),
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
178
pumpkin-inventory/src/drag_handler.rs
Normal file
178
pumpkin-inventory/src/drag_handler.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
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::iter::{Enumerate, Filter, TakeWhile};
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use std::vec::IntoIter;
|
||||||
|
#[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,
|
||||||
|
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 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 mut 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();
|
||||||
|
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] = carried_item.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseDragType::Right => {
|
||||||
|
let amount_of_items = carried_item.unwrap().item_count as usize;
|
||||||
|
let mut single_item = carried_item.clone().unwrap();
|
||||||
|
single_item.item_count = 1;
|
||||||
|
let changing_slots = drag.changing_slots(
|
||||||
|
amount_of_items,
|
||||||
|
&slots_cloned,
|
||||||
|
carried_item.as_ref().unwrap(),
|
||||||
|
);
|
||||||
|
let mut amount_removed = 0;
|
||||||
|
changing_slots.for_each(|slot| {
|
||||||
|
amount_removed += 1;
|
||||||
|
*slots[slot] = Some(single_item.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut remaining = carried_item.unwrap();
|
||||||
|
if remaining.item_count == amount_removed {
|
||||||
|
*carried_item = None
|
||||||
|
} else {
|
||||||
|
remaining.item_count -= amount_removed;
|
||||||
|
*carried_item = Some(remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseDragType::Left => {
|
||||||
|
let amount_of_items = carried_item.unwrap().item_count as usize;
|
||||||
|
// 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.changing_slots(
|
||||||
|
amount_of_items,
|
||||||
|
&slots_cloned,
|
||||||
|
carried_item.as_ref().unwrap(),
|
||||||
|
);
|
||||||
|
let amount_of_slots = changing_slots.clone().count();
|
||||||
|
let (amount_per_slot, remainder) =
|
||||||
|
(carried_item.unwrap().item_count as usize).div_rem_euclid(&amount_of_slots);
|
||||||
|
let mut item_in_each_slot = carried_item.unwrap().clone();
|
||||||
|
item_in_each_slot.item_count = amount_per_slot as u8;
|
||||||
|
changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot.clone()));
|
||||||
|
|
||||||
|
if remainder > 0 {
|
||||||
|
let mut remaining = carried_item.unwrap().clone();
|
||||||
|
remaining.item_count = remainder as u8;
|
||||||
|
*carried_item = Some(remaining)
|
||||||
|
} else {
|
||||||
|
*carried_item = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Drag {
|
||||||
|
player: i32,
|
||||||
|
drag_type: MouseDragType,
|
||||||
|
slots: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drag {
|
||||||
|
fn changing_slots<'a>(
|
||||||
|
&'a self,
|
||||||
|
amount_of_items: usize,
|
||||||
|
slots: &'a [Option<ItemStack>],
|
||||||
|
carried_item: &'a ItemStack,
|
||||||
|
) -> impl Iterator<Item = usize> + 'a + Clone {
|
||||||
|
self.slots
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take_while(move |(slot_number, _)| *slot_number <= amount_of_items)
|
||||||
|
.filter_map(move |(_, slot)| match &slots[*slot] {
|
||||||
|
Some(item_slot) => {
|
||||||
|
if *item_slot == *carried_item {
|
||||||
|
Some(*slot)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(*slot),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
33
pumpkin-inventory/src/error.rs
Normal file
33
pumpkin-inventory/src/error.rs
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,16 @@
|
|||||||
use crate::container_click::MouseClick;
|
use crate::container_click::{MouseClick, MouseDragState, MouseDragType};
|
||||||
use crate::player::PlayerInventory;
|
use crate::player::PlayerInventory;
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
|
|
||||||
pub mod container_click;
|
pub mod container_click;
|
||||||
|
pub mod drag_handler;
|
||||||
|
mod error;
|
||||||
mod open_container;
|
mod open_container;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod window_property;
|
pub mod window_property;
|
||||||
|
|
||||||
|
pub use error::InventoryError;
|
||||||
pub use open_container::OpenContainer;
|
pub use open_container::OpenContainer;
|
||||||
|
|
||||||
/// https://wiki.vg/Inventory
|
/// https://wiki.vg/Inventory
|
||||||
@ -68,8 +71,13 @@ pub trait Container: Sync + Send {
|
|||||||
carried_item: &mut Option<ItemStack>,
|
carried_item: &mut Option<ItemStack>,
|
||||||
slot: usize,
|
slot: usize,
|
||||||
mouse_click: MouseClick,
|
mouse_click: MouseClick,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
handle_item_change(carried_item, self.all_slots()[slot], mouse_click)
|
let mut all_slots = self.all_slots();
|
||||||
|
if slot < all_slots.len() {
|
||||||
|
Err(InventoryError::InvalidSlot)?
|
||||||
|
}
|
||||||
|
handle_item_change(carried_item, all_slots[slot], mouse_click);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>>;
|
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>>;
|
||||||
@ -98,6 +106,10 @@ pub trait Container: Sync + Send {
|
|||||||
dbg!(slot, item);
|
dbg!(slot, item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn internal_pumpkin_id(&self) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_item_take(
|
pub fn handle_item_take(
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
use crate::drag_handler::DragHandler;
|
||||||
use crate::{Container, WindowType};
|
use crate::{Container, WindowType};
|
||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub struct OpenContainer {
|
pub struct OpenContainer {
|
||||||
players: Vec<i32>,
|
players: Vec<i32>,
|
||||||
@ -8,21 +9,12 @@ pub struct OpenContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OpenContainer {
|
impl OpenContainer {
|
||||||
pub fn try_open(&self, player_id: i32) -> Option<MutexGuard<Box<dyn Container>>> {
|
pub fn try_open(&self, player_id: i32) -> Option<&Mutex<Box<dyn Container>>> {
|
||||||
if !self.players.contains(&player_id) {
|
if !self.players.contains(&player_id) {
|
||||||
dbg!("couldn't open container");
|
dbg!("couldn't open container");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let container = self.container.lock().unwrap();
|
let container = &self.container;
|
||||||
container
|
|
||||||
.all_slots_ref()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(slot, item)| {
|
|
||||||
if let Some(item) = item {
|
|
||||||
dbg!(slot, item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Some(container)
|
Some(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::container_click::MouseClick;
|
use crate::container_click::MouseClick;
|
||||||
use crate::{handle_item_change, Container, WindowType};
|
use crate::drag_handler::DragHandler;
|
||||||
|
use crate::{handle_item_change, Container, InventoryError, WindowType};
|
||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -92,17 +93,17 @@ impl PlayerInventory {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_slot(&mut self, slot: usize) -> &mut Option<ItemStack> {
|
pub fn get_slot(&mut self, slot: usize) -> Result<&mut Option<ItemStack>, InventoryError> {
|
||||||
match slot {
|
match slot {
|
||||||
0 => {
|
0 => {
|
||||||
// TODO: Add crafting check here
|
// TODO: Add crafting check here
|
||||||
&mut self.crafting_output
|
Ok(&mut self.crafting_output)
|
||||||
}
|
}
|
||||||
1..=4 => &mut self.crafting[slot - 1],
|
1..=4 => Ok(&mut self.crafting[slot - 1]),
|
||||||
5..=8 => &mut self.armor[slot - 5],
|
5..=8 => Ok(&mut self.armor[slot - 5]),
|
||||||
9..=44 => &mut self.items[slot - 9],
|
9..=44 => Ok(&mut self.items[slot - 9]),
|
||||||
45 => &mut self.offhand,
|
45 => Ok(&mut self.offhand),
|
||||||
_ => unreachable!(),
|
_ => Err(InventoryError::InvalidSlot),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_selected(&mut self, slot: usize) {
|
pub fn set_selected(&mut self, slot: usize) {
|
||||||
@ -144,9 +145,11 @@ impl Container for PlayerInventory {
|
|||||||
carried_slot: &mut Option<ItemStack>,
|
carried_slot: &mut Option<ItemStack>,
|
||||||
slot: usize,
|
slot: usize,
|
||||||
mouse_click: MouseClick,
|
mouse_click: MouseClick,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
let item_slot = self.get_slot(slot);
|
let item_slot = self.get_slot(slot)?;
|
||||||
handle_item_change(carried_slot, item_slot, mouse_click)
|
|
||||||
|
handle_item_change(carried_slot, item_slot, mouse_click);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>> {
|
fn all_slots(&mut self) -> Vec<&mut Option<ItemStack>> {
|
||||||
|
|||||||
@ -18,3 +18,9 @@ pub struct ItemStack {
|
|||||||
pub item_id: u32,
|
pub item_id: u32,
|
||||||
// TODO: Add Item Components
|
// TODO: Add Item Components
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ItemStack {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.item_id == other.item_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
|
use crate::entity::player::Player;
|
||||||
|
use crate::server::Server;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pumpkin_core::text::TextComponent;
|
use pumpkin_core::text::TextComponent;
|
||||||
use pumpkin_inventory::container_click::{KeyClick, MouseClick};
|
use pumpkin_core::GameMode;
|
||||||
|
use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState, MouseDragType};
|
||||||
|
use pumpkin_inventory::drag_handler::DragHandler;
|
||||||
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
|
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
|
||||||
use pumpkin_inventory::{container_click, OptionallyCombinedContainer};
|
use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer};
|
||||||
use pumpkin_inventory::{Container, WindowType};
|
use pumpkin_inventory::{Container, WindowType};
|
||||||
use pumpkin_protocol::client::play::{
|
use pumpkin_protocol::client::play::{
|
||||||
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
|
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
|
||||||
@ -10,10 +14,7 @@ use pumpkin_protocol::client::play::{
|
|||||||
use pumpkin_protocol::server::play::SClickContainer;
|
use pumpkin_protocol::server::play::SClickContainer;
|
||||||
use pumpkin_protocol::slot::Slot;
|
use pumpkin_protocol::slot::Slot;
|
||||||
use pumpkin_world::item::ItemStack;
|
use pumpkin_world::item::ItemStack;
|
||||||
use std::sync::MutexGuard;
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use crate::entity::player::Player;
|
|
||||||
use crate::server::Server;
|
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn open_container(
|
pub fn open_container(
|
||||||
@ -22,8 +23,11 @@ impl Player {
|
|||||||
minecraft_menu_id: &str,
|
minecraft_menu_id: &str,
|
||||||
window_title: Option<&str>,
|
window_title: Option<&str>,
|
||||||
) {
|
) {
|
||||||
let mut container = self.get_open_container(server);
|
self.inventory.reset_state_id();
|
||||||
let container = container.as_deref_mut();
|
let total_opened_containers = self.inventory.total_opened_containers;
|
||||||
|
let mut container = self
|
||||||
|
.get_open_container(server)
|
||||||
|
.map(|container| container.lock().unwrap());
|
||||||
let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
|
let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
|
||||||
.get("minecraft:menu")
|
.get("minecraft:menu")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -40,14 +44,12 @@ impl Player {
|
|||||||
.to_owned();
|
.to_owned();
|
||||||
let title = TextComponent::text(window_title.unwrap_or(window_type.default_title()));
|
let title = TextComponent::text(window_title.unwrap_or(window_type.default_title()));
|
||||||
|
|
||||||
self.inventory.reset_state_id();
|
|
||||||
|
|
||||||
self.client.send_packet(&COpenScreen::new(
|
self.client.send_packet(&COpenScreen::new(
|
||||||
self.inventory.total_opened_containers.into(),
|
total_opened_containers.into(),
|
||||||
menu_protocol_id,
|
menu_protocol_id,
|
||||||
title,
|
title,
|
||||||
));
|
));
|
||||||
self.set_container_content(container);
|
self.set_container_content(container.as_deref_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_container_content(&mut self, container: Option<&mut Box<dyn Container>>) {
|
pub fn set_container_content(&mut self, container: Option<&mut Box<dyn Container>>) {
|
||||||
@ -113,33 +115,35 @@ impl Player {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_click_container(&mut self, server: &mut Server, packet: SClickContainer) {
|
pub fn handle_click_container(
|
||||||
|
&mut self,
|
||||||
|
server: &mut Server,
|
||||||
|
packet: SClickContainer,
|
||||||
|
) -> Result<(), InventoryError> {
|
||||||
use container_click::*;
|
use container_click::*;
|
||||||
let mut opened_container = self.get_open_container(server);
|
let mut opened_container = self
|
||||||
let opened_container = opened_container.as_deref_mut();
|
.get_open_container(server)
|
||||||
|
.map(|container| container.lock().unwrap());
|
||||||
|
let mut opened_container = opened_container.as_deref_mut();
|
||||||
|
let drag_handler = &server.drag_handler;
|
||||||
|
|
||||||
let current_state_id = if let Some(container) = opened_container.as_ref() {
|
let current_state_id = if let Some(container) = opened_container.as_ref() {
|
||||||
container.state_id()
|
container.state_id()
|
||||||
} else {
|
} else {
|
||||||
self.inventory.state_id()
|
self.inventory.state_id()
|
||||||
};
|
};
|
||||||
|
// This is just checking for regular desync, client hasn't done anything malicious
|
||||||
if current_state_id != packet.state_id.0 {
|
if current_state_id != packet.state_id.0 {
|
||||||
dbg!(current_state_id, packet.state_id.0);
|
self.set_container_content(opened_container.as_deref_mut());
|
||||||
//self.set_container_content(opened_container.as_deref_mut());
|
return Ok(());
|
||||||
//return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opened_container.is_some() {
|
if opened_container.is_some() {
|
||||||
if packet.window_id != self.inventory.total_opened_containers {
|
if packet.window_id != self.inventory.total_opened_containers {
|
||||||
dbg!(
|
return Err(InventoryError::ClosedContainerInteract(self.entity_id()));
|
||||||
self.inventory.total_opened_containers,
|
|
||||||
opened_container.unwrap().window_type()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else if packet.window_id != 0 {
|
} else if packet.window_id != 0 {
|
||||||
dbg!("weird");
|
return Err(InventoryError::ClosedContainerInteract(self.entity_id()));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let click = Click::new(
|
let click = Click::new(
|
||||||
@ -160,26 +164,27 @@ impl Player {
|
|||||||
container_click::Slot::Normal(slot) => {
|
container_click::Slot::Normal(slot) => {
|
||||||
self.number_button_pressed(opened_container, key_click, slot)
|
self.number_button_pressed(opened_container, key_click, slot)
|
||||||
}
|
}
|
||||||
container_click::Slot::OutsideInventory => {
|
container_click::Slot::OutsideInventory => Err(InventoryError::InvalidPacket),
|
||||||
unimplemented!("This is not a valid state")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ClickType::CreativePickItem => {
|
ClickType::CreativePickItem => {
|
||||||
if let container_click::Slot::Normal(slot) = click.slot {
|
if let container_click::Slot::Normal(slot) = click.slot {
|
||||||
self.creative_pick_item(opened_container, slot)
|
self.creative_pick_item(opened_container, slot)
|
||||||
|
} else {
|
||||||
|
Err(InventoryError::InvalidPacket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClickType::DoubleClick => {
|
ClickType::DoubleClick => {
|
||||||
if let container_click::Slot::Normal(slot) = click.slot {
|
if let container_click::Slot::Normal(slot) = click.slot {
|
||||||
self.double_click(slot)
|
self.double_click(slot)
|
||||||
|
} else {
|
||||||
|
Err(InventoryError::InvalidPacket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClickType::MouseDrag {
|
ClickType::MouseDrag { drag_state } => {
|
||||||
drag_state: _,
|
self.mouse_drag(drag_handler, opened_container, drag_state)
|
||||||
drag_type: _,
|
}
|
||||||
} => (),
|
ClickType::DropType(_drop_type) => todo!(),
|
||||||
ClickType::DropType(_drop_type) => (),
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_click(
|
fn mouse_click(
|
||||||
@ -187,22 +192,22 @@ impl Player {
|
|||||||
opened_container: Option<&mut Box<dyn Container>>,
|
opened_container: Option<&mut Box<dyn Container>>,
|
||||||
mouse_click: MouseClick,
|
mouse_click: MouseClick,
|
||||||
slot: container_click::Slot,
|
slot: container_click::Slot,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
||||||
|
|
||||||
match slot {
|
match slot {
|
||||||
container_click::Slot::Normal(slot) => {
|
container_click::Slot::Normal(slot) => {
|
||||||
container.handle_item_change(&mut self.carried_item, slot, mouse_click)
|
container.handle_item_change(&mut self.carried_item, slot, mouse_click)
|
||||||
}
|
}
|
||||||
container_click::Slot::OutsideInventory => (),
|
container_click::Slot::OutsideInventory => todo!(),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shift_mouse_click(
|
fn shift_mouse_click(
|
||||||
&mut self,
|
&mut self,
|
||||||
opened_container: Option<&mut Box<dyn Container>>,
|
opened_container: Option<&mut Box<dyn Container>>,
|
||||||
slot: container_click::Slot,
|
slot: container_click::Slot,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
||||||
|
|
||||||
match slot {
|
match slot {
|
||||||
@ -235,13 +240,14 @@ impl Player {
|
|||||||
if let Some(slot) = slots {
|
if let Some(slot) = slots {
|
||||||
let mut item_slot = container.all_slots()[slot].map(|i| i.to_owned());
|
let mut item_slot = container.all_slots()[slot].map(|i| i.to_owned());
|
||||||
|
|
||||||
container.handle_item_change(&mut item_slot, slot, MouseClick::Left);
|
container.handle_item_change(&mut item_slot, slot, MouseClick::Left)?;
|
||||||
*container.all_slots()[slot] = item_slot;
|
*container.all_slots()[slot] = item_slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container_click::Slot::OutsideInventory => (),
|
container_click::Slot::OutsideInventory => (),
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn number_button_pressed(
|
fn number_button_pressed(
|
||||||
@ -249,35 +255,74 @@ impl Player {
|
|||||||
opened_container: Option<&mut Box<dyn Container>>,
|
opened_container: Option<&mut Box<dyn Container>>,
|
||||||
key_click: KeyClick,
|
key_click: KeyClick,
|
||||||
slot: usize,
|
slot: usize,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
let changing_slot = match key_click {
|
let changing_slot = match key_click {
|
||||||
KeyClick::Slot(slot) => slot,
|
KeyClick::Slot(slot) => slot,
|
||||||
KeyClick::Offhand => 45,
|
KeyClick::Offhand => 45,
|
||||||
};
|
};
|
||||||
let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned();
|
let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize)?.to_owned();
|
||||||
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
||||||
|
|
||||||
container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left);
|
container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left)?;
|
||||||
*self.inventory.get_slot(changing_slot as usize) = changing_item_slot
|
*self.inventory.get_slot(changing_slot as usize)? = changing_item_slot;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn creative_pick_item(
|
fn creative_pick_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
opened_container: Option<&mut Box<dyn Container>>,
|
opened_container: Option<&mut Box<dyn Container>>,
|
||||||
slot: usize,
|
slot: usize,
|
||||||
) {
|
) -> Result<(), InventoryError> {
|
||||||
|
if self.gamemode != GameMode::Creative {
|
||||||
|
return Err(InventoryError::PermissionError);
|
||||||
|
}
|
||||||
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
||||||
if let Some(Some(item)) = container.all_slots().get_mut(slot) {
|
if let Some(Some(item)) = container.all_slots().get_mut(slot) {
|
||||||
self.carried_item = Some(item.to_owned())
|
self.carried_item = Some(item.to_owned())
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn double_click(&mut self, _slot: usize) {}
|
fn double_click(&mut self, _slot: usize) -> Result<(), InventoryError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_drag(
|
||||||
|
&mut self,
|
||||||
|
drag_handler: &DragHandler,
|
||||||
|
opened_container: Option<&mut Box<dyn Container>>,
|
||||||
|
mouse_drag_state: MouseDragState,
|
||||||
|
) -> Result<(), InventoryError> {
|
||||||
|
let player_id = self.entity_id();
|
||||||
|
let container_id = opened_container
|
||||||
|
.as_ref()
|
||||||
|
.map(|container| container.internal_pumpkin_id())
|
||||||
|
.unwrap_or(player_id as u64);
|
||||||
|
match mouse_drag_state {
|
||||||
|
MouseDragState::Start(drag_type) => {
|
||||||
|
drag_handler.new_drag(container_id, player_id, drag_type)?
|
||||||
|
}
|
||||||
|
MouseDragState::AddSlot(slot) => {
|
||||||
|
drag_handler.add_slot(container_id, player_id, slot)?
|
||||||
|
}
|
||||||
|
MouseDragState::End => {
|
||||||
|
let mut container =
|
||||||
|
OptionallyCombinedContainer::new(&mut self.inventory, opened_container);
|
||||||
|
drag_handler.apply_drag(
|
||||||
|
&mut self.carried_item,
|
||||||
|
&mut container,
|
||||||
|
&container_id,
|
||||||
|
player_id,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_open_container<'a>(
|
pub fn get_open_container<'a>(
|
||||||
&self,
|
&self,
|
||||||
server: &'a Server,
|
server: &'a Server,
|
||||||
) -> Option<MutexGuard<'a, Box<dyn Container>>> {
|
) -> Option<&'a Mutex<Box<dyn Container>>> {
|
||||||
if let Some(id) = self.open_container {
|
if let Some(id) = self.open_container {
|
||||||
server.try_get_container(self.entity_id(), id)
|
server.try_get_container(self.entity_id(), id)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{client::Client, entity::player::Player, world::World};
|
use crate::{client::Client, entity::player::Player, world::World};
|
||||||
|
use pumpkin_inventory::drag_handler::DragHandler;
|
||||||
use pumpkin_inventory::{Container, OpenContainer};
|
use pumpkin_inventory::{Container, OpenContainer};
|
||||||
use pumpkin_registry::Registry;
|
use pumpkin_registry::Registry;
|
||||||
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
|
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
|
||||||
@ -48,6 +49,7 @@ pub struct Server {
|
|||||||
pub cached_registry: Vec<Registry>,
|
pub cached_registry: Vec<Registry>,
|
||||||
|
|
||||||
pub open_containers: HashMap<u64, OpenContainer>,
|
pub open_containers: HashMap<u64, OpenContainer>,
|
||||||
|
pub drag_handler: DragHandler,
|
||||||
entity_id: AtomicI32,
|
entity_id: AtomicI32,
|
||||||
|
|
||||||
/// Used for Authentication, None is Online mode is disabled
|
/// Used for Authentication, None is Online mode is disabled
|
||||||
@ -94,6 +96,7 @@ impl Server {
|
|||||||
plugin_loader,
|
plugin_loader,
|
||||||
cached_registry: Registry::get_static(),
|
cached_registry: Registry::get_static(),
|
||||||
open_containers: HashMap::new(),
|
open_containers: HashMap::new(),
|
||||||
|
drag_handler: DragHandler::new(),
|
||||||
// 0 is invalid
|
// 0 is invalid
|
||||||
entity_id: 2.into(),
|
entity_id: 2.into(),
|
||||||
worlds: vec![Arc::new(tokio::sync::Mutex::new(world))],
|
worlds: vec![Arc::new(tokio::sync::Mutex::new(world))],
|
||||||
@ -134,8 +137,11 @@ impl Server {
|
|||||||
&self,
|
&self,
|
||||||
player_id: EntityId,
|
player_id: EntityId,
|
||||||
container_id: u64,
|
container_id: u64,
|
||||||
) -> Option<MutexGuard<'_, Box<dyn Container>>> {
|
) -> Option<&Mutex<Box<dyn Container>>> {
|
||||||
self.open_containers.get(&container_id)?.try_open(player_id)
|
self.open_containers
|
||||||
|
.get(&container_id)?
|
||||||
|
.try_open(player_id)
|
||||||
|
.map(|container| container)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a Packet to all Players in all worlds
|
/// Sends a Packet to all Players in all worlds
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user