Add all functionality of mode zero for packet SClickContainer

This commit is contained in:
Edvin Bryntesson 2024-08-26 12:58:12 +02:00
parent b90fd4c62d
commit e0a20c1194
No known key found for this signature in database
9 changed files with 240 additions and 64 deletions

View File

@ -1,5 +1,5 @@
use crate::WindowType; use crate::WindowType;
use pumpkin_world::item::Item; use pumpkin_world::item::ItemStack;
pub struct ClickMode { pub struct ClickMode {
pub slot: Slot, pub slot: Slot,
@ -11,7 +11,7 @@ pub struct Click {
pub state_id: u32, pub state_id: u32,
pub window_type: WindowType, pub window_type: WindowType,
pub changed_items: Vec<ItemChange>, pub changed_items: Vec<ItemChange>,
pub carried_item: Option<Item>, pub carried_item: Option<ItemStack>,
} }
impl ClickMode { impl ClickMode {
@ -166,11 +166,10 @@ pub enum ClickType {
}, },
DoubleClick, DoubleClick,
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
pub enum MouseClick { pub enum MouseClick {
Left, Left,
Right, Right,
Middle,
} }
pub enum KeyClick { pub enum KeyClick {
@ -202,5 +201,5 @@ pub enum MouseDragState {
pub enum ItemChange { pub enum ItemChange {
Remove { slot: usize }, Remove { slot: usize },
Add { slot: usize, item: Item }, Add { slot: usize, item: ItemStack },
} }

View File

@ -1,11 +1,13 @@
use crate::container_click::MouseClick;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use pumpkin_world::item::ItemStack;
pub mod container_click; pub mod container_click;
pub mod player; pub mod player;
pub mod window_property; pub mod window_property;
/// https://wiki.vg/Inventory /// https://wiki.vg/Inventory
#[derive(Debug, ToPrimitive, FromPrimitive, Clone)] #[derive(Debug, ToPrimitive, FromPrimitive, Clone, Eq, PartialEq)]
pub enum WindowType { pub enum WindowType {
// not used // not used
Generic9x1, Generic9x1,
@ -55,25 +57,120 @@ impl WindowType {
} }
pub trait Container { pub trait Container {
const SLOTS: usize; fn window_type(&self) -> &WindowType;
fn item_mut(&mut self, slot: usize) -> &mut Item; fn handle_item_change(
&mut self,
carried_item: &mut Option<ItemStack>,
slot: usize,
mouse_click: MouseClick,
);
}
pub fn handle_item_take(
carried_item: &mut Option<ItemStack>,
item_slot: &mut Option<ItemStack>,
mouse_click: MouseClick,
) {
let Some(item) = item_slot else {
return;
};
let mut new_item = *item;
match mouse_click {
MouseClick::Left => {
*item_slot = None;
}
MouseClick::Right => {
let half = item.item_count / 2;
item.item_count -= half;
new_item.item_count = half;
}
}
*carried_item = Some(new_item);
}
pub fn handle_item_change(
carried_slot: &mut Option<ItemStack>,
current_slot: &mut Option<ItemStack>,
mouse_click: MouseClick,
) {
match (current_slot.as_mut(), carried_slot.as_mut()) {
// Swap or combine current and carried
(Some(current), Some(carried)) => {
if current.item_id == carried.item_id {
combine_stacks(carried_slot, current, mouse_click);
} else if mouse_click == MouseClick::Left {
let carried = *carried;
*carried_slot = Some(current.to_owned());
*current_slot = Some(carried.to_owned());
}
}
// Put held stack into empty slot
(None, Some(carried)) => match mouse_click {
MouseClick::Left => {
*current_slot = Some(carried.to_owned());
*carried_slot = None;
}
MouseClick::Right => {
carried.item_count -= 1;
let mut new = *carried;
new.item_count = 1;
*current_slot = Some(new);
}
},
// Take stack into carried
(Some(_current), None) => handle_item_take(carried_slot, current_slot, mouse_click),
(None, None) => (),
}
}
pub fn combine_stacks(
carried_slot: &mut Option<ItemStack>,
slot: &mut ItemStack,
mouse_click: MouseClick,
) {
let Some(carried_item) = carried_slot else {
return;
};
let carried_change = match mouse_click {
MouseClick::Left => carried_item.item_count,
MouseClick::Right => 1,
};
// TODO: Check for item stack max size here
if slot.item_count + carried_change <= 64 {
slot.item_count += carried_change;
carried_item.item_count -= carried_change;
if carried_item.item_count == 0 {
*carried_slot = None;
}
} else {
let left_over = slot.item_count + carried_change - 64;
slot.item_count = 64;
carried_item.item_count = left_over;
}
} }
pub struct ContainerStruct<const SLOTS: usize> { pub struct ContainerStruct<const SLOTS: usize> {
slots: [Option<Item>; SLOTS], slots: [Option<ItemStack>; SLOTS],
state_id: usize, _state_id: usize,
open_by: Option<Vec<i32>>, _open_by: Option<Vec<i32>>,
window_type: WindowType,
} }
impl<const T: usize> ContainerStruct<T> { impl<const T: usize> Container for ContainerStruct<T> {
pub fn take_item(&mut self, slot: usize) -> Item { fn window_type(&self) -> &WindowType {
assert!(slot < T); &self.window_type
let Some(item) = self.slots[slot] else { }
panic!()
}; fn handle_item_change(
self.slots[slot] = None; &mut self,
self.state_id += 1; carried_slot: &mut Option<ItemStack>,
item slot: usize,
mouse_click: MouseClick,
) {
let current_slot = &mut self.slots[slot];
handle_item_change(carried_slot, current_slot, mouse_click)
} }
} }

View File

@ -1,13 +1,16 @@
use pumpkin_world::item::Item; use crate::container_click::MouseClick;
use crate::{handle_item_change, Container, WindowType};
use pumpkin_world::item::ItemStack;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)]
pub struct PlayerInventory { pub struct PlayerInventory {
// Main Inventory + Hotbar // Main Inventory + Hotbar
crafting: [Option<Item>; 4], crafting: [Option<ItemStack>; 4],
crafting_output: Option<Item>, crafting_output: Option<ItemStack>,
items: [Option<Item>; 36], items: [Option<ItemStack>; 36],
armor: [Option<Item>; 4], armor: [Option<ItemStack>; 4],
offhand: Option<Item>, offhand: Option<ItemStack>,
// current selected slot in hotbar // current selected slot in hotbar
selected: usize, selected: usize,
} }
@ -42,7 +45,7 @@ impl PlayerInventory {
/// ## Item allowed override /// ## Item allowed override
/// An override, which when enabled, makes it so that invalid items, can be placed in slots they normally can't. /// An override, which when enabled, makes it so that invalid items, can be placed in slots they normally can't.
/// Useful functionality for plugins in the future. /// Useful functionality for plugins in the future.
pub fn set_slot(&mut self, slot: usize, item: Option<Item>, item_allowed_override: bool) { pub fn set_slot(&mut self, slot: usize, item: Option<ItemStack>, item_allowed_override: bool) {
match slot { match slot {
0 => { 0 => {
// TODO: Add crafting check here // TODO: Add crafting check here
@ -85,18 +88,30 @@ impl PlayerInventory {
_ => unreachable!(), _ => unreachable!(),
} }
} }
pub fn get_slot(&mut self, slot: usize) -> &mut Option<ItemStack> {
match slot {
0 => {
// TODO: Add crafting check here
&mut self.crafting_output
}
1..=4 => &mut self.crafting[slot - 1],
5..=8 => &mut self.armor[slot - 5],
9..=44 => &mut self.items[slot - 9],
45 => &mut self.offhand,
_ => unreachable!(),
}
}
pub fn set_selected(&mut self, slot: usize) { pub fn set_selected(&mut self, slot: usize) {
assert!((0..9).contains(&slot)); assert!((0..9).contains(&slot));
self.selected = slot; self.selected = slot;
} }
pub fn held_item(&self) -> Option<&Item> { pub fn held_item(&self) -> Option<&ItemStack> {
debug_assert!((0..9).contains(&self.selected)); debug_assert!((0..9).contains(&self.selected));
self.items[self.selected + 36 - 9].as_ref() self.items[self.selected + 36 - 9].as_ref()
} }
pub fn slots(&self) -> Vec<Option<&Item>> { pub fn slots(&self) -> Vec<Option<&ItemStack>> {
let mut slots = vec![self.crafting_output.as_ref()]; let mut slots = vec![self.crafting_output.as_ref()];
slots.extend(self.crafting.iter().map(|c| c.as_ref())); slots.extend(self.crafting.iter().map(|c| c.as_ref()));
slots.extend(self.armor.iter().map(|c| c.as_ref())); slots.extend(self.armor.iter().map(|c| c.as_ref()));
@ -104,4 +119,29 @@ impl PlayerInventory {
slots.push(self.offhand.as_ref()); slots.push(self.offhand.as_ref());
slots slots
} }
pub fn slots_mut(&mut self) -> Vec<&mut Option<ItemStack>> {
let mut slots = vec![&mut self.crafting_output];
slots.extend(self.crafting.iter_mut());
slots.extend(self.armor.iter_mut());
slots.extend(self.items.iter_mut());
slots.push(&mut self.offhand);
slots
}
}
impl Container for PlayerInventory {
fn window_type(&self) -> &WindowType {
&WindowType::Generic9x1
}
fn handle_item_change(
&mut self,
carried_slot: &mut Option<ItemStack>,
slot: usize,
mouse_click: MouseClick,
) {
let item_slot = self.get_slot(slot);
handle_item_change(carried_slot, item_slot, mouse_click)
}
} }

View File

@ -4,6 +4,7 @@ use pumpkin_macros::packet;
use serde::de::SeqAccess; use serde::de::SeqAccess;
use serde::{de, Deserialize}; use serde::{de, Deserialize};
#[derive(Debug)]
#[packet(0x0E)] #[packet(0x0E)]
pub struct SClickContainer { pub struct SClickContainer {
pub window_id: u8, pub window_id: u8,
@ -52,12 +53,17 @@ impl<'de> Deserialize<'de> for SClickContainer {
let length_of_array = seq let length_of_array = seq
.next_element::<VarInt>()? .next_element::<VarInt>()?
.ok_or(de::Error::custom("Failed to decode VarInt"))?; .ok_or(de::Error::custom("Failed to decode VarInt"))?;
let array_of_changed_slots = if length_of_array.0 != 0 { let mut array_of_changed_slots = vec![];
seq.next_element::<Vec<(i16, Slot)>>()? for _ in 0..length_of_array.0 {
.ok_or(de::Error::custom("Unable to parse changed slots list"))? let slot_number = seq
} else { .next_element::<i16>()?
vec![] .ok_or(de::Error::custom("Unable to parse slot"))?;
}; let slot = seq
.next_element::<Slot>()?
.ok_or(de::Error::custom("Unable to parse item"))?;
array_of_changed_slots.push((slot_number, slot));
}
let carried_item = seq let carried_item = seq
.next_element::<Slot>()? .next_element::<Slot>()?
.ok_or(de::Error::custom("Failed to decode carried item"))?; .ok_or(de::Error::custom("Failed to decode carried item"))?;

View File

@ -1,5 +1,5 @@
use crate::VarInt; use crate::VarInt;
use pumpkin_world::item::Item; use pumpkin_world::item::ItemStack;
use serde::ser::SerializeSeq; use serde::ser::SerializeSeq;
use serde::{ use serde::{
de::{self, SeqAccess}, de::{self, SeqAccess},
@ -130,9 +130,9 @@ impl Serialize for Slot {
} }
impl Slot { impl Slot {
pub fn to_item(self) -> Option<Item> { pub fn to_item(self) -> Option<ItemStack> {
let item_id = self.item_id?.0.try_into().unwrap(); let item_id = self.item_id?.0.try_into().unwrap();
Some(Item { Some(ItemStack {
item_id, item_id,
item_count: self.item_count.0.try_into().unwrap(), item_count: self.item_count.0.try_into().unwrap(),
}) })
@ -150,8 +150,8 @@ impl Slot {
} }
} }
impl From<&Item> for Slot { impl From<&ItemStack> for Slot {
fn from(item: &Item) -> Self { fn from(item: &ItemStack) -> Self {
Slot { Slot {
item_count: item.item_count.into(), item_count: item.item_count.into(),
item_id: Some(item.item_id.into()), item_id: Some(item.item_id.into()),
@ -164,8 +164,8 @@ impl From<&Item> for Slot {
} }
} }
impl From<Option<&Item>> for Slot { impl From<Option<&ItemStack>> for Slot {
fn from(item: Option<&Item>) -> Self { fn from(item: Option<&ItemStack>) -> Self {
item.map(Slot::from).unwrap_or(Slot::empty()) item.map(Slot::from).unwrap_or(Slot::empty())
} }
} }

View File

@ -1,6 +1,6 @@
use crate::item::Item; use crate::item::ItemStack;
impl Item { impl ItemStack {
pub fn is_helmet(&self) -> bool { pub fn is_helmet(&self) -> bool {
[ [
// Leather // Leather

View File

@ -11,9 +11,9 @@ pub enum Rarity {
Epic, Epic,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub struct Item { pub struct ItemStack {
pub item_count: u32, pub item_count: u8,
// This ID is the numerical protocol ID, not the usual minecraft::block ID. // This ID is the numerical protocol ID, not the usual minecraft::block ID.
pub item_id: u32, pub item_id: u32,
// TODO: Add Item Components // TODO: Add Item Components

View File

@ -1,17 +1,17 @@
use num_traits::FromPrimitive;
use pumpkin_core::text::TextComponent; use pumpkin_core::text::TextComponent;
use pumpkin_inventory::container_click;
use pumpkin_inventory::container_click::MouseClick; use pumpkin_inventory::container_click::MouseClick;
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::WindowType; use pumpkin_inventory::{Container, WindowType};
use pumpkin_inventory::{container_click, ContainerStruct};
use pumpkin_protocol::client::play::{ use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
}; };
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::Item; use pumpkin_world::item::ItemStack;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server;
impl Player { impl Player {
pub fn open_container( pub fn open_container(
@ -19,8 +19,8 @@ impl Player {
window_type: WindowType, window_type: WindowType,
minecraft_menu_id: &str, minecraft_menu_id: &str,
window_title: Option<&str>, window_title: Option<&str>,
items: Option<Vec<Option<&Item>>>, items: Option<Vec<Option<&ItemStack>>>,
carried_item: Option<&Item>, carried_item: Option<&ItemStack>,
) { ) {
let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
.get("minecraft:menu") .get("minecraft:menu")
@ -43,8 +43,8 @@ impl Player {
pub fn set_container_content<'a>( pub fn set_container_content<'a>(
&mut self, &mut self,
window_type: WindowType, window_type: WindowType,
items: Option<Vec<Option<&'a Item>>>, items: Option<Vec<Option<&'a ItemStack>>>,
carried_item: Option<&'a Item>, carried_item: Option<&'a ItemStack>,
) { ) {
let slots: Vec<Slot> = { let slots: Vec<Slot> = {
if let Some(mut items) = items { if let Some(mut items) = items {
@ -80,7 +80,7 @@ impl Player {
&mut self, &mut self,
window_type: WindowType, window_type: WindowType,
slot: usize, slot: usize,
item: Option<&Item>, item: Option<&ItemStack>,
) { ) {
self.client.send_packet(&CSetContainerSlot::new( self.client.send_packet(&CSetContainerSlot::new(
window_type as i8, window_type as i8,
@ -136,13 +136,45 @@ impl Player {
}; };
match click.mode.click_type { match click.mode.click_type {
ClickType::MouseClick(mouse_click) => self.mouse_click(mouse_click), ClickType::MouseClick(mouse_click) => {
self.mouse_click(mouse_click, click.window_type, click.mode.slot)
}
_ => todo!(), _ => todo!(),
} }
} }
pub fn mouse_click(&mut self, mouse_click: MouseClick) { pub fn mouse_click(
dbg!(mouse_click); &mut self,
mouse_click: MouseClick,
window_type: WindowType,
slot: container_click::Slot,
) {
let Some(_) = &self.open_container else {
// Inventory
if window_type == WindowType::Generic9x1 {
match slot {
container_click::Slot::Normal(slot) => {
self.inventory
.handle_item_change(&mut self.carried_item, slot, mouse_click)
}
container_click::Slot::OutsideInventory => (),
};
dbg!(&self.carried_item);
let filled_inventory_slots = self
.inventory
.slots()
.into_iter()
.enumerate()
.filter_map(|(slot, item)| item.map(|item| (slot, item.item_count)))
.collect::<Vec<_>>();
dbg!(filled_inventory_slots);
return;
} else {
return;
}
};
} }
} }

View File

@ -1,10 +1,12 @@
use std::str::FromStr; use std::str::FromStr;
use crate::{client::Client, server::Server};
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use pumpkin_core::text::TextComponent; use pumpkin_core::text::TextComponent;
use pumpkin_entity::{entity_type::EntityType, Entity, EntityId}; use pumpkin_entity::{entity_type::EntityType, Entity, EntityId};
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
use pumpkin_inventory::Container;
use pumpkin_protocol::{ use pumpkin_protocol::{
bytebuf::packet_id::Packet, bytebuf::packet_id::Packet,
client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage}, client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage},
@ -16,11 +18,10 @@ use pumpkin_protocol::{
}, },
ConnectionState, RawPacket, ServerPacket, VarInt, ConnectionState, RawPacket, ServerPacket, VarInt,
}; };
use pumpkin_world::item::ItemStack;
use pumpkin_world::vector3::Vector3; use pumpkin_world::vector3::Vector3;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{client::Client, server::Server};
pub struct Player { pub struct Player {
pub client: Client, pub client: Client,
pub entity: Entity, pub entity: Entity,
@ -31,8 +32,8 @@ pub struct Player {
pub food: i32, pub food: i32,
pub food_saturation: f32, pub food_saturation: f32,
pub inventory: PlayerInventory, pub inventory: PlayerInventory,
pub open_container: Option<()>, pub open_container: Option<Box<dyn Container>>,
pub carried_item: Option<ItemStack>,
// Client side value, Should be not trusted // Client side value, Should be not trusted
pub on_ground: bool, pub on_ground: bool,
@ -66,6 +67,7 @@ impl Player {
velocity: Vector3::new(0.0, 0.0, 0.0), velocity: Vector3::new(0.0, 0.0, 0.0),
inventory: PlayerInventory::new(), inventory: PlayerInventory::new(),
open_container: None, open_container: None,
carried_item: None,
gamemode, gamemode,
} }
} }