From d68bd47035bdea8027b47e9445da3a6ce3d1074a Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Mon, 9 Dec 2024 00:26:42 -0800 Subject: [PATCH 1/5] Corrected interactive block functionality --- pumpkin-core/src/math/position.rs | 2 +- pumpkin-inventory/src/lib.rs | 7 ++ pumpkin-inventory/src/open_container.rs | 16 +++- pumpkin/src/block/block_manager.rs | 18 +++- pumpkin/src/block/blocks/chest.rs | 81 ++++++++++++---- pumpkin/src/block/blocks/crafting_table.rs | 104 ++++++++++++++++++--- pumpkin/src/block/blocks/furnace.rs | 72 ++++++++++---- pumpkin/src/block/blocks/jukebox.rs | 18 +++- pumpkin/src/block/pumpkin_block.rs | 40 +++++++- pumpkin/src/net/packet/play.rs | 16 ++-- pumpkin/src/server/mod.rs | 31 ++++++ 11 files changed, 337 insertions(+), 68 deletions(-) diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index 839890646..a3b82cbeb 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -5,7 +5,7 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] /// Aka Block Position pub struct WorldPosition(pub Vector3); diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index bf56dca63..f1273b054 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -94,6 +94,13 @@ pub trait Container: Sync + Send { fn all_slots_ref(&self) -> Vec>; + fn clear_all_slots(&mut self) { + let all_slots = self.all_slots(); + for stack in all_slots { + *stack = None; + } + } + fn all_combinable_slots(&self) -> Vec> { self.all_slots_ref() } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index ec68578fb..03727016d 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -6,7 +6,6 @@ use pumpkin_world::item::ItemStack; use std::sync::Arc; use tokio::sync::Mutex; pub struct OpenContainer { - // TODO: unique id should be here players: Vec, container: Arc>>, location: Option, @@ -54,6 +53,21 @@ impl OpenContainer { } } + pub fn is_location(&self, try_position: WorldPosition) -> bool { + if let Some(location) = self.location { + location == try_position + } else { + false + } + } + + pub async fn on_destroy(&self) { + let mut container = self.container.lock().await; + + container.clear_all_slots(); + // TODO drop all items by default + } + pub fn all_player_ids(&self) -> Vec { self.players.clone() } diff --git a/pumpkin/src/block/block_manager.rs b/pumpkin/src/block/block_manager.rs index 92395bb81..0fb26ede3 100644 --- a/pumpkin/src/block/block_manager.rs +++ b/pumpkin/src/block/block_manager.rs @@ -2,6 +2,7 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::entity::player::Player; use crate::server::Server; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use std::collections::HashMap; @@ -33,7 +34,7 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_use(player, location, server).await; + pumpkin_block.on_use(block, player, location, server).await; } } @@ -48,7 +49,7 @@ impl BlockManager { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { return pumpkin_block - .on_use_with_item(player, location, item, server) + .on_use_with_item(block, player, location, item, server) .await; } BlockActionResult::Continue @@ -63,7 +64,9 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_placed(player, location, server).await; + pumpkin_block + .on_placed(block, player, location, server) + .await; } } @@ -76,7 +79,9 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_broken(player, location, server).await; + pumpkin_block + .on_broken(block, player, location, server) + .await; } } @@ -86,10 +91,13 @@ impl BlockManager { player: &Player, location: WorldPosition, server: &Server, + container: &mut OpenContainer, ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_close(player, location, server).await; + pumpkin_block + .on_close(block, player, location, server, container) + .await; } } diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 1600bb096..2d334eb2d 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{Chest, OpenContainer, WindowType}; use pumpkin_macros::{pumpkin_block, sound}; -use pumpkin_world::{block::block_registry::get_block, item::item_registry::Item}; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; use crate::{ block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, @@ -15,8 +15,15 @@ pub struct ChestBlock; #[async_trait] impl PumpkinBlock for ChestBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_chest_block(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_chest_block(block, player, _location, server) + .await; player .world() .play_block_sound(sound!("block.chest.open"), _location) @@ -25,16 +32,47 @@ impl PumpkinBlock for ChestBlock { async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_chest_block(player, _location, server).await; + self.open_chest_block(block, player, _location, server) + .await; BlockActionResult::Consume } - async fn on_close<'a>(&self, player: &Player, _location: WorldPosition, _server: &Server) { + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + // TODO: drop all items and close screen if different player breaks block + let entity_id = player.entity_id(); + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good ct ID: {}", container_id); + + container.on_destroy().await; + + container.remove_player(entity_id); + player.open_container.store(None); + } + } + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { player .world() .play_block_sound(sound!("block.chest.close"), _location) @@ -45,25 +83,34 @@ impl PumpkinBlock for ChestBlock { impl ChestBlock { pub async fn open_chest_block( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { let entity_id = player.entity_id(); - // TODO: This should be a unique identifier for the each block container - player.open_container.store(Some(2)); - { + if let Some(container_id) = server.get_container_id(location, block.clone()).await { let mut open_containers = server.open_containers.write().await; - if let Some(chest) = open_containers.get_mut(&2) { - chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:chest").cloned(), - ); - open_containers.insert(2, open_container); + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good chest ID: {}", container_id); + container.add_player(entity_id); + player.open_container.store(Some(container_id.into())); } + // drop(open_containers); + } else { + let mut open_containers = server.open_containers.write().await; + + let new_id = server.new_container_id(); + log::info!("New chest ID: {}", new_id); + + let open_container = OpenContainer::new_empty_container::( + entity_id, + Some(location), + Some(block.clone()), + ); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + // drop(open_containers); } player.open_container(server, WindowType::Generic9x3).await; } diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 962d56626..0c53ad580 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -6,52 +6,126 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType}; use pumpkin_macros::pumpkin_block; -use pumpkin_world::{block::block_registry::get_block, item::item_registry::Item}; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; #[pumpkin_block("minecraft:crafting_table")] pub struct CraftingTableBlock; #[async_trait] impl PumpkinBlock for CraftingTableBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_crafting_screen(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_crafting_screen(block, player, _location, server) + .await; } async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_crafting_screen(player, _location, server).await; + self.open_crafting_screen(block, player, _location, server) + .await; BlockActionResult::Consume } + + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + // TODO: drop all items and close screen if different player breaks block + let entity_id = player.entity_id(); + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + if let Some(ender_chest) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good ct ID: {}", container_id); + + ender_chest.on_destroy().await; + + ender_chest.remove_player(entity_id); + player.open_container.store(None); + } + } + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + container: &OpenContainer, + ) { + log::info!("On Close CT"); + let entity_id = player.entity_id(); + + for player_id in container.all_player_ids() { + if entity_id == player_id { + container.on_destroy().await; + } + } + + // TODO: should re-add all items to player or drop? + } } impl CraftingTableBlock { pub async fn open_crafting_screen( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { //TODO: Adjust /craft command to real crafting table let entity_id = player.entity_id(); - player.open_container.store(Some(1)); - { - let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&1) { + let mut open_containers = server.open_containers.write().await; + let mut id_to_use = -1; + + for (id, container) in open_containers.iter() { + if let Some(a_block) = container.get_block() { + if a_block.id == block.id && container.all_player_ids().is_empty() { + id_to_use = *id as i64; + } + } + } + + if id_to_use == -1 { + let new_id = server.new_container_id(); + + log::info!("New ct ID: {}", new_id); + + let open_container = OpenContainer::new_empty_container::( + entity_id, + Some(location), + Some(block.clone()), + ); + + open_containers.insert(new_id.into(), open_container); + + player.open_container.store(Some(new_id.into())); + // drop(open_containers); + } else { + log::info!("Using previous ct ID: {}", id_to_use); + if let Some(ender_chest) = open_containers.get_mut(&(id_to_use as u64)) { ender_chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:crafting_table").cloned(), - ); - open_containers.insert(1, open_container); + player + .open_container + .store(Some(id_to_use.try_into().unwrap())); } } + drop(open_containers); player .open_container(server, WindowType::CraftingTable) .await; diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index 8fc479492..c00a5feb2 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -5,7 +5,7 @@ use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::Furnace; use pumpkin_inventory::{OpenContainer, WindowType}; use pumpkin_macros::pumpkin_block; -use pumpkin_world::block::block_registry::get_block; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use crate::{block::pumpkin_block::PumpkinBlock, server::Server}; @@ -15,44 +15,84 @@ pub struct FurnaceBlock; #[async_trait] impl PumpkinBlock for FurnaceBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_furnace_screen(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_furnace_screen(block, player, _location, server) + .await; } async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_furnace_screen(player, _location, server).await; + self.open_furnace_screen(block, player, _location, server) + .await; BlockActionResult::Consume } + + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + // TODO: drop all items and close screen if different player breaks block + let entity_id = player.entity_id(); + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good ct ID: {}", container_id); + + container.on_destroy().await; + + container.remove_player(entity_id); + player.open_container.store(None); + } + } + } } impl FurnaceBlock { pub async fn open_furnace_screen( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - //TODO: Adjust /craft command to real crafting table let entity_id = player.entity_id(); - player.open_container.store(Some(3)); - { + if let Some(container_id) = server.get_container_id(location, block.clone()).await { let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&3) { - ender_chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:furnace").cloned(), - ); - open_containers.insert(3, open_container); + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good furnance ID: {}", container_id); + container.add_player(entity_id); + player.open_container.store(Some(container_id.into())); } + // drop(open_containers); + } else { + let mut open_containers = server.open_containers.write().await; + + let new_id = server.new_container_id(); + log::info!("New furnace ID: {}", new_id); + + let open_container = OpenContainer::new_empty_container::( + entity_id, + Some(location), + Some(block.clone()), + ); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + // drop(open_containers); } player.open_container(server, WindowType::Furnace).await; } diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 3c52395ba..4836c4be7 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::pumpkin_block; use pumpkin_registry::SYNCED_REGISTRIES; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; #[pumpkin_block("minecraft:jukebox")] @@ -13,7 +14,13 @@ pub struct JukeboxBlock; #[async_trait] impl PumpkinBlock for JukeboxBlock { - async fn on_use<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_use<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; @@ -22,6 +29,7 @@ impl PumpkinBlock for JukeboxBlock { async fn on_use_with_item<'a>( &self, + _block: &Block, player: &Player, location: WorldPosition, item: &Item, @@ -49,7 +57,13 @@ impl PumpkinBlock for JukeboxBlock { BlockActionResult::Consume } - async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_broken<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index 1849ea5a5..c0a65c316 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -3,6 +3,8 @@ use crate::entity::player::Player; use crate::server::Server; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; pub trait BlockMetadata { @@ -15,9 +17,17 @@ pub trait BlockMetadata { #[async_trait] pub trait PumpkinBlock: Send + Sync { - async fn on_use<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_use<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } async fn on_use_with_item<'a>( &self, + _block: &Block, _player: &Player, _location: WorldPosition, _item: &Item, @@ -26,9 +36,31 @@ pub trait PumpkinBlock: Send + Sync { BlockActionResult::Continue } - async fn on_placed<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_placed<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } - async fn on_broken<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_broken<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } - async fn on_close<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_close<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { + } } diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index ec01ee04e..baf904659 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -18,7 +18,7 @@ use pumpkin_core::{ text::TextComponent, GameMode, }; -use pumpkin_inventory::{InventoryError, WindowType}; +use pumpkin_inventory::InventoryError; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse; use pumpkin_protocol::{ @@ -838,11 +838,13 @@ impl Player { // TODO: // This function will in the future be used to keep track of if the client is in a valid state. // But this is not possible yet - pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) { - let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { - self.kick(TextComponent::text("Invalid window ID")).await; - return; - }; + pub async fn handle_close_container(&self, server: &Server, _packet: SCloseContainer) { + // TODO: This should check if player sent this packet before + // let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { + // log::info!("Closed ID: {}", packet.window_id.0); + // self.kick(TextComponent::text("Invalid window ID")).await; + // return; + // }; // window_id 0 represents both 9x1 Generic AND inventory here let mut inventory = self.inventory().lock().await; @@ -856,7 +858,7 @@ impl Player { if let Some(block) = container.get_block() { server .block_manager - .on_close(&block, self, pos, server) //block, self, location, server) + .on_close(&block, self, pos, server, container) //block, self, location, server) .await; } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index cb5c57b13..dac6c87f4 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,6 +1,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -9,9 +10,11 @@ use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::{DimensionType, Registry}; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::dimension::Dimension; use rand::prelude::SliceRandom; use std::collections::HashMap; +use std::sync::atomic::AtomicU32; use std::{ sync::{ atomic::{AtomicI32, Ordering}, @@ -61,6 +64,8 @@ pub struct Server { pub drag_handler: DragHandler, /// Assigns unique IDs to entities. entity_id: AtomicI32, + /// Assigns unique IDs to containers. + container_id: AtomicU32, /// Manages authentication with a authentication server, if enabled. pub auth_client: Option, /// The server's custom bossbars @@ -102,6 +107,7 @@ impl Server { drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), + container_id: 0.into(), worlds: vec![Arc::new(world)], dimensions: vec![ DimensionType::Overworld, @@ -192,6 +198,26 @@ impl Server { .cloned() } + pub async fn get_container_id(&self, location: WorldPosition, block: Block) -> Option { + let open_containers = self.open_containers.read().await; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found container id: {}", id); + return Some(*id as u32); + } + } + } + } + log::debug!("No container found... this should not happen."); + + drop(open_containers); + + None + } + /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. @@ -303,6 +329,11 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + /// Generates a new container id + pub fn new_container_id(&self) -> u32 { + self.container_id.fetch_add(1, Ordering::SeqCst) + } + pub fn get_branding(&self) -> CPluginMessage<'_> { self.server_branding.get_branding() } From 5e243eedaf9711a14a4e4395cde50d850eb1f35e Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:36:11 -0800 Subject: [PATCH 2/5] Added standard functions and player remove on destroy --- pumpkin-inventory/src/open_container.rs | 10 ++-- pumpkin/src/block/blocks/chest.rs | 50 ++++------------ pumpkin/src/block/blocks/crafting_table.rs | 18 +----- pumpkin/src/block/blocks/furnace.rs | 49 ++++------------ pumpkin/src/block/blocks/mod.rs | 68 ++++++++++++++++++++++ pumpkin/src/net/container.rs | 1 + 6 files changed, 98 insertions(+), 98 deletions(-) diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 03727016d..2e2b4d10a 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -5,7 +5,10 @@ use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::ItemStack; use std::sync::Arc; use tokio::sync::Mutex; + pub struct OpenContainer { + // TODO: unique id should be here + // TODO: should this be uuid? players: Vec, container: Arc>>, location: Option, @@ -61,11 +64,8 @@ impl OpenContainer { } } - pub async fn on_destroy(&self) { - let mut container = self.container.lock().await; - - container.clear_all_slots(); - // TODO drop all items by default + pub async fn clear_all_slots(&self) { + self.container.lock().await.clear_all_slots(); } pub fn all_player_ids(&self) -> Vec { diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 2d334eb2d..123b91bc5 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -50,19 +50,7 @@ impl PumpkinBlock for ChestBlock { location: WorldPosition, server: &Server, ) { - // TODO: drop all items and close screen if different player breaks block - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good ct ID: {}", container_id); - - container.on_destroy().await; - - container.remove_player(entity_id); - player.open_container.store(None); - } - } + super::standard_on_destroy_with_container(block, player, location, server).await; } async fn on_close<'a>( @@ -77,6 +65,7 @@ impl PumpkinBlock for ChestBlock { .world() .play_block_sound(sound!("block.chest.close"), _location) .await; + // TODO: send entity updates close } } @@ -88,30 +77,15 @@ impl ChestBlock { location: WorldPosition, server: &Server, ) { - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good chest ID: {}", container_id); - container.add_player(entity_id); - player.open_container.store(Some(container_id.into())); - } - // drop(open_containers); - } else { - let mut open_containers = server.open_containers.write().await; - - let new_id = server.new_container_id(); - log::info!("New chest ID: {}", new_id); - - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - Some(block.clone()), - ); - open_containers.insert(new_id.into(), open_container); - player.open_container.store(Some(new_id.into())); - // drop(open_containers); - } - player.open_container(server, WindowType::Generic9x3).await; + // TODO: shouldn't Chest and window type be constrained together to avoid errors? + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Generic9x3, + ) + .await; + // TODO: send entity updates open } } diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 0c53ad580..eeab10514 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -44,19 +44,7 @@ impl PumpkinBlock for CraftingTableBlock { location: WorldPosition, server: &Server, ) { - // TODO: drop all items and close screen if different player breaks block - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good ct ID: {}", container_id); - - ender_chest.on_destroy().await; - - ender_chest.remove_player(entity_id); - player.open_container.store(None); - } - } + super::standard_on_destroy_with_container(block, player, location, server).await; } async fn on_close<'a>( @@ -67,12 +55,11 @@ impl PumpkinBlock for CraftingTableBlock { _server: &Server, container: &OpenContainer, ) { - log::info!("On Close CT"); let entity_id = player.entity_id(); for player_id in container.all_player_ids() { if entity_id == player_id { - container.on_destroy().await; + container.clear_all_slots().await; } } @@ -115,7 +102,6 @@ impl CraftingTableBlock { open_containers.insert(new_id.into(), open_container); player.open_container.store(Some(new_id.into())); - // drop(open_containers); } else { log::info!("Using previous ct ID: {}", id_to_use); if let Some(ender_chest) = open_containers.get_mut(&(id_to_use as u64)) { diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index c00a5feb2..1f84e8a02 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -3,7 +3,7 @@ use crate::entity::player::Player; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::Furnace; -use pumpkin_inventory::{OpenContainer, WindowType}; +use pumpkin_inventory::WindowType; use pumpkin_macros::pumpkin_block; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; @@ -46,19 +46,7 @@ impl PumpkinBlock for FurnaceBlock { location: WorldPosition, server: &Server, ) { - // TODO: drop all items and close screen if different player breaks block - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good ct ID: {}", container_id); - - container.on_destroy().await; - - container.remove_player(entity_id); - player.open_container.store(None); - } - } + super::standard_on_destroy_with_container(block, player, location, server).await; } } @@ -70,30 +58,13 @@ impl FurnaceBlock { location: WorldPosition, server: &Server, ) { - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good furnance ID: {}", container_id); - container.add_player(entity_id); - player.open_container.store(Some(container_id.into())); - } - // drop(open_containers); - } else { - let mut open_containers = server.open_containers.write().await; - - let new_id = server.new_container_id(); - log::info!("New furnace ID: {}", new_id); - - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - Some(block.clone()), - ); - open_containers.insert(new_id.into(), open_container); - player.open_container.store(Some(new_id.into())); - // drop(open_containers); - } - player.open_container(server, WindowType::Furnace).await; + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Furnace, + ) + .await; } } diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 4bdfa819b..15ffdceee 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -1,4 +1,72 @@ +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::{Container, OpenContainer, WindowType}; +use pumpkin_world::block::block_registry::Block; + +use crate::{entity::player::Player, server::Server}; + pub(crate) mod chest; pub(crate) mod crafting_table; pub(crate) mod furnace; pub(crate) mod jukebox; + +/// The standard destroy with container removes the player forcibly from the container, +/// drops items to the floor and back to the player's inventory if the item stack is in movement. +pub async fn standard_on_destroy_with_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, +) { + // TODO: drop all items and back to players inventory if in motion + let entity_id = player.entity_id(); + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good ct ID: {}", container_id); + container.remove_player(entity_id); + container.clear_all_slots().await; + player.open_container.store(None); + close_all_in_container(player, container).await; + } + } +} + +/// The standard open container creates a new container if a container of the same block +/// type does not exist at the selected block location. If a container of the same type exists, the player +/// is added to the currently connected players to that container. +pub async fn standard_open_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + log::info!("Good ID: {}", container_id); + container.add_player(entity_id); + player.open_container.store(Some(container_id.into())); + } + } else { + let mut open_containers = server.open_containers.write().await; + + let new_id = server.new_container_id(); + log::info!("New ID: {}", new_id); + + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + } + player.open_container(server, window_type).await; +} + +pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { + for id in container.all_player_ids() { + if let Some(y) = player.world().get_player_by_entityid(id).await { + y.close_container().await; + } + } +} diff --git a/pumpkin/src/net/container.rs b/pumpkin/src/net/container.rs index 9e06a46de..c157b5e25 100644 --- a/pumpkin/src/net/container.rs +++ b/pumpkin/src/net/container.rs @@ -81,6 +81,7 @@ impl Player { } /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent + // TODO: is this just bc ids are not synced? pub async fn close_container(&self) { let mut inventory = self.inventory().lock().await; inventory.total_opened_containers += 1; From f2470af3f2d0248c0f1a9ac46b29cebb65679879 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:19:57 -0800 Subject: [PATCH 3/5] Added unique block standard functions --- pumpkin-inventory/src/open_container.rs | 8 +++ pumpkin/src/block/blocks/chest.rs | 2 +- pumpkin/src/block/blocks/crafting_table.rs | 55 ++++------------ pumpkin/src/block/blocks/furnace.rs | 2 +- pumpkin/src/block/blocks/mod.rs | 75 +++++++++++++++++----- pumpkin/src/server/mod.rs | 29 ++++++++- 6 files changed, 108 insertions(+), 63 deletions(-) diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 2e2b4d10a..3025ed92f 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -68,6 +68,10 @@ impl OpenContainer { self.container.lock().await.clear_all_slots(); } + pub fn clear_all_players(&mut self) { + self.players = vec![]; + } + pub fn all_player_ids(&self) -> Vec { self.players.clone() } @@ -76,6 +80,10 @@ impl OpenContainer { self.location } + pub async fn set_location(&mut self, location: Option) { + self.location = location; + } + pub fn get_block(&self) -> Option { self.block.clone() } diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 123b91bc5..313d9a5bf 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -50,7 +50,7 @@ impl PumpkinBlock for ChestBlock { location: WorldPosition, server: &Server, ) { - super::standard_on_destroy_with_container(block, player, location, server).await; + super::standard_on_broken_with_container(block, player, location, server).await; } async fn on_close<'a>( diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index eeab10514..01ad41795 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -44,7 +44,7 @@ impl PumpkinBlock for CraftingTableBlock { location: WorldPosition, server: &Server, ) { - super::standard_on_destroy_with_container(block, player, location, server).await; + super::standard_on_broken_with_container(block, player, location, server).await; } async fn on_close<'a>( @@ -56,14 +56,15 @@ impl PumpkinBlock for CraftingTableBlock { container: &OpenContainer, ) { let entity_id = player.entity_id(); - for player_id in container.all_player_ids() { if entity_id == player_id { container.clear_all_slots().await; } } - // TODO: should re-add all items to player or drop? + // TODO: items should be re-added to player inventory or dropped dependending on if they are in movement. + // TODO: unique containers should be implemented as a separate stack internally (optimizes large player servers for example) + // TODO: ephemeral containers (crafting tables) might need to be separate data structure than stored (ender chest) } } @@ -75,45 +76,13 @@ impl CraftingTableBlock { location: WorldPosition, server: &Server, ) { - //TODO: Adjust /craft command to real crafting table - let entity_id = player.entity_id(); - let mut open_containers = server.open_containers.write().await; - let mut id_to_use = -1; - - for (id, container) in open_containers.iter() { - if let Some(a_block) = container.get_block() { - if a_block.id == block.id && container.all_player_ids().is_empty() { - id_to_use = *id as i64; - } - } - } - - if id_to_use == -1 { - let new_id = server.new_container_id(); - - log::info!("New ct ID: {}", new_id); - - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - Some(block.clone()), - ); - - open_containers.insert(new_id.into(), open_container); - - player.open_container.store(Some(new_id.into())); - } else { - log::info!("Using previous ct ID: {}", id_to_use); - if let Some(ender_chest) = open_containers.get_mut(&(id_to_use as u64)) { - ender_chest.add_player(entity_id); - player - .open_container - .store(Some(id_to_use.try_into().unwrap())); - } - } - drop(open_containers); - player - .open_container(server, WindowType::CraftingTable) - .await; + super::standard_open_container_unique::( + block, + player, + location, + server, + WindowType::CraftingTable, + ) + .await; } } diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index 1f84e8a02..1ecdf9950 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -46,7 +46,7 @@ impl PumpkinBlock for FurnaceBlock { location: WorldPosition, server: &Server, ) { - super::standard_on_destroy_with_container(block, player, location, server).await; + super::standard_on_broken_with_container(block, player, location, server).await; } } diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 15ffdceee..d3785d660 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -10,23 +10,23 @@ pub(crate) mod furnace; pub(crate) mod jukebox; /// The standard destroy with container removes the player forcibly from the container, -/// drops items to the floor and back to the player's inventory if the item stack is in movement. -pub async fn standard_on_destroy_with_container( +/// drops items to the floor, and back to the player's inventory if the item stack is in movement. +pub async fn standard_on_broken_with_container( block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { // TODO: drop all items and back to players inventory if in motion - let entity_id = player.entity_id(); - if let Some(container_id) = server.get_container_id(location, block.clone()).await { + if let Some(all_container_ids) = server.get_all_container_ids(location, block.clone()).await { let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good ct ID: {}", container_id); - container.remove_player(entity_id); - container.clear_all_slots().await; - player.open_container.store(None); - close_all_in_container(player, container).await; + for individual_id in all_container_ids { + if let Some(container) = open_containers.get_mut(&u64::from(individual_id)) { + container.clear_all_slots().await; + player.open_container.store(None); + close_all_in_container(player, container).await; + container.clear_all_players(); + } } } } @@ -42,31 +42,72 @@ pub async fn standard_open_container( window_type: WindowType, ) { let entity_id = player.entity_id(); + let mut open_containers = server.open_containers.write().await; + // If container exists, add player to container, otherwise create new container if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; + log::debug!("Using previous standard container ID: {}", container_id); if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - log::info!("Good ID: {}", container_id); container.add_player(entity_id); player.open_container.store(Some(container_id.into())); } } else { - let mut open_containers = server.open_containers.write().await; - let new_id = server.new_container_id(); - log::info!("New ID: {}", new_id); + log::debug!("Creating new standard container ID: {}", new_id); + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + } + player.open_container(server, window_type).await; +} +pub async fn standard_open_container_unique( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + let mut open_containers = server.open_containers.write().await; + let mut id_to_use = -1; + + // TODO: we can do better than brute force + for (id, container) in open_containers.iter() { + if let Some(a_block) = container.get_block() { + if a_block.id == block.id && container.all_player_ids().is_empty() { + id_to_use = *id as i64; + } + } + } + + if id_to_use == -1 { + let new_id = server.new_container_id(); + log::debug!("Creating new unqiue container ID: {}", new_id); let open_container = OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + } else { + log::debug!("Using previous unqiue container ID: {}", id_to_use); + if let Some(unique_container) = open_containers.get_mut(&(id_to_use as u64)) { + unique_container.set_location(Some(location)).await; + unique_container.add_player(entity_id); + player + .open_container + .store(Some(id_to_use.try_into().unwrap())); + } } + drop(open_containers); player.open_container(server, window_type).await; } pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { for id in container.all_player_ids() { - if let Some(y) = player.world().get_player_by_entityid(id).await { - y.close_container().await; + if let Some(remote_player) = player.world().get_player_by_entityid(id).await { + remote_player.close_container().await; } } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index dac6c87f4..60f016043 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -60,6 +60,7 @@ pub struct Server { /// Caches game registries for efficient access. pub cached_registry: Vec, /// Tracks open containers used for item interactions. + // TODO: should have per player open_containers pub open_containers: RwLock>, pub drag_handler: DragHandler, /// Assigns unique IDs to entities. @@ -198,6 +199,8 @@ impl Server { .cloned() } + /// Returns the first id with a matching location and block type. If this is used with unique + /// blocks, the output will return a random result. pub async fn get_container_id(&self, location: WorldPosition, block: Block) -> Option { let open_containers = self.open_containers.read().await; // TODO: do better than brute force @@ -211,13 +214,37 @@ impl Server { } } } - log::debug!("No container found... this should not happen."); + log::error!("No container found... this should not happen."); drop(open_containers); None } + pub async fn get_all_container_ids( + &self, + location: WorldPosition, + block: Block, + ) -> Option> { + let open_containers = self.open_containers.read().await; + let mut matching_container_ids: Vec = vec![]; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found matching container id: {}", id); + matching_container_ids.push(*id as u32); + } + } + } + } + + drop(open_containers); + + Some(matching_container_ids) + } + /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. From d6e1811422d5645d52095ca1f78d4c4a7dc97913 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Wed, 25 Dec 2024 08:26:36 -0800 Subject: [PATCH 4/5] Fix deadlock in standard function --- pumpkin/src/block/blocks/mod.rs | 3 ++- pumpkin/src/server/mod.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index d3785d660..b0b128028 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -42,15 +42,16 @@ pub async fn standard_open_container( window_type: WindowType, ) { let entity_id = player.entity_id(); - let mut open_containers = server.open_containers.write().await; // If container exists, add player to container, otherwise create new container if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; log::debug!("Using previous standard container ID: {}", container_id); if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { container.add_player(entity_id); player.open_container.store(Some(container_id.into())); } } else { + let mut open_containers = server.open_containers.write().await; let new_id = server.new_container_id(); log::debug!("Creating new standard container ID: {}", new_id); let open_container = diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 60f016043..e04e14ed2 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -214,7 +214,6 @@ impl Server { } } } - log::error!("No container found... this should not happen."); drop(open_containers); From 6fa9cd9d82f2eae9288a1c0367b06260dd22d3ed Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 28 Dec 2024 18:33:50 +0100 Subject: [PATCH 5/5] Refactor global inventories system --- pumpkin-core/src/math/position.rs | 2 +- pumpkin-inventory/Cargo.toml | 2 + pumpkin-inventory/src/lib.rs | 53 +++++++- pumpkin-inventory/src/open_container.rs | 142 ++++++++++++++++----- pumpkin/src/block/block_manager.rs | 6 +- pumpkin/src/block/blocks/chest.rs | 53 ++------ pumpkin/src/block/blocks/crafting_table.rs | 46 ++----- pumpkin/src/block/blocks/furnace.rs | 33 ++--- pumpkin/src/block/blocks/mod.rs | 110 ---------------- pumpkin/src/block/container.rs | 79 ++++++++++++ pumpkin/src/block/mod.rs | 1 + pumpkin/src/block/pumpkin_block.rs | 11 +- pumpkin/src/entity/player.rs | 2 +- pumpkin/src/net/container.rs | 17 ++- pumpkin/src/net/packet/play.rs | 17 +-- pumpkin/src/server/mod.rs | 81 +++--------- pumpkin/src/world/mod.rs | 7 +- 17 files changed, 309 insertions(+), 353 deletions(-) create mode 100644 pumpkin/src/block/container.rs diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index a3b82cbeb..20f3815c3 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -5,7 +5,7 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] /// Aka Block Position pub struct WorldPosition(pub Vector3); diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 79ff82f60..c158d2d84 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -18,3 +18,5 @@ thiserror.workspace = true num-traits.workspace = true num-derive.workspace = true +rand = "0.8.5" +uuid = { version = "1.11.0", features = ["v4"] } diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index f1273b054..93d2709da 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -94,11 +94,18 @@ pub trait Container: Sync + Send { fn all_slots_ref(&self) -> Vec>; - fn clear_all_slots(&mut self) { - let all_slots = self.all_slots(); - for stack in all_slots { - *stack = None; + fn destroy_container( + &mut self, + player_inventory: &mut PlayerInventory, + carried_item: &mut Option, + unique: bool, + ) { + if unique { + self.combine_container_with_player_inventory(player_inventory) } + // TODO: Add drop for these remaining things + self.all_slots().into_iter().for_each(|slot| *slot = None); + *carried_item = None; } fn all_combinable_slots(&self) -> Vec> { @@ -132,6 +139,44 @@ pub trait Container: Sync + Send { } fn recipe_used(&mut self) {} + + fn combine_container_with_player_inventory(&mut self, player_inventory: &mut PlayerInventory) { + let slots = self + .all_slots() + .into_iter() + .filter_map(|slot| { + if let Some(stack) = slot { + let stack = *stack; + Some((slot, stack)) + } else { + None + } + }) + .collect::>(); + let mut receiving_slots = player_inventory + .slots_with_hotbar_first() + .collect::>(); + + for (slot, stack) in slots { + let matches = receiving_slots.iter_mut().filter_map(|slot| { + if let Some(receiving_stack) = slot { + if receiving_stack.item_id == stack.item_id { + Some(receiving_stack) + } else { + None + } + } else { + None + } + }); + for receiving_slot in matches { + if slot.is_none() { + break; + } + combine_stacks(slot, receiving_slot, MouseClick::Left); + } + } + } } pub struct EmptyContainer; diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 3025ed92f..f1c80efd7 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,22 +1,111 @@ use crate::crafting::check_if_matches_crafting; +use crate::player::PlayerInventory; use crate::{Container, WindowType}; use pumpkin_core::math::position::WorldPosition; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::ItemStack; +use rand::random; +use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; +use uuid::Uuid; + +#[derive(Default)] +pub struct ContainerHolder { + pub containers_by_id: HashMap, + pub location_to_container_id: HashMap, +} + +impl ContainerHolder { + pub async fn destroy( + &mut self, + id: usize, + player_inventory: &mut PlayerInventory, + carried_item: &mut Option, + ) -> Vec { + if let Some(container) = self.containers_by_id.remove(&id) { + let unique = container.unique; + let players = container.players; + let mut container = container.container.lock().await; + container.destroy_container(player_inventory, carried_item, unique); + players + } else { + vec![] + } + } + + pub async fn destroy_by_location( + &mut self, + location: &WorldPosition, + player_inventory: &mut PlayerInventory, + carried_item: &mut Option, + ) -> Vec { + if let Some(id) = self.location_to_container_id.remove(location) { + self.destroy(id, player_inventory, carried_item).await + } else { + vec![] + } + } + + pub fn get_by_location(&self, location: &WorldPosition) -> Option<&OpenContainer> { + self.containers_by_id + .get(self.location_to_container_id.get(location)?) + } + + pub fn get_mut_by_location(&mut self, location: &WorldPosition) -> Option<&mut OpenContainer> { + self.containers_by_id + .get_mut(self.location_to_container_id.get(location)?) + } + + pub fn new_by_location( + &mut self, + location: WorldPosition, + block: Option, + ) -> Option<&mut OpenContainer> { + if self.location_to_container_id.contains_key(&location) { + return None; + } + let id = self.new_container::(block, false); + self.location_to_container_id.insert(location, id); + self.containers_by_id.get_mut(&id) + } + + pub fn new_container( + &mut self, + block: Option, + unique: bool, + ) -> usize { + let mut id: usize = random(); + let mut new_container = OpenContainer::new_empty_container::(block, unique); + while let Some(container) = self.containers_by_id.insert(id, new_container) { + new_container = container; + id = random(); + } + id + } + + pub fn new_unique( + &mut self, + block: Option, + player_id: Uuid, + ) -> usize { + let id = self.new_container::(block, true); + let container = self.containers_by_id.get_mut(&id).expect("just created it"); + container.players.push(player_id); + id + } +} pub struct OpenContainer { - // TODO: unique id should be here - // TODO: should this be uuid? - players: Vec, - container: Arc>>, - location: Option, + pub unique: bool, block: Option, + pub id: usize, + container: Arc>>, + players: Vec, } impl OpenContainer { - pub fn try_open(&self, player_id: i32) -> Option<&Arc>>> { + pub fn try_open(&self, player_id: Uuid) -> Option<&Arc>>> { if !self.players.contains(&player_id) { log::debug!("couldn't open container"); return None; @@ -25,13 +114,13 @@ impl OpenContainer { Some(container) } - pub fn add_player(&mut self, player_id: i32) { + pub fn add_player(&mut self, player_id: Uuid) { if !self.players.contains(&player_id) { self.players.push(player_id); } } - pub fn remove_player(&mut self, player_id: i32) { + pub fn remove_player(&mut self, player_id: Uuid) { if let Some(index) = self.players.iter().enumerate().find_map(|(index, id)| { if *id == player_id { Some(index) @@ -44,49 +133,34 @@ impl OpenContainer { } pub fn new_empty_container( - player_id: i32, - location: Option, block: Option, + unique: bool, ) -> Self { Self { - players: vec![player_id], + unique, + players: vec![], container: Arc::new(Mutex::new(Box::new(C::default()))), - location, block, + id: 0, } } - pub fn is_location(&self, try_position: WorldPosition) -> bool { - if let Some(location) = self.location { - location == try_position - } else { - false - } - } - - pub async fn clear_all_slots(&self) { - self.container.lock().await.clear_all_slots(); - } - pub fn clear_all_players(&mut self) { self.players = vec![]; } - pub fn all_player_ids(&self) -> Vec { - self.players.clone() - } - - pub fn get_location(&self) -> Option { - self.location - } - - pub async fn set_location(&mut self, location: Option) { - self.location = location; + pub fn all_player_ids(&self) -> &[Uuid] { + &self.players } pub fn get_block(&self) -> Option { self.block.clone() } + + pub async fn window_type(&self) -> &'static WindowType { + let container = self.container.lock().await; + container.window_type() + } } #[derive(Default)] pub struct Chest([Option; 27]); diff --git a/pumpkin/src/block/block_manager.rs b/pumpkin/src/block/block_manager.rs index 0fb26ede3..bfaefe82c 100644 --- a/pumpkin/src/block/block_manager.rs +++ b/pumpkin/src/block/block_manager.rs @@ -2,7 +2,6 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::entity::player::Player; use crate::server::Server; use pumpkin_core::math::position::WorldPosition; -use pumpkin_inventory::OpenContainer; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use std::collections::HashMap; @@ -91,13 +90,10 @@ impl BlockManager { player: &Player, location: WorldPosition, server: &Server, - container: &mut OpenContainer, ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block - .on_close(block, player, location, server, container) - .await; + pumpkin_block.on_close(player, location, server).await; } } diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 313d9a5bf..eca901c03 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; -use pumpkin_inventory::{Chest, OpenContainer, WindowType}; +use pumpkin_inventory::Chest; use pumpkin_macros::{pumpkin_block, sound}; use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; +use crate::block::container::ContainerBlock; use crate::{ block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, entity::player::Player, @@ -19,14 +20,13 @@ impl PumpkinBlock for ChestBlock { &self, block: &Block, player: &Player, - _location: WorldPosition, + location: WorldPosition, server: &Server, ) { - self.open_chest_block(block, player, _location, server) - .await; + self.open(block, player, location, server).await; player .world() - .play_block_sound(sound!("block.chest.open"), _location) + .play_block_sound(sound!("block.chest.open"), location) .await; } @@ -34,58 +34,33 @@ impl PumpkinBlock for ChestBlock { &self, block: &Block, player: &Player, - _location: WorldPosition, + location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_chest_block(block, player, _location, server) - .await; + self.open(block, player, location, server).await; BlockActionResult::Consume } async fn on_broken<'a>( &self, - block: &Block, + _block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - super::standard_on_broken_with_container(block, player, location, server).await; + self.destroy(location, server, player).await; } - async fn on_close<'a>( - &self, - _block: &Block, - player: &Player, - _location: WorldPosition, - _server: &Server, - _container: &OpenContainer, - ) { + async fn on_close<'a>(&self, player: &Player, location: WorldPosition, server: &Server) { + self.close(location, server, player).await; player .world() - .play_block_sound(sound!("block.chest.close"), _location) + .play_block_sound(sound!("block.chest.close"), location) .await; // TODO: send entity updates close } } - -impl ChestBlock { - pub async fn open_chest_block( - &self, - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - ) { - // TODO: shouldn't Chest and window type be constrained together to avoid errors? - super::standard_open_container::( - block, - player, - location, - server, - WindowType::Generic9x3, - ) - .await; - // TODO: send entity updates open - } +impl ContainerBlock for ChestBlock { + const UNIQUE: bool = false; } diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 01ad41795..3c7970497 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -1,10 +1,11 @@ use crate::block::block_manager::BlockActionResult; +use crate::block::container::ContainerBlock; use crate::block::pumpkin_block::PumpkinBlock; use crate::entity::player::Player; use crate::server::Server; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; -use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType}; +use pumpkin_inventory::CraftingTable; use pumpkin_macros::pumpkin_block; use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; @@ -20,8 +21,7 @@ impl PumpkinBlock for CraftingTableBlock { _location: WorldPosition, server: &Server, ) { - self.open_crafting_screen(block, player, _location, server) - .await; + self.open(block, player, _location, server).await; } async fn on_use_with_item<'a>( @@ -32,8 +32,7 @@ impl PumpkinBlock for CraftingTableBlock { _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_crafting_screen(block, player, _location, server) - .await; + self.open(block, player, _location, server).await; BlockActionResult::Consume } @@ -44,23 +43,11 @@ impl PumpkinBlock for CraftingTableBlock { location: WorldPosition, server: &Server, ) { - super::standard_on_broken_with_container(block, player, location, server).await; + self.on_broken(block, player, location, server).await; } - async fn on_close<'a>( - &self, - _block: &Block, - player: &Player, - _location: WorldPosition, - _server: &Server, - container: &OpenContainer, - ) { - let entity_id = player.entity_id(); - for player_id in container.all_player_ids() { - if entity_id == player_id { - container.clear_all_slots().await; - } - } + async fn on_close<'a>(&self, player: &Player, location: WorldPosition, server: &Server) { + self.close(location, server, player).await; // TODO: items should be re-added to player inventory or dropped dependending on if they are in movement. // TODO: unique containers should be implemented as a separate stack internally (optimizes large player servers for example) @@ -68,21 +55,6 @@ impl PumpkinBlock for CraftingTableBlock { } } -impl CraftingTableBlock { - pub async fn open_crafting_screen( - &self, - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - ) { - super::standard_open_container_unique::( - block, - player, - location, - server, - WindowType::CraftingTable, - ) - .await; - } +impl ContainerBlock for CraftingTableBlock { + const UNIQUE: bool = true; } diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index 1ecdf9950..154b51c98 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -3,11 +3,11 @@ use crate::entity::player::Player; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::Furnace; -use pumpkin_inventory::WindowType; use pumpkin_macros::pumpkin_block; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; +use crate::block::container::ContainerBlock; use crate::{block::pumpkin_block::PumpkinBlock, server::Server}; #[pumpkin_block("minecraft:furnace")] @@ -22,49 +22,32 @@ impl PumpkinBlock for FurnaceBlock { _location: WorldPosition, server: &Server, ) { - self.open_furnace_screen(block, player, _location, server) - .await; + self.open(block, player, _location, server).await; } async fn on_use_with_item<'a>( &self, block: &Block, player: &Player, - _location: WorldPosition, + location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_furnace_screen(block, player, _location, server) - .await; + self.open(block, player, location, server).await; BlockActionResult::Consume } async fn on_broken<'a>( &self, - block: &Block, + _block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - super::standard_on_broken_with_container(block, player, location, server).await; + self.destroy(location, server, player).await; } } -impl FurnaceBlock { - pub async fn open_furnace_screen( - &self, - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - ) { - super::standard_open_container::( - block, - player, - location, - server, - WindowType::Furnace, - ) - .await; - } +impl ContainerBlock for FurnaceBlock { + const UNIQUE: bool = false; } diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index b0b128028..4bdfa819b 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -1,114 +1,4 @@ -use pumpkin_core::math::position::WorldPosition; -use pumpkin_inventory::{Container, OpenContainer, WindowType}; -use pumpkin_world::block::block_registry::Block; - -use crate::{entity::player::Player, server::Server}; - pub(crate) mod chest; pub(crate) mod crafting_table; pub(crate) mod furnace; pub(crate) mod jukebox; - -/// The standard destroy with container removes the player forcibly from the container, -/// drops items to the floor, and back to the player's inventory if the item stack is in movement. -pub async fn standard_on_broken_with_container( - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, -) { - // TODO: drop all items and back to players inventory if in motion - if let Some(all_container_ids) = server.get_all_container_ids(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - for individual_id in all_container_ids { - if let Some(container) = open_containers.get_mut(&u64::from(individual_id)) { - container.clear_all_slots().await; - player.open_container.store(None); - close_all_in_container(player, container).await; - container.clear_all_players(); - } - } - } -} - -/// The standard open container creates a new container if a container of the same block -/// type does not exist at the selected block location. If a container of the same type exists, the player -/// is added to the currently connected players to that container. -pub async fn standard_open_container( - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - window_type: WindowType, -) { - let entity_id = player.entity_id(); - // If container exists, add player to container, otherwise create new container - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let mut open_containers = server.open_containers.write().await; - log::debug!("Using previous standard container ID: {}", container_id); - if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { - container.add_player(entity_id); - player.open_container.store(Some(container_id.into())); - } - } else { - let mut open_containers = server.open_containers.write().await; - let new_id = server.new_container_id(); - log::debug!("Creating new standard container ID: {}", new_id); - let open_container = - OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); - open_containers.insert(new_id.into(), open_container); - player.open_container.store(Some(new_id.into())); - } - player.open_container(server, window_type).await; -} - -pub async fn standard_open_container_unique( - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - window_type: WindowType, -) { - let entity_id = player.entity_id(); - let mut open_containers = server.open_containers.write().await; - let mut id_to_use = -1; - - // TODO: we can do better than brute force - for (id, container) in open_containers.iter() { - if let Some(a_block) = container.get_block() { - if a_block.id == block.id && container.all_player_ids().is_empty() { - id_to_use = *id as i64; - } - } - } - - if id_to_use == -1 { - let new_id = server.new_container_id(); - log::debug!("Creating new unqiue container ID: {}", new_id); - let open_container = - OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); - - open_containers.insert(new_id.into(), open_container); - - player.open_container.store(Some(new_id.into())); - } else { - log::debug!("Using previous unqiue container ID: {}", id_to_use); - if let Some(unique_container) = open_containers.get_mut(&(id_to_use as u64)) { - unique_container.set_location(Some(location)).await; - unique_container.add_player(entity_id); - player - .open_container - .store(Some(id_to_use.try_into().unwrap())); - } - } - drop(open_containers); - player.open_container(server, window_type).await; -} - -pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { - for id in container.all_player_ids() { - if let Some(remote_player) = player.world().get_player_by_entityid(id).await { - remote_player.close_container().await; - } - } -} diff --git a/pumpkin/src/block/container.rs b/pumpkin/src/block/container.rs new file mode 100644 index 000000000..4b59a5dee --- /dev/null +++ b/pumpkin/src/block/container.rs @@ -0,0 +1,79 @@ +use crate::entity::player::Player; +use crate::server::Server; +use async_trait::async_trait; +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::Container; +use pumpkin_world::block::block_registry::Block; + +#[async_trait] +pub trait ContainerBlock { + const UNIQUE: bool; + async fn open(&self, block: &Block, player: &Player, location: WorldPosition, server: &Server) + where + C: Default + 'static, + { + if Self::UNIQUE { + let mut open_containers = server.open_containers.write().await; + let id = open_containers.new_unique::(Some(block.clone()), player.gameprofile.id); + let window_type = open_containers + .containers_by_id + .get(&id) + .expect("just created it") + .window_type() + .await; + drop(open_containers); + player.open_container.store(Some(id)); + player.open_container(server, window_type).await; + } else { + let player_id = player.gameprofile.id; + let mut open_containers = server.open_containers.write().await; + let opened_container = open_containers.get_mut_by_location(&location); + + #[allow(clippy::option_if_let_else)] + let container = match opened_container { + Some(container) => container, + None => open_containers + .new_by_location::(location, Some(block.clone())) + .unwrap(), + }; + + container.add_player(player_id); + player.open_container.store(Some(container.id)); + player + .open_container(server, container.window_type().await) + .await; + } + } + + async fn close(&self, location: WorldPosition, server: &Server, player: &Player) + where + C: Default + 'static, + { + if Self::UNIQUE { + self.destroy(location, server, player).await; + } else { + let mut containers = server.open_containers.write().await; + if let Some(container) = containers.get_mut_by_location(&location) { + container.remove_player(player.gameprofile.id); + } + } + } + + /// The standard destroy with container removes the player forcibly from the container, + /// drops items to the floor, and back to the player's inventory if the item stack is in movement. + async fn destroy(&self, location: WorldPosition, server: &Server, player: &Player) { + let mut open_containers = server.open_containers.write().await; + + let mut inventory = player.inventory().lock().await; + let mut carried_item = player.carried_item.load(); + let player_ids = open_containers + .destroy_by_location(&location, &mut inventory, &mut carried_item) + .await; + player.carried_item.store(carried_item); + for player_id in player_ids { + if let Some(player) = server.get_player_by_uuid(player_id).await { + player.open_container.store(None); + } + } + } +} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index b07790339..c46a6599b 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -8,6 +8,7 @@ use std::sync::Arc; pub mod block_manager; mod blocks; +mod container; pub mod pumpkin_block; #[must_use] diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index c0a65c316..96061e8b9 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -3,7 +3,6 @@ use crate::entity::player::Player; use crate::server::Server; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; -use pumpkin_inventory::OpenContainer; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; @@ -54,13 +53,5 @@ pub trait PumpkinBlock: Send + Sync { ) { } - async fn on_close<'a>( - &self, - _block: &Block, - _player: &Player, - _location: WorldPosition, - _server: &Server, - _container: &OpenContainer, - ) { - } + async fn on_close<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index dbc84e74d..f958cd914 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -88,7 +88,7 @@ pub struct Player { /// The player's food saturation level. pub food_saturation: AtomicCell, /// The ID of the currently open container (if any). - pub open_container: AtomicCell>, + pub open_container: AtomicCell>, /// The item currently being held by the player. pub carried_item: AtomicCell>, diff --git a/pumpkin/src/net/container.rs b/pumpkin/src/net/container.rs index c157b5e25..21baae871 100644 --- a/pumpkin/src/net/container.rs +++ b/pumpkin/src/net/container.rs @@ -18,9 +18,10 @@ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_world::item::item_registry::Item; use pumpkin_world::item::ItemStack; use std::sync::Arc; +use uuid::Uuid; impl Player { - pub async fn open_container(&self, server: &Server, window_type: WindowType) { + pub async fn open_container(&self, server: &Server, window_type: &'static WindowType) { let mut inventory = self.inventory().lock().await; inventory.state_id = 0; inventory.total_opened_containers += 1; @@ -38,7 +39,7 @@ impl Player { self.client .send_packet(&COpenScreen::new( inventory.total_opened_containers.into(), - VarInt(window_type as i32), + VarInt(*window_type as i32), title, )) .await; @@ -454,14 +455,16 @@ impl Player { } async fn get_current_players_in_container(&self, server: &Server) -> Vec> { - let player_ids: Vec = { + let player_ids: Vec = { let open_containers = server.open_containers.read().await; open_containers + .containers_by_id .get(&self.open_container.load().unwrap()) .unwrap() .all_player_ids() - .into_iter() - .filter(|player_id| *player_id != self.entity_id()) + .iter() + .filter(|&player_id| (*player_id != self.gameprofile.id)) + .copied() .collect() }; let player_token = self.gameprofile.id; @@ -481,7 +484,7 @@ impl Player { if *token == player_token { None } else { - let entity_id = player.entity_id(); + let entity_id = player.gameprofile.id; player_ids.contains(&entity_id).then(|| player.clone()) } }) @@ -531,7 +534,7 @@ impl Player { server: &Server, ) -> Option>>> { match self.open_container.load() { - Some(id) => server.try_get_container(self.entity_id(), id).await, + Some(id) => server.try_get_container(self.gameprofile.id, id).await, None => None, } } diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index baf904659..6f81d10bb 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -549,7 +549,7 @@ impl Player { } let world = &entity.world; - let victim = world.get_player_by_entityid(entity_id.0).await; + let victim = world.get_player_by_entity_id(*entity_id).await; let Some(victim) = victim else { self.kick(TextComponent::text("Interacted with invalid entity id")) .await; @@ -852,18 +852,9 @@ impl Player { let open_container = self.open_container.load(); if let Some(id) = open_container { let mut open_containers = server.open_containers.write().await; - if let Some(container) = open_containers.get_mut(&id) { - // If container contains both a location and a type, run the on_close block_manager handler - if let Some(pos) = container.get_location() { - if let Some(block) = container.get_block() { - server - .block_manager - .on_close(&block, self, pos, server, container) //block, self, location, server) - .await; - } - } - // Remove the player from the container - container.remove_player(self.entity_id()); + if let Some(container) = open_containers.containers_by_id.get_mut(&id) { + container.remove_player(self.gameprofile.id); + if container.unique {} } self.open_container.store(None); } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index e04e14ed2..225542570 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,19 +1,26 @@ +use crate::block::block_manager::BlockManager; +use crate::block::default_block_manager; +use crate::net::EncryptionError; +use crate::world::custom_bossbar::CustomBossbars; +use crate::{ + command::{default_dispatcher, dispatcher::CommandDispatcher}, + entity::player::Player, + net::Client, + world::World, +}; use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use pumpkin_config::BASIC_CONFIG; -use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; use pumpkin_inventory::drag_handler::DragHandler; -use pumpkin_inventory::{Container, OpenContainer}; +use pumpkin_inventory::{Container, ContainerHolder}; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::{DimensionType, Registry}; -use pumpkin_world::block::block_registry::Block; use pumpkin_world::dimension::Dimension; use rand::prelude::SliceRandom; -use std::collections::HashMap; use std::sync::atomic::AtomicU32; use std::{ sync::{ @@ -23,17 +30,7 @@ use std::{ time::Duration, }; use tokio::sync::{Mutex, RwLock}; - -use crate::block::block_manager::BlockManager; -use crate::block::default_block_manager; -use crate::net::EncryptionError; -use crate::world::custom_bossbar::CustomBossbars; -use crate::{ - command::{default_dispatcher, dispatcher::CommandDispatcher}, - entity::player::Player, - net::Client, - world::World, -}; +use uuid::Uuid; mod connection_cache; mod key_store; @@ -61,7 +58,7 @@ pub struct Server { pub cached_registry: Vec, /// Tracks open containers used for item interactions. // TODO: should have per player open_containers - pub open_containers: RwLock>, + pub open_containers: RwLock, pub drag_handler: DragHandler, /// Assigns unique IDs to entities. entity_id: AtomicI32, @@ -104,7 +101,7 @@ impl Server { Self { cached_registry: Registry::get_synced(), - open_containers: RwLock::new(HashMap::new()), + open_containers: RwLock::new(ContainerHolder::default()), drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), @@ -189,61 +186,17 @@ impl Server { pub async fn try_get_container( &self, - player_id: EntityId, - container_id: u64, + player_id: Uuid, + container_id: usize, ) -> Option>>> { let open_containers = self.open_containers.read().await; open_containers + .containers_by_id .get(&container_id)? .try_open(player_id) .cloned() } - /// Returns the first id with a matching location and block type. If this is used with unique - /// blocks, the output will return a random result. - pub async fn get_container_id(&self, location: WorldPosition, block: Block) -> Option { - let open_containers = self.open_containers.read().await; - // TODO: do better than brute force - for (id, container) in open_containers.iter() { - if container.is_location(location) { - if let Some(container_block) = container.get_block() { - if container_block.id == block.id { - log::debug!("Found container id: {}", id); - return Some(*id as u32); - } - } - } - } - - drop(open_containers); - - None - } - - pub async fn get_all_container_ids( - &self, - location: WorldPosition, - block: Block, - ) -> Option> { - let open_containers = self.open_containers.read().await; - let mut matching_container_ids: Vec = vec![]; - // TODO: do better than brute force - for (id, container) in open_containers.iter() { - if container.is_location(location) { - if let Some(container_block) = container.get_block() { - if container_block.id == block.id { - log::debug!("Found matching container id: {}", id); - matching_container_ids.push(*id as u32); - } - } - } - } - - drop(open_containers); - - Some(matching_container_ids) - } - /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 92239719b..a40c6835b 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -47,6 +47,7 @@ use tokio::{ runtime::Handle, sync::{mpsc, RwLock}, }; +use uuid::Uuid; use worldborder::Worldborder; pub mod bossbar; @@ -595,7 +596,7 @@ impl World { } /// Gets a Player by entity id - pub async fn get_player_by_entityid(&self, id: EntityId) -> Option> { + pub async fn get_player_by_entity_id(&self, id: EntityId) -> Option> { for player in self.current_players.lock().await.values() { if player.entity_id() == id { return Some(player.clone()); @@ -626,8 +627,8 @@ impl World { /// # Returns /// /// An `Option>` containing the player if found, or `None` if not. - pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option> { - return self.current_players.lock().await.get(&id).cloned(); + pub async fn get_player_by_uuid(&self, id: Uuid) -> Option> { + self.current_players.lock().await.get(&id).cloned() } /// Gets a list of players who's location equals the given position in the world.