diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index a3b82cbe..20f3815c 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 79ff82f6..c158d2d8 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 f1273b05..93d2709d 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 c563ee47..f1c80efd 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,53 +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_number_of_players(&self) -> usize { - self.players.len() - } - - 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 0fb26ede..bfaefe82 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 5a83570e..0809038e 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -1,25 +1,20 @@ 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_protocol::{client::play::CBlockAction, codec::var_int::VarInt}; +use pumpkin_protocol::client::play::CBlockAction; +use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_world::{ block::block_registry::{get_block, Block}, item::item_registry::Item, }; +use crate::block::container::ContainerBlock; use crate::{ block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, entity::player::Player, server::Server, }; - -#[derive(PartialEq)] -pub enum ChestState { - IsOpened, - IsClosed, -} - #[pumpkin_block("minecraft:chest")] pub struct ChestBlock; @@ -29,93 +24,61 @@ 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; + self.play_chest_action(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_chest_block(block, player, _location, server) - .await; + self.open(block, player, location, server).await; BlockActionResult::Consume } async fn on_broken<'a>( - &self, - block: &Block, - player: &Player, - location: WorldPosition, - server: &Server, - ) { - super::standard_on_broken_with_container(block, player, location, server).await; - } - - async fn on_close<'a>( &self, _block: &Block, player: &Player, location: WorldPosition, server: &Server, - container: &mut OpenContainer, ) { - container.remove_player(player.entity_id()); + self.destroy(location, server, player).await; + } - self.play_chest_action(container, player, location, server, ChestState::IsClosed) - .await; + async fn on_close<'a>(&self, player: &Player, location: WorldPosition, server: &Server) { + self.close(location, server, player).await; + self.play_chest_action(player, location, server).await; } } 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; - - if let Some(container_id) = server.get_container_id(location, block.clone()).await { - let open_containers = server.open_containers.read().await; - if let Some(container) = open_containers.get(&u64::from(container_id)) { - self.play_chest_action(container, player, location, server, ChestState::IsOpened) - .await; - } - } - } - pub async fn play_chest_action( &self, - container: &OpenContainer, player: &Player, location: WorldPosition, server: &Server, - state: ChestState, ) { - let num_players = container.get_number_of_players() as u8; - if state == ChestState::IsClosed && num_players == 0 { + let num_players = server + .open_containers + .read() + .await + .get_by_location(&location) + .map(|container| container.all_player_ids().len()) + .unwrap_or_default(); + if num_players == 0 { player .world() .play_block_sound(sound!("block.chest.close"), location) .await; - } else if state == ChestState::IsOpened && num_players == 1 { + } else if num_players == 1 { player .world() .play_block_sound(sound!("block.chest.open"), location) @@ -127,10 +90,14 @@ impl ChestBlock { .broadcast_packet_all(&CBlockAction::new( &location, 1, - num_players, + num_players as u8, VarInt(e.id.into()), )) .await; } } } + +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 c879f060..3c797049 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,25 +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: &mut 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; - } - } - - container.remove_player(entity_id); + 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) @@ -70,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 1ecdf995..154b51c9 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 b0b12802..4bdfa819 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 00000000..4b59a5de --- /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 b0779033..c46a6599 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 90de5718..96061e8b 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: &mut 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 c197f58f..6f4920e0 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -91,7 +91,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 c157b5e2..21baae87 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 baf90465..6f81d10b 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 96090ae8..d929d565 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; @@ -60,8 +57,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 open_containers: RwLock, pub drag_handler: DragHandler, /// Assigns unique IDs to entities. entity_id: AtomicI32, @@ -104,7 +100,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 +185,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 92239719..a40c6835 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.