diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 3108b23f..68f4128c 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -3,13 +3,27 @@ use pumpkin_world::item::Item; #[allow(dead_code)] pub struct PlayerInventory { // Main Inventory + Hotbar + crafting: [Option; 4], + crafting_output: Option, items: [Option; 36], armor: [Option; 4], offhand: Option, - // current selected slot in hortbar - selected: i16, + // current selected slot in hotbar + selected: usize, } +pub struct Hotbar<'a>(&'a mut [Option;9]); + +impl Hotbar<'_> { + fn get_mut(&mut self, index: usize) -> &mut Option { + &mut self.0[index] + } +} + +pub struct Armor<'a>(&'a mut [Option; 4]); + + + impl Default for PlayerInventory { fn default() -> Self { Self::new() @@ -19,6 +33,8 @@ impl Default for PlayerInventory { impl PlayerInventory { pub fn new() -> Self { Self { + crafting: [None; 4], + crafting_output: None, items: [None; 36], armor: [None; 4], offhand: None, @@ -26,11 +42,73 @@ impl PlayerInventory { selected: 0, } } + + /// Set the contents of an item in a slot + /// + /// ## Slot + /// The slot according to https://wiki.vg/Inventory#Player_Inventory + /// + /// ## Item + /// The optional item to place in the slot + /// + /// ## Item allowed override + /// 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. + pub fn set_slot(&mut self, slot: usize, item: Option, item_allowed_override: bool) { + match slot { + 0 => { + // TODO: Add crafting check here + self.crafting_output = item + } + 1..=4 => { + self.crafting[slot-1] = item + } + 5..=8 => { + match item { + None => { + self.armor[slot-4] = None + }, + Some(item) => { + // TODO: Replace asserts with error handling + match slot-5 { + 0 => { + assert!(item.is_helmet() || item_allowed_override); + self.armor[0] = Some(item); + } + 1 => { + assert!(item.is_chestplate() || item_allowed_override); + self.armor[1] = Some(item) + } + 2 => { + assert!(item.is_leggings() || item_allowed_override); + self.armor[2] = Some(item); + } + 3 => { + assert!(item.is_boots() || item_allowed_override); + self.armor[3] = Some(item) + } + _ => unreachable!() + } + } + } + } + 9..=44 => { + self.items[slot-9] = item; + } + 45 => { + self.offhand = item; + } + _ => unreachable!() + } + } - pub fn set_slot(_slot: u32, _item: Item) {} - - pub fn set_selected(&mut self, slot: i16) { + pub fn set_selected(&mut self, slot: usize) { assert!((0..9).contains(&slot)); self.selected = slot; } + + pub fn held_item(&self) -> Option<&Item> { + debug_assert!((0..9).contains(&self.selected)); + self.items[self.selected+36-9].as_ref() + } } diff --git a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs index d1a2fbe5..8f1c7a52 100644 --- a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs +++ b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs @@ -6,6 +6,6 @@ use crate::slot::Slot; #[allow(dead_code)] #[packet(0x32)] pub struct SSetCreativeSlot { - slot: i16, - clicked_item: Slot, + pub slot: i16, + pub clicked_item: Slot, } diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/slot.rs index ffefbc03..04ac9fe3 100644 --- a/pumpkin-protocol/src/slot.rs +++ b/pumpkin-protocol/src/slot.rs @@ -2,7 +2,7 @@ use serde::{ de::{self, SeqAccess, Visitor}, Deserialize, }; - +use pumpkin_world::item::Item; use crate::VarInt; #[derive(Debug, Clone)] @@ -16,6 +16,9 @@ pub struct Slot { components_to_remove: Option>, } + + + impl<'de> Deserialize<'de> for Slot { fn deserialize(deserializer: D) -> Result where @@ -75,3 +78,20 @@ impl<'de> Deserialize<'de> for Slot { deserializer.deserialize_seq(VarIntVisitor) } } +impl Slot { + pub fn to_item(self) -> Option { + let item_id = self.item_id?.0.try_into().unwrap(); + Some(Item { + item_id, + item_count: self.item_count.0.try_into().unwrap(), + }) + } +} +impl From for Item { + fn from(slot: Slot) -> Self { + Item { + item_count: slot.item_count.0.try_into().unwrap(), + item_id: slot.item_id.unwrap().0.try_into().unwrap() + } + } +} \ No newline at end of file diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index 13a423de..fa2b790b 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -7,28 +7,28 @@ use crate::level::WorldError; const BLOCKS_JSON: &str = include_str!("../../assets/blocks.json"); #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] -struct BlockDefinition { +pub struct BlockDefinition { #[serde(rename = "type")] kind: String, block_set_type: Option, } #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] -struct BlockState { +pub struct BlockState { default: Option, id: i64, properties: Option>, } #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] -struct BlocksElement { +pub struct BlocksElement { definition: BlockDefinition, properties: Option>>, states: Vec, } lazy_static! { - static ref BLOCKS: HashMap = + pub static ref BLOCKS: HashMap = serde_json::from_str(BLOCKS_JSON).expect("Could not parse block.json registry."); } diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 6840d84d..1009f9fe 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -3,7 +3,7 @@ use num_derive::FromPrimitive; use crate::vector3::Vector3; pub mod block_registry; - +pub use block_registry::BLOCKS; #[derive(FromPrimitive)] pub enum BlockFace { Bottom = 0, diff --git a/pumpkin-world/src/global_registry.rs b/pumpkin-world/src/global_registry.rs index 149e03b8..fd55c358 100644 --- a/pumpkin-world/src/global_registry.rs +++ b/pumpkin-world/src/global_registry.rs @@ -9,12 +9,12 @@ const REGISTRY_JSON: &str = include_str!("../assets/registries.json"); #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] pub struct RegistryElement { default: Option, - entries: HashMap, + pub entries: HashMap>, } lazy_static! { - static ref REGISTRY: HashMap = - serde_json::from_str(REGISTRY_JSON).expect("Could not parse items.json registry."); + pub static ref REGISTRY: HashMap = + serde_json::from_str(REGISTRY_JSON).expect("Could not parse registry.json registry."); } pub fn get_protocol_id(category: &str, entry: &str) -> u32 { @@ -23,6 +23,7 @@ pub fn get_protocol_id(category: &str, entry: &str) -> u32 { .expect("Invalid Category in registry") .entries .get(entry) + .map(|p|p.get("protocol_id").unwrap()) .expect("No Entry found") } @@ -34,3 +35,11 @@ pub fn get_default<'a>(category: &str) -> Option<&'a str> { .default .as_deref() } + +pub fn find_minecraft_id(category: &str, protocol_id: u32) -> Option<&str> { + REGISTRY.get(category)? + .entries + .iter() + .find(|(_,other_protocol_id)|*other_protocol_id.get("protocol_id").unwrap()==protocol_id) + .map(|(id,_)|id.as_str()) +} diff --git a/pumpkin-world/src/item/item_categories.rs b/pumpkin-world/src/item/item_categories.rs new file mode 100644 index 00000000..dfd032e3 --- /dev/null +++ b/pumpkin-world/src/item/item_categories.rs @@ -0,0 +1,75 @@ +use crate::item::Item; + +impl Item { + pub fn is_helmet(&self) -> bool { + [ + // Leather + 856, + // Netherite + 876, + // Turtle helmet + 794, + // Chainmail + 860, + // Diamond + 868, + // Gold + 872, + // Iron + 864 + ].contains(&self.item_id) + } + + pub fn is_chestplate(&self) -> bool { + [ + // Leather + 857, + // Netherite + 877, + // Chainmail + 861, + // Diamond + 869, + // Gold + 873, + // Iron + 865, + // Elytra + 773, + ].contains(&self.item_id) + } + + pub fn is_leggings(&self) -> bool { + [ + // Leather + 858, + // Netherite + 878, + // Chainmail + 862, + // Diamond + 870, + // Gold + 874, + // Iron + 866 + ].contains(&self.item_id) + } + + pub fn is_boots(&self) -> bool { + [ + // Leather + 859, + // Netherite + 879, + // Chainmail + 863, + // Diamond + 871, + // Gold + 875, + // Iron + 867 + ].contains(&self.item_id) + } +} \ No newline at end of file diff --git a/pumpkin-world/src/item/item_registry.rs b/pumpkin-world/src/item/item_registry.rs index 0fa923fc..f391cea9 100644 --- a/pumpkin-world/src/item/item_registry.rs +++ b/pumpkin-world/src/item/item_registry.rs @@ -4,7 +4,7 @@ use lazy_static::lazy_static; use crate::global_registry::{self, ITEM_REGISTRY}; -use super::Raritiy; +use super::Rarity; const ITEMS_JSON: &str = include_str!("../../assets/items.json"); @@ -17,18 +17,18 @@ pub struct ItemComponents { #[serde(rename = "minecraft:max_stack_size")] max_stack_size: u32, #[serde(rename = "minecraft:rarity")] - rarity: Raritiy, + rarity: Rarity, #[serde(rename = "minecraft:repair_cost")] repair_cost: u32, } #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] -struct ItemElement { +pub struct ItemElement { components: ItemComponents, } lazy_static! { - static ref ITEMS: HashMap = + pub static ref ITEMS: HashMap = serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry."); } diff --git a/pumpkin-world/src/item/mod.rs b/pumpkin-world/src/item/mod.rs index ef9ec114..0fa843d5 100644 --- a/pumpkin-world/src/item/mod.rs +++ b/pumpkin-world/src/item/mod.rs @@ -1,9 +1,10 @@ mod item_registry; - +mod item_categories; +pub use item_registry::ITEMS; #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "lowercase")] -/// Item Raritiy -pub enum Raritiy { +/// Item Rarity +pub enum Rarity { Common, UnCommon, Rare, @@ -11,4 +12,9 @@ pub enum Raritiy { } #[derive(Clone, Copy)] -pub struct Item {} +pub struct Item { + pub item_count: u32, + // This ID is the numerical protocol ID, not the usual minecraft::block ID. + pub item_id: u32, + // TODO: Add Item Components +} diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index df3a2b28..143942f5 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -6,7 +6,7 @@ pub const WORLD_HEIGHT: usize = 384; pub const WORLD_Y_START_AT: i32 = -64; pub const DIRECT_PALETTE_BITS: u32 = 15; pub mod block; -mod global_registry; +pub mod global_registry; pub mod item; mod level; pub mod radial_chunk_iterator; diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index f29d21d5..66482ee0 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -16,7 +16,7 @@ use pumpkin_protocol::{ }; use pumpkin_text::TextComponent; use pumpkin_world::block::BlockFace; - +use pumpkin_world::global_registry; use crate::{ commands::{handle_command, CommandSender}, entity::player::{ChatMode, GameMode, Hand}, @@ -317,11 +317,16 @@ impl Client { } pub fn handle_player_action(&mut self, _server: &mut Server, _player_action: SPlayerAction) {} - pub fn handle_use_item_on(&mut self, server: &mut Server, use_item_on: SUseItemOn) { + pub fn handle_use_item_on(&mut self, server: &mut Server, use_item_on: SUseItemOn) { let location = use_item_on.location; let face = BlockFace::from_i32(use_item_on.face.0).unwrap(); let location = WorldPosition(location.0 + face.to_offset()); - server.broadcast_packet(self, &CBlockUpdate::new(location, 11.into())); + if let Some(item) = self.player.as_ref().unwrap().inventory.held_item() { + let minecraft_id = global_registry::find_minecraft_id(global_registry::ITEM_REGISTRY,item.item_id).expect("All item ids are in the global registry"); + if let Ok(block_state_id) = pumpkin_world::block::block_registry::block_id_and_properties_to_block_state_id(minecraft_id,None) { + server.broadcast_packet(self, &CBlockUpdate::new(location, (block_state_id as i32).into())); + } + } } pub fn handle_set_held_item(&mut self, _server: &mut Server, held: SSetHeldItem) { @@ -330,11 +335,14 @@ impl Client { self.kick("Invalid held slot") } let player = self.player.as_mut().unwrap(); - player.inventory.set_selected(slot); + player.inventory.set_selected(slot as usize); } pub fn handle_set_creative_slot(&mut self, _server: &mut Server, packet: SSetCreativeSlot) { - // TODO: handle this - dbg!(&packet); + let inventory = &mut self.player.as_mut() + .unwrap() + .inventory; + + inventory.set_slot(packet.slot as usize, packet.clicked_item.to_item(), false); } }