diff --git a/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs b/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs index 1cdbb4b02..46f346b54 100644 --- a/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs +++ b/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs @@ -12,7 +12,7 @@ use pumpkin_protocol::bedrock::server::{ raknet::{ connection::SConnectionRequest, open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2}, - unconnected_ping::SUnconnectedPing, + unconnected_ping::{SUnconnectedPing, SUnconnectedPingOpenConnections}, }, request_chunk_radius::SRequestChunkRadius, request_network_settings::SRequestNetworkSettings, @@ -60,6 +60,8 @@ fn fuzz_serverbound_packets(payload: &[u8]) { let _ = SOpenConnectionRequest2::read(&mut cursor); cursor.set_position(0); let _ = SUnconnectedPing::read(&mut cursor); + cursor.set_position(0); + let _ = SUnconnectedPingOpenConnections::read(&mut cursor); } // --------------------------------------------------------------------------- diff --git a/pumpkin-protocol/src/bedrock/client/change_dimension.rs b/pumpkin-protocol/src/bedrock/client/change_dimension.rs new file mode 100644 index 000000000..7fb910ee3 --- /dev/null +++ b/pumpkin-protocol/src/bedrock/client/change_dimension.rs @@ -0,0 +1,25 @@ +use pumpkin_macros::packet; +use pumpkin_util::math::vector3::Vector3; + +use crate::{codec::var_int::VarInt, serial::PacketWrite}; + +#[derive(PacketWrite)] +#[packet(61)] +pub struct CChangeDimension { + pub dimension: VarInt, + pub position: Vector3, + pub respawn: bool, + pub has_loading_screen_id: bool, +} + +impl CChangeDimension { + #[must_use] + pub const fn new(dimension: i32, position: Vector3, respawn: bool) -> Self { + Self { + dimension: VarInt(dimension), + position, + respawn, + has_loading_screen_id: false, + } + } +} diff --git a/pumpkin-protocol/src/bedrock/client/mod.rs b/pumpkin-protocol/src/bedrock/client/mod.rs index 8452a0df6..058b521c6 100644 --- a/pumpkin-protocol/src/bedrock/client/mod.rs +++ b/pumpkin-protocol/src/bedrock/client/mod.rs @@ -3,6 +3,7 @@ pub mod add_item_actor; pub mod add_player; pub mod available_commands; pub mod boss_event; +pub mod change_dimension; pub mod chunk_radius_update; pub mod common; pub mod container_open; @@ -50,6 +51,7 @@ pub use add_item_actor::*; pub use add_player::*; pub use available_commands::*; pub use boss_event::*; +pub use change_dimension::*; pub use chunk_radius_update::*; pub use common::*; pub use container_open::*; diff --git a/pumpkin-protocol/src/bedrock/client/raknet/connection.rs b/pumpkin-protocol/src/bedrock/client/raknet/connection.rs index 03357ddc1..2ff276a61 100644 --- a/pumpkin-protocol/src/bedrock/client/raknet/connection.rs +++ b/pumpkin-protocol/src/bedrock/client/raknet/connection.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use pumpkin_macros::packet; -use crate::serial::PacketWrite; +use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite}; #[derive(PacketWrite)] #[packet(0x03)] pub struct CConnectedPong { @@ -46,3 +46,75 @@ impl CConnectionRequestAccepted { } } } + +#[derive(PacketWrite)] +#[packet(0x12)] +pub struct CAlreadyConnected { + magic: [u8; 16], + server_guid: u64, +} + +impl CAlreadyConnected { + #[must_use] + pub const fn new(server_guid: u64) -> Self { + Self { + magic: RAKNET_MAGIC, + server_guid, + } + } +} + +#[derive(PacketWrite)] +#[packet(0x14)] +pub struct CNoFreeIncomingConnections { + magic: [u8; 16], + server_guid: u64, +} + +impl CNoFreeIncomingConnections { + #[must_use] + pub const fn new(server_guid: u64) -> Self { + Self { + magic: RAKNET_MAGIC, + server_guid, + } + } +} + +#[derive(PacketWrite)] +#[packet(0x17)] +pub struct CConnectionBanned { + magic: [u8; 16], + server_guid: u64, +} + +impl CConnectionBanned { + #[must_use] + pub const fn new(server_guid: u64) -> Self { + Self { + magic: RAKNET_MAGIC, + server_guid, + } + } +} + +#[derive(PacketWrite)] +#[packet(0x1A)] +pub struct CIpRecentlyConnected { + magic: [u8; 16], + server_guid: u64, +} + +impl CIpRecentlyConnected { + #[must_use] + pub const fn new(server_guid: u64) -> Self { + Self { + magic: RAKNET_MAGIC, + server_guid, + } + } +} + +#[derive(PacketWrite)] +#[packet(0x15)] +pub struct CDisconnect; diff --git a/pumpkin-protocol/src/bedrock/network_item.rs b/pumpkin-protocol/src/bedrock/network_item.rs index d8cc131ff..596dcac52 100644 --- a/pumpkin-protocol/src/bedrock/network_item.rs +++ b/pumpkin-protocol/src/bedrock/network_item.rs @@ -44,6 +44,7 @@ impl PacketRead for NetworkItemDescriptor { let has_net_id = bool::read(buf)?; if has_net_id { + let _variant = VarUInt::read(buf)?; let _net_id = VarInt::read(buf)?; } diff --git a/pumpkin-protocol/src/bedrock/packet_decoder.rs b/pumpkin-protocol/src/bedrock/packet_decoder.rs index ed3d4fa34..e87c0afe6 100644 --- a/pumpkin-protocol/src/bedrock/packet_decoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_decoder.rs @@ -226,7 +226,7 @@ mod tests { const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1; let payload = vec![0x2a; PAYLOAD_LEN]; let mut wire_buf = Vec::new(); - let mut network_encoder = UDPNetworkEncoder::new(); + let network_encoder = UDPNetworkEncoder::new(); network_encoder .write_game_packet( 0x01, diff --git a/pumpkin-protocol/src/bedrock/packet_encoder.rs b/pumpkin-protocol/src/bedrock/packet_encoder.rs index 32eabe53c..1410ee420 100644 --- a/pumpkin-protocol/src/bedrock/packet_encoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_encoder.rs @@ -119,8 +119,12 @@ impl UDPNetworkEncoder { Ok(()) } + pub const fn encryptor_mut(&mut self) -> Option<&mut BedrockEncryptor> { + self.encryptor.as_mut() + } + pub fn write_game_packet( - &mut self, + &self, packet_id: u16, sub_client_sender: SubClient, sub_client_target: SubClient, @@ -151,26 +155,22 @@ impl UDPNetworkEncoder { .write_u8(0xfe) .map_err(|e| Error::other(e.to_string()))?; // Bedrock Game Packet Header - let mut data_to_encrypt = Vec::new(); + let mut data_to_write = Vec::new(); if let Some((_threshold, level)) = self.compression { // Write Compression Method (0x00 for Zlib) - data_to_encrypt.push(0x00); + data_to_write.push(0x00); let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(level)); encoder.write_all(&inner_buffer)?; let compressed_data = encoder.finish()?; - data_to_encrypt.extend_from_slice(&compressed_data); + data_to_write.extend_from_slice(&compressed_data); } else { - data_to_encrypt.extend_from_slice(&inner_buffer); + data_to_write.extend_from_slice(&inner_buffer); } - if let Some(encryptor) = &mut self.encryptor { - encryptor.encrypt(&mut data_to_encrypt); - } - - writer.write_all(&data_to_encrypt)?; + writer.write_all(&data_to_write)?; Ok(()) } diff --git a/pumpkin-protocol/src/bedrock/server/player_auth_input.rs b/pumpkin-protocol/src/bedrock/server/player_auth_input.rs index 0c1d8c540..3d4002f28 100644 --- a/pumpkin-protocol/src/bedrock/server/player_auth_input.rs +++ b/pumpkin-protocol/src/bedrock/server/player_auth_input.rs @@ -27,6 +27,8 @@ pub struct SPlayerAuthInput { pub tick: VarULong, pub delta: Vector3, pub block_actions: Option>, + pub item_interaction: Option, + pub item_stack_request: Option, pub vehicle_rotation: Option>, pub vehicle_unique_id: Option, pub analog_move: Vector2, @@ -52,22 +54,18 @@ impl PacketRead for SPlayerAuthInput { let delta = Vector3::::read(reader)?; // 1. Perform Item Interaction - if input_data.get(InputData::PerformItemInteraction as usize) { - // protocol.UseItemTransactionData (Simplified skip) - let _action_type = VarUInt::read(reader)?; - let action_count = VarUInt::read(reader)?.0; - for _ in 0..action_count { - let _type = VarUInt::read(reader)?; - let _pos = BlockPos::read(reader)?; - let _face = VarInt::read(reader)?; - } - } + let item_interaction = if input_data.get(InputData::PerformItemInteraction as usize) { + Some(PlayerInventoryAction::read(reader)?) + } else { + None + }; // 2. Item Stack Request - if input_data.get(InputData::PerformItemStackRequest as usize) { - // protocol.Single ItemStackRequest - return Err(Error::other("ItemStackRequest decoding not implemented")); - } + let item_stack_request = if input_data.get(InputData::PerformItemStackRequest as usize) { + Some(crate::bedrock::server::item_stack_request::ItemStackRequest::read(reader)?) + } else { + None + }; // 3. Block Actions let block_actions = if input_data.get(InputData::PerformBlockActions as usize) { @@ -109,6 +107,8 @@ impl PacketRead for SPlayerAuthInput { tick, delta, block_actions, + item_interaction, + item_stack_request, vehicle_rotation, vehicle_unique_id, analog_move, @@ -118,6 +118,43 @@ impl PacketRead for SPlayerAuthInput { } } +#[derive(Debug)] +pub struct PlayerInventoryAction { + pub legacy_request_id: VarInt, + pub legacy_slots: Vec, + pub actions: Vec, + pub transaction: crate::bedrock::server::inventory_transaction::UseItemTransactionData, +} + +impl PacketRead for PlayerInventoryAction { + fn read(buf: &mut R) -> Result { + let legacy_request_id = VarInt::read(buf)?; + let mut legacy_slots = Vec::new(); + if legacy_request_id.0 < -1 && (legacy_request_id.0 & 1) == 0 { + let slots_len = VarUInt::read(buf)?.0; + for _ in 0..slots_len { + legacy_slots.push( + crate::bedrock::server::inventory_transaction::LegacySetItemSlot::read(buf)?, + ); + } + } + let actions_len = VarUInt::read(buf)?.0; + let mut actions = Vec::with_capacity(actions_len as usize); + for _ in 0..actions_len { + actions + .push(crate::bedrock::server::inventory_transaction::InventoryAction::read(buf)?); + } + let transaction = + crate::bedrock::server::inventory_transaction::UseItemTransactionData::read(buf)?; + Ok(Self { + legacy_request_id, + legacy_slots, + actions, + transaction, + }) + } +} + #[derive(Debug, PacketRead)] pub struct PlayerBlockAction { pub action: VarInt, diff --git a/pumpkin-protocol/src/bedrock/server/raknet/connection.rs b/pumpkin-protocol/src/bedrock/server/raknet/connection.rs index 678df712a..52b212a4c 100644 --- a/pumpkin-protocol/src/bedrock/server/raknet/connection.rs +++ b/pumpkin-protocol/src/bedrock/server/raknet/connection.rs @@ -35,3 +35,6 @@ pub struct SNewIncomingConnection { #[packet(0x15)] pub struct SDisconnect; + +#[packet(0x16)] +pub struct SConnectionLost; diff --git a/pumpkin-protocol/src/bedrock/server/raknet/unconnected_ping.rs b/pumpkin-protocol/src/bedrock/server/raknet/unconnected_ping.rs index 6f2c48e70..9da8ccce0 100644 --- a/pumpkin-protocol/src/bedrock/server/raknet/unconnected_ping.rs +++ b/pumpkin-protocol/src/bedrock/server/raknet/unconnected_ping.rs @@ -12,3 +12,14 @@ pub struct SUnconnectedPing { #[serial(big_endian)] pub client_guid: u64, } + +#[derive(PacketRead)] +#[packet(0x02)] +/// Used to request Server information like MOTD when connection is open? +pub struct SUnconnectedPingOpenConnections { + #[serial(big_endian)] + pub time: u64, + pub magic: [u8; 16], + #[serial(big_endian)] + pub client_guid: u64, +} diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index 94e04d517..a945d3498 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -385,7 +385,242 @@ pub struct BlockRegistry { fluids: FxHashMap>, } +#[derive(Debug)] +pub enum BlockPlacingError { + InvalidGamemode, + BlockOutOfWorld, +} + impl BlockRegistry { + fn entity_blocks_block_placement(entity: &dyn EntityBase) -> bool { + let base_entity = entity.get_entity(); + if base_entity.is_removed() + || base_entity + .no_clip + .load(std::sync::atomic::Ordering::Relaxed) + || entity.is_spectator() + { + return false; + } + + if entity.get_living_entity().is_some() { + return true; + } + + let entity_type = base_entity.entity_type; + let resource_name = entity_type.resource_name; + entity_type == &pumpkin_data::entity::EntityType::END_CRYSTAL + || entity_type == &pumpkin_data::entity::EntityType::FALLING_BLOCK + || entity_type == &pumpkin_data::entity::EntityType::TNT + || resource_name.ends_with("_minecart") + || resource_name.ends_with("_boat") + || resource_name.ends_with("_raft") + } + + fn has_blocking_entity_in_box(world: &World, placed_box: &BoundingBox) -> bool { + let players = world.players.load(); + if players.iter().any(|player| { + Self::entity_blocks_block_placement(player.as_ref()) + && player + .get_entity() + .bounding_box + .load() + .intersects(placed_box) + }) { + return true; + } + + world.entities.load().iter().any(|entity| { + Self::entity_blocks_block_placement(entity.as_ref()) + && entity + .get_entity() + .bounding_box + .load() + .intersects(placed_box) + }) + } + + #[expect(clippy::too_many_lines)] + pub async fn place_block( + &self, + player: &Arc, + placed_block: &'static Block, + server: &Server, + use_item_on: &SUseItemOn, + location: BlockPos, + face: BlockDirection, + ) -> Result, BlockPlacingError> { + let entity = &player.get_entity(); + + match player.gamemode.load() { + pumpkin_util::GameMode::Spectator | pumpkin_util::GameMode::Adventure => { + return Err(BlockPlacingError::InvalidGamemode); + } + _ => {} + } + + let clicked_block_pos = BlockPos(location.0); + let world = entity.world.load_full(); + + if location.0.y + face.to_offset().y < world.get_bottom_y() { + return Err(BlockPlacingError::BlockOutOfWorld); + } + + if location.0.y + face.to_offset().y > world.get_top_y() { + player + .send_system_message_raw( + &pumpkin_util::text::TextComponent::translate_cross( + pumpkin_data::translation::java::BUILD_TOOHIGH, + pumpkin_data::translation::bedrock::BUILD_TOOHIGH, + vec![pumpkin_util::text::TextComponent::text( + (world.get_top_y()).to_string(), + )], + ) + .color_named(pumpkin_util::text::color::NamedColor::Red), + true, + ) + .await; + return Err(BlockPlacingError::BlockOutOfWorld); + } + + let (clicked_block, clicked_block_state) = world.get_block_and_state(&clicked_block_pos); + + let replace_clicked_block = if clicked_block == placed_block { + self.can_update_at( + &world, + clicked_block, + clicked_block_state.id, + &clicked_block_pos, + face, + use_item_on, + player, + ) + .then_some(BlockIsReplacing::Itself(clicked_block_state.id)) + } else if clicked_block_state.replaceable() { + if clicked_block == &Block::WATER { + use pumpkin_data::block_properties::{BlockProperties, WaterLikeProperties}; + let water_props = + WaterLikeProperties::from_state_id(clicked_block_state.id, clicked_block); + Some(BlockIsReplacing::Water(water_props.level)) + } else { + Some(BlockIsReplacing::Other) + } + } else { + None + }; + + let (final_block_pos, final_face, replacing) = + if let Some(replacing) = replace_clicked_block { + (clicked_block_pos, face.opposite(), replacing) + } else { + let block_pos = BlockPos(location.0 + face.to_offset()); + let (previous_block, previous_block_state) = world.get_block_and_state(&block_pos); + + let replace_previous_block = if previous_block == placed_block { + self.can_update_at( + &world, + previous_block, + previous_block_state.id, + &block_pos, + face.opposite(), + use_item_on, + player, + ) + .then_some(BlockIsReplacing::Itself(previous_block_state.id)) + } else { + previous_block_state.replaceable().then(|| { + if previous_block == &Block::WATER { + use pumpkin_data::block_properties::{ + BlockProperties, WaterLikeProperties, + }; + let water_props = WaterLikeProperties::from_state_id( + previous_block_state.id, + previous_block, + ); + BlockIsReplacing::Water(water_props.level) + } else { + BlockIsReplacing::None + } + }) + }; + + match replace_previous_block { + Some(replacing) => (block_pos, face.opposite(), replacing), + None => { + return Ok(None); + } + } + }; + + if !self.can_place_at( + Some(server), + Some(&*world), + &*world, + Some(player), + placed_block, + placed_block.default_state, + &final_block_pos, + Some(final_face), + Some(use_item_on), + ) { + return Ok(None); + } + + let new_state = self + .on_place( + server, + &world, + player, + placed_block, + &final_block_pos, + final_face, + replacing, + use_item_on, + ) + .await; + + // Mirror vanilla obstruction checks: only entities that block building should prevent + // placement. (e.g. arrows/xp orbs/displays/markers should not) + let state = BlockState::from_id(new_state); + for shape in state.get_block_collision_shapes() { + let placed_box = shape.at_pos(final_block_pos); + + if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) { + return Ok(None); + } + } + + let event = crate::plugin::block::block_place::BlockPlaceEvent::new( + player.clone(), + placed_block, + clicked_block, + final_block_pos, + true, + ); + let event = server + .plugin_manager + .fire::(event) + .await; + if event.cancelled { + return Ok(None); + } + + let _replaced_id = world + .set_block_state(&final_block_pos, new_state, BlockFlags::NOTIFY_ALL) + .await; + + self.player_placed( + &world, + placed_block, + new_state, + &final_block_pos, + face, + player, + ) + .await; + + Ok(Some((final_block_pos, new_state))) + } pub fn register(&mut self, block: T) { let ids = T::ids(); let val = Arc::new(block); diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index da3ed5d97..e2cc0779e 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -2496,21 +2496,42 @@ impl Player { last_pos.y.round() as i32, last_pos.z.round() as i32, )); - self.client - .send_packet_now(&CRespawn::new( - PlayerSpawnData::new( - new_world.dimension.clone(), - biome::hash_seed(new_world.level.seed.0), // seed - self.gamemode.load() as u8, - self.previous_gamemode.load().unwrap_or(self.gamemode.load()) as i8, - false, - false, - Some((death_dimension, death_location)), - VarInt(self.get_entity().portal_cooldown.load(Ordering::Relaxed) as i32), - new_world.sea_level.into(), - ), - CRespawn::KEEP_ALL_DATA, - )).await; + match self.client.as_ref() { + ClientPlatform::Java(java) => { + java.send_packet_now(&CRespawn::new( + PlayerSpawnData::new( + new_world.dimension.clone(), + biome::hash_seed(new_world.level.seed.0), // seed + self.gamemode.load() as u8, + self.previous_gamemode.load().unwrap_or(self.gamemode.load()) as i8, + false, + false, + Some((death_dimension, death_location)), + VarInt(self.get_entity().portal_cooldown.load(Ordering::Relaxed) as i32), + new_world.sea_level.into(), + ), + CRespawn::KEEP_ALL_DATA, + )).await; + } + ClientPlatform::Bedrock(bedrock) => { + let bedrock_dimension = if new_world.dimension == Dimension::OVERWORLD { + 0 + } else if new_world.dimension == Dimension::THE_NETHER { + 1 + } else if new_world.dimension == Dimension::THE_END { + 2 + } else { + 0 + }; + let pos_f32 = Vector3::new(position.x as f32, position.y as f32, position.z as f32); + let change_dim_packet = pumpkin_protocol::bedrock::client::CChangeDimension::new( + bedrock_dimension, + pos_f32, + false, + ); + bedrock.enqueue_packet(&change_dim_packet).await; + } + } self.send_permission_lvl_update(); @@ -4931,7 +4952,6 @@ impl InventoryPlayer for Player { } ClientPlatform::Bedrock(bedrock) => { use pumpkin_protocol::bedrock::{ - client::inventory_content::CInventoryContent, client::inventory_slot::CInventorySlot, network_item::{ ContainerName, FullContainerName, NetworkItemStackDescriptor, @@ -4948,23 +4968,20 @@ impl InventoryPlayer for Player { if window_id == 0 { tracing::info!( - "enqueue_slot_packet: window_id is 0, sending CInventoryContent to Bedrock client" + "enqueue_slot_packet: window_id is 0, sending CInventorySlot to Bedrock client" ); - let mut slots = Vec::with_capacity(36); - let main_inventory = &self.inventory().main_inventory; - for s in main_inventory { - let stack = s.lock().await; - slots.push(NetworkItemStackDescriptor::from(&*stack)); - } + let slot_idx = packet.slot as usize; + let item_desc = NetworkItemStackDescriptor::from(&*packet.slot_data.0); - let bedrock_packet = CInventoryContent { - container_id: VarUInt(0), - slots, - full_container_name: FullContainerName { + let bedrock_packet = CInventorySlot { + window_id: VarUInt(0), + inventory_slot: VarUInt(slot_idx as u32), + container_name: Some(FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, - }, - storage_item: NetworkItemStackDescriptor::default(), + }), + storage: None, + item: item_desc, }; bedrock.enqueue_packet(&bedrock_packet).await; } else { @@ -5073,7 +5090,7 @@ impl InventoryPlayer for Player { } ClientPlatform::Bedrock(bedrock) => { use pumpkin_protocol::bedrock::{ - client::inventory_content::CInventoryContent, + client::inventory_slot::CInventorySlot, network_item::{ ContainerName, FullContainerName, NetworkItemStackDescriptor, }, @@ -5081,24 +5098,21 @@ impl InventoryPlayer for Player { use pumpkin_protocol::codec::var_uint::VarUInt; tracing::info!( - "enqueue_slot_set_packet: slot={}, sending CInventoryContent to Bedrock client", + "enqueue_slot_set_packet: slot={}, sending CInventorySlot to Bedrock client", packet.slot.0 ); - let mut slots = Vec::with_capacity(36); - let main_inventory = &self.inventory().main_inventory; - for s in main_inventory { - let stack = s.lock().await; - slots.push(NetworkItemStackDescriptor::from(&*stack)); - } - let bedrock_packet = CInventoryContent { - container_id: VarUInt(0), - slots, - full_container_name: FullContainerName { + let item_stack = &*packet.item.0; + let item_desc = NetworkItemStackDescriptor::from(item_stack); + let bedrock_packet = CInventorySlot { + window_id: VarUInt(0), + inventory_slot: VarUInt(packet.slot.0 as u32), + container_name: Some(FullContainerName { container_name: ContainerName::Inventory, dynamic_id: None, - }, - storage_item: NetworkItemStackDescriptor::default(), + }), + storage: None, + item: item_desc, }; bedrock.enqueue_packet(&bedrock_packet).await; } diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index e45bb31e3..061906aea 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -517,7 +517,7 @@ impl PumpkinServer { }; let id = udp_buf[0]; - let is_online = id & 128 != 0; + let is_online = id & pumpkin_protocol::bedrock::RAKNET_VALID != 0; if is_online { let be_clients = bedrock_clients.clone(); diff --git a/pumpkin/src/net/bedrock/login.rs b/pumpkin/src/net/bedrock/login.rs index 8d2b57c8b..500c6eb32 100644 --- a/pumpkin/src/net/bedrock/login.rs +++ b/pumpkin/src/net/bedrock/login.rs @@ -12,7 +12,6 @@ use pumpkin_protocol::bedrock::{ resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo, start_game::Experiments, }, - frame_set::FrameSet, server::{login::SLogin, request_network_settings::SRequestNetworkSettings}, }; use pumpkin_protocol::bedrock::{ @@ -256,9 +255,7 @@ impl BedrockClient { return Ok(None); } - let mut frame_set = FrameSet::default(); - - self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set) + self.enqueue_packet_internal(&CPlayStatus::LoginSuccess) .await; let br_config = &server.advanced_config.resource_pack.bedrock; @@ -289,10 +286,7 @@ impl BedrockClient { world_template_version: String::new(), resource_packs: entries, }; - self.write_game_packet_to_set(&packs_info, &mut frame_set) - .await; - - self.send_frame_set(frame_set, 0x84).await; + self.enqueue_packet_internal(&packs_info).await; let new_config = PlayerConfig { locale: client_data.language_code.clone(), @@ -327,10 +321,7 @@ impl BedrockClient { } SResourcePackResponse::STATUS_HAVE_ALL_PACKS => { debug!("Bedrock: SResourcePackResponse::STATUS_HAVE_ALL_PACKS"); - let mut frame_set = FrameSet::default(); - let br_config = &server.advanced_config.resource_pack.bedrock; - // Convert your config packs into protocol stack entries let resource_packs = if br_config.enabled { br_config @@ -346,22 +337,17 @@ impl BedrockClient { Vec::new() }; - self.write_game_packet_to_set( - &CResourcePackStackPacket::new( - br_config.force, - resource_packs, - CURRENT_BEDROCK_MC_VERSION.to_string(), - Experiments { - names_size: 0, - experiments_ever_toggled: false, - }, - false, - ), - &mut frame_set, - ) + self.enqueue_packet_internal(&CResourcePackStackPacket::new( + br_config.force, + resource_packs, + CURRENT_BEDROCK_MC_VERSION.to_string(), + Experiments { + names_size: 0, + experiments_ever_toggled: false, + }, + false, + )) .await; - - self.send_frame_set(frame_set, 0x84).await; } SResourcePackResponse::STATUS_COMPLETED => { debug!("Bedrock: SResourcePackResponse::STATUS_COMPLETED"); diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index fa47631b7..8b3c0104d 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -18,14 +18,17 @@ use pumpkin_config::networking::compression::CompressionInfo; use pumpkin_protocol::{ BClientPacket, PacketDecodeError, RawPacket, bedrock::{ - MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RakReliability, SPLIT_FRAME_MAX_CONTENT, - SubClient, UDP_HEADER_SIZE, + MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RAKNET_VALID, RakReliability, + SPLIT_FRAME_MAX_CONTENT, SubClient, UDP_HEADER_SIZE, ack::Acknowledge, client::{ disconnect_player::CDisconnectPlayer, level_chunk::CLevelChunk, play_status::CPlayStatus, - raknet::connection::CConnectionRequestAccepted, + raknet::connection::{ + CAlreadyConnected, CConnectionBanned, CConnectionRequestAccepted, CDisconnect, + CNoFreeIncomingConnections, + }, resource_packs_info::{CResourcePacksInfo, ResourcePackEntry}, }, frame_set::{Frame, FrameSet}, @@ -48,10 +51,11 @@ use pumpkin_protocol::{ player_auth_input::SPlayerAuthInput, raknet::{ connection::{ - SConnectedPing, SConnectionRequest, SDisconnect, SNewIncomingConnection, + SConnectedPing, SConnectionLost, SConnectionRequest, SDisconnect, + SNewIncomingConnection, }, open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2}, - unconnected_ping::SUnconnectedPing, + unconnected_ping::{SUnconnectedPing, SUnconnectedPingOpenConnections}, }, request_ability::SRequestAbility, request_chunk_radius::SRequestChunkRadius, @@ -241,7 +245,7 @@ impl BedrockClient { let mut interval = tokio::time::interval(std::time::Duration::from_millis(100)); while !client.close_token.is_cancelled() { - let packet = tokio::select! { + let mut packet = tokio::select! { biased; () = client.close_token.cancelled() => break, res = priority_packet_receiver.recv() => match res { @@ -283,7 +287,7 @@ impl BedrockClient { } if !resend.is_empty() { - let encoder = client.network_writer.write().await; + let encoder = client.network_writer.read().await; for (seq, id, data) in resend { debug!("Resending reliable sequence {} (ID: {})", seq, id); if let Err(err) = encoder.write_packet(&data, client.address, &client.socket).await { @@ -299,6 +303,19 @@ impl BedrockClient { }, }; + // Encrypt the packet payload if encryption is enabled. + if packet.data.len() > 1 && packet.data[0] == 0xfe { + let mut encoder = client.network_writer.write().await; + if let Some(encryptor) = encoder.encryptor_mut() { + let mut data_to_encrypt = packet.data[1..].to_vec(); + encryptor.encrypt(&mut data_to_encrypt); + let mut encrypted_payload = Vec::with_capacity(1 + data_to_encrypt.len()); + encrypted_payload.push(0xfe); + encrypted_payload.extend_from_slice(&data_to_encrypt); + packet.data = encrypted_payload.into(); + } + } + client .send_framed_packet_data(packet.data.to_vec(), RakReliability::ReliableOrdered) .await; @@ -388,7 +405,7 @@ impl BedrockClient { let mut packets_to_enqueue = Vec::with_capacity(encoded_payloads.len()); { - let mut encoder = self.network_writer.write().await; + let encoder = self.network_writer.read().await; for payload in encoded_payloads { let mut packet_buf = Vec::new(); match encoder.write_game_packet( @@ -446,7 +463,7 @@ impl BedrockClient { } { - let Ok(mut network_writer) = self.network_writer.try_write() else { + let Ok(network_writer) = self.network_writer.try_read() else { debug!("Failed to lock network writer for try_enqueue_packet"); return; }; @@ -523,7 +540,7 @@ impl BedrockClient { let mut packet_payload = Vec::new(); packet.write_packet(&mut packet_payload)?; - let mut encoder = self.network_writer.write().await; + let encoder = self.network_writer.read().await; encoder.write_game_packet( P::PACKET_ID as u16, SubClient::Main, @@ -707,6 +724,10 @@ impl BedrockClient { return; } self.close_token.cancel(); + + self.send_framed_packet(&CDisconnect, RakReliability::Unreliable) + .await; + self.be_clients.lock().await.remove(&self.address); } @@ -758,7 +779,7 @@ impl BedrockClient { RAKNET_NACK => { self.handle_nack(&Acknowledge::read(reader)?).await; } - 0x80..=0x8d => { + RAKNET_VALID..=0x8d => { self.handle_frame_set(server, FrameSet::read(reader)?) .await?; } @@ -1020,8 +1041,7 @@ impl BedrockClient { }; let pending = self.pending_profile.lock().await.take(); if let Some((profile, new_config)) = pending { - let mut frame_set = FrameSet::default(); - self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set) + self.enqueue_packet_internal(&CPlayStatus::LoginSuccess) .await; let br_config = &server.advanced_config.resource_pack.bedrock; @@ -1052,10 +1072,7 @@ impl BedrockClient { world_template_version: String::new(), resource_packs: entries, }; - self.write_game_packet_to_set(&packs_info, &mut frame_set) - .await; - - self.send_frame_set(frame_set, 0x84).await; + self.enqueue_packet_internal(&packs_info).await; return PacketHandlerResult::ReadyToPlay(profile, new_config); } error!("Received ClientToServerHandshake but no pending profile was found."); @@ -1226,7 +1243,7 @@ impl BedrockClient { self.handle_connected_ping(SConnectedPing::read(reader)?) .await; } - SDisconnect::PACKET_ID => { + SDisconnect::PACKET_ID | SConnectionLost::PACKET_ID => { self.close().await; } _ => { @@ -1236,6 +1253,7 @@ impl BedrockClient { Ok(()) } + #[expect(clippy::too_many_lines)] pub async fn handle_offline_packet( server: &Server, packet_id: u8, @@ -1246,6 +1264,38 @@ impl BedrockClient { ) -> Result<(), Error> { let packet_id_i32 = i32::from(packet_id); if packet_id_i32 == SOpenConnectionRequest1::PACKET_ID { + let is_banned = { + let mut banned_ips = server.data.banned_ip_list.write().await; + banned_ips.get_entry(&addr.ip()).is_some() + }; + if is_banned { + Self::send_offline_packet( + &CConnectionBanned::new(server.server_guid), + addr, + socket, + ) + .await; + return Ok(()); + } + + let player_count = { + let status = server.get_status().lock().await; + status + .status_response + .players + .as_ref() + .map_or(0, |p| p.online) as u32 + }; + if player_count >= server.basic_config.max_players { + Self::send_offline_packet( + &CNoFreeIncomingConnections::new(server.server_guid), + addr, + socket, + ) + .await; + return Ok(()); + } + let old_client = { let mut clients_guard = be_clients.lock().await; clients_guard.remove(&addr) @@ -1269,6 +1319,20 @@ impl BedrockClient { ) .await; } + SUnconnectedPingOpenConnections::PACKET_ID => { + let packet = SUnconnectedPingOpenConnections::read(payload)?; + Self::handle_unconnected_ping( + server, + SUnconnectedPing { + time: packet.time, + magic: packet.magic, + client_guid: packet.client_guid, + }, + addr, + socket, + ) + .await; + } SOpenConnectionRequest1::PACKET_ID => { Self::handle_open_connection_1( server, @@ -1279,6 +1343,20 @@ impl BedrockClient { .await; } SOpenConnectionRequest2::PACKET_ID => { + let is_already_connected = { + let clients_guard = be_clients.lock().await; + clients_guard.contains_key(&addr) + }; + if is_already_connected { + Self::send_offline_packet( + &CAlreadyConnected::new(server.server_guid), + addr, + socket, + ) + .await; + return Ok(()); + } + Self::handle_open_connection_2( server, SOpenConnectionRequest2::read(payload)?, diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index f52bccff7..9a5924923 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -57,21 +57,45 @@ use crate::{ use pumpkin_data::BlockDirection; use tracing::{debug, info}; -fn descriptor_to_stack(desc: &NetworkItemDescriptor) -> ItemStack { +fn descriptor_to_stack(desc: &NetworkItemDescriptor, is_creative: bool) -> ItemStack { if desc.id.0 == 0 || desc.stack_size == 0 { ItemStack::EMPTY.clone() - } else if let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( - desc.id.0 as i16, - desc.aux_value.0, - ) { - ItemStack::new(desc.stack_size as u8, mapping.java_item) } else { - tracing::warn!( - "Failed to map bedrock item id {} and data {} to Java item", - desc.id.0, - desc.aux_value.0 - ); - ItemStack::EMPTY.clone() + let mut mapped_item = None; + + if is_creative { + let index = (desc.id.0.saturating_sub(1)) as usize; + if index < pumpkin_data::bedrock_creative::CREATIVE_ENTRIES.len() { + let entry = pumpkin_data::bedrock_creative::CREATIVE_ENTRIES[index]; + if let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( + entry.item_id, + entry.item_aux_value, + ) { + mapped_item = Some(mapping.java_item); + } + } + } + + if mapped_item.is_none() + && let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( + desc.id.0 as i16, + desc.aux_value.0, + ) + { + mapped_item = Some(mapping.java_item); + } + + mapped_item.map_or_else( + || { + tracing::warn!( + "Failed to map bedrock item id {} and data {} to Java item", + desc.id.0, + desc.aux_value.0 + ); + ItemStack::EMPTY.clone() + }, + |item| ItemStack::new(desc.stack_size as u8, item), + ) } } @@ -321,13 +345,15 @@ impl BedrockClient { } if input_data.get(InputData::StartFlying as usize) { - let mut abilities = player.abilities.lock().await; - if !abilities.flying { + let flying = { player.abilities.lock().await.flying }; + if !flying { send_cancellable! {{ server; PlayerToggleFlightEvent::new(player.clone(), true); 'after: { - abilities.flying = true; + { + player.abilities.lock().await.flying = true; + }; player.send_abilities_update().await; } 'cancelled: { @@ -336,13 +362,15 @@ impl BedrockClient { }} } } else if input_data.get(InputData::StopFlying as usize) { - let mut abilities = player.abilities.lock().await; - if abilities.flying { + let flying = { player.abilities.lock().await.flying }; + if flying { send_cancellable! {{ server; PlayerToggleFlightEvent::new(player.clone(), false); 'after: { - abilities.flying = false; + { + player.abilities.lock().await.flying = false; + }; player.send_abilities_update().await; } 'cancelled: { @@ -504,12 +532,13 @@ impl BedrockClient { player_screen_handler.send_content_updates().await; } + let is_creative = player.gamemode.load() == GameMode::Creative; for action in &packet.actions { use pumpkin_protocol::bedrock::server::inventory_transaction::InventoryActionSource; let source_type = InventoryActionSource::from(action.source_type); if source_type == InventoryActionSource::World { - let old_stack = descriptor_to_stack(&action.old_item); - let new_stack = descriptor_to_stack(&action.new_item); + let old_stack = descriptor_to_stack(&action.old_item, is_creative); + let new_stack = descriptor_to_stack(&action.new_item, is_creative); if old_stack.is_empty() && !new_stack.is_empty() { player.drop_item(new_stack).await; } @@ -517,7 +546,7 @@ impl BedrockClient { if let Some(screen_slot) = map_bedrock_slot_to_screen_handler(window_id, action.inventory_slot) { - let item_stack = descriptor_to_stack(&action.new_item); + let item_stack = descriptor_to_stack(&action.new_item, is_creative); let mut player_screen_handler = player.player_screen_handler.lock().await; @@ -610,7 +639,16 @@ impl BedrockClient { if data.action_type.0 == 0 { // Click block + let is_creative = player.gamemode.load() == GameMode::Creative; + let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative); + let held_item = player.inventory.held_item(); + if !client_stack.is_empty() { + let mut server_stack = held_item.lock().await; + if server_stack.is_empty() || server_stack.item.id != client_stack.item.id { + *server_stack = client_stack.clone(); + } + } let result = server .block_registry @@ -648,6 +686,53 @@ impl BedrockClient { ) .await; } + + let mut stack = held_item.lock().await; + if !stack.is_empty() { + server + .item_registry + .use_on_block( + &mut stack, + player, + data.block_position, + face, + data.click_position, + block, + &server, + ) + .await; + + let item_id = stack.item.id; + if let Some(placed_block) = pumpkin_data::Block::from_item_id(item_id) { + let dummy_use_item_on = + pumpkin_protocol::java::server::play::SUseItemOn { + hand: VarInt(0), + position: data.block_position, + face: VarInt(data.block_face), + cursor_pos: data.click_position, + inside_block: false, + is_against_world_border: false, + sequence: VarInt(0), + }; + + if let Ok(Some(_)) = server + .block_registry + .place_block( + player, + placed_block, + &server, + &dummy_use_item_on, + data.block_position, + face, + ) + .await + { + if player.gamemode.load() != GameMode::Creative { + stack.decrement(1); + } + } + } + } } } TransactionData::UseItemOnEntity(data) => { diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index 53cbb568e..8cba6eabe 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -12,7 +12,7 @@ use tracing::{Level, debug, error, info, trace, warn}; use crate::block::BlockHitResult; use crate::block::registry::BlockActionResult; -use crate::block::{self, BlockIsReplacing}; +use crate::block::{self}; use crate::entity::EntityBase; use crate::entity::equipment_break_status; use crate::entity::player::statistics::{CustomStatistic, StatisticCategory}; @@ -21,7 +21,6 @@ use crate::error::PumpkinError; use crate::log_at_level; use crate::net::PlayerConfig; use crate::net::java::JavaClient; -use crate::plugin::block::block_place::BlockPlaceEvent; use crate::plugin::player::changed_main_hand::PlayerChangedMainHandEvent; use crate::plugin::player::fish::{PlayerFishEvent, PlayerFishState}; use crate::plugin::player::item_held::PlayerItemHeldEvent; @@ -40,17 +39,14 @@ use crate::block::entities::sign::SignBlockEntity; use crate::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent; use crate::server::{Server, seasonal_events}; use crate::world::{World, chunker}; -use pumpkin_data::block_properties::{ - BlockProperties, CommandBlockLikeProperties, WaterLikeProperties, -}; +use pumpkin_data::block_properties::{BlockProperties, CommandBlockLikeProperties}; use pumpkin_data::data_component_impl::{ BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl, }; -use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::sound::{Sound, SoundCategory}; -use pumpkin_data::{Advancement, Block, BlockDirection, BlockState, translation}; +use pumpkin_data::{Advancement, Block, BlockDirection, translation}; use pumpkin_inventory::InventoryError; use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler; use pumpkin_inventory::player::player_inventory::PlayerInventory; @@ -77,10 +73,8 @@ use pumpkin_protocol::java::server::play::{ SSetJigsawBlock, SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity, STestInstanceBlockAction, SUpdateSign, SUseItem, SUseItemOn, Status, }; -use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::{polynomial_rolling_hash, position::BlockPos, wrap_degrees}; -use pumpkin_util::text::color::NamedColor; use pumpkin_util::{GameMode, text::TextComponent}; use pumpkin_world::generation::structure::structures::jigsaw::JigsawJointType; use pumpkin_world::world::BlockFlags; @@ -2711,55 +2705,6 @@ impl JavaClient { ); } - fn entity_blocks_block_placement(entity: &dyn EntityBase) -> bool { - let base_entity = entity.get_entity(); - if base_entity.is_removed() - || base_entity.no_clip.load(Ordering::Relaxed) - || entity.is_spectator() - { - return false; - } - - if entity.get_living_entity().is_some() { - return true; - } - - // Matches vanilla's "blocksBuilding" intent for non-living entities: - // minecarts/boats/rafts + a few special entities. - let entity_type = base_entity.entity_type; - let resource_name = entity_type.resource_name; - entity_type == &EntityType::END_CRYSTAL - || entity_type == &EntityType::FALLING_BLOCK - || entity_type == &EntityType::TNT - || resource_name.ends_with("_minecart") - || resource_name.ends_with("_boat") - || resource_name.ends_with("_raft") - } - - fn has_blocking_entity_in_box(world: &World, placed_box: &BoundingBox) -> bool { - let players = world.players.load(); - if players.iter().any(|player| { - Self::entity_blocks_block_placement(player.as_ref()) - && player - .get_entity() - .bounding_box - .load() - .intersects(placed_box) - }) { - return true; - } - - world.entities.load().iter().any(|entity| { - Self::entity_blocks_block_placement(entity.as_ref()) - && entity - .get_entity() - .bounding_box - .load() - .intersects(placed_box) - }) - } - - #[expect(clippy::too_many_lines)] async fn run_is_block_place( &self, player: &Arc, @@ -2769,171 +2714,27 @@ impl JavaClient { location: BlockPos, face: BlockDirection, ) -> Result { - let entity = &player.get_entity(); - - match player.gamemode.load() { - GameMode::Spectator | GameMode::Adventure => { - return Err(BlockPlacingError::InvalidGamemode); - } - _ => {} - } - - let clicked_block_pos = BlockPos(location.0); - let world = entity.world.load_full(); - - // Check if the block is under the world - if location.0.y + face.to_offset().y < world.get_bottom_y() { - return Err(BlockPlacingError::BlockOutOfWorld); - } - - // Check the world's max build height - if location.0.y + face.to_offset().y > world.get_top_y() { - player - .send_system_message_raw( - &TextComponent::translate_cross( - translation::java::BUILD_TOOHIGH, - translation::bedrock::BUILD_TOOHIGH, - vec![TextComponent::text((world.get_top_y()).to_string())], - ) - .color_named(NamedColor::Red), - true, - ) + match server + .block_registry + .place_block(player, block, server, &use_item_on, location, face) + .await + { + Ok(Some((final_block_pos, new_state))) => { + self.send_packet_now(&CBlockUpdate::new( + final_block_pos, + VarInt(i32::from(new_state.as_u16())), + )) .await; - return Err(BlockPlacingError::BlockOutOfWorld); - } - - let (clicked_block, clicked_block_state) = world.get_block_and_state(&clicked_block_pos); - - let replace_clicked_block = if clicked_block == block { - world - .block_registry - .can_update_at( - &world, - clicked_block, - clicked_block_state.id, - &clicked_block_pos, - face, - &use_item_on, - player, - ) - .then_some(BlockIsReplacing::Itself(clicked_block_state.id)) - } else if clicked_block_state.replaceable() { - if clicked_block == &Block::WATER { - let water_props = - WaterLikeProperties::from_state_id(clicked_block_state.id, clicked_block); - Some(BlockIsReplacing::Water(water_props.level)) - } else { - Some(BlockIsReplacing::Other) + Ok(true) } - } else { - None - }; - - let (final_block_pos, final_face, replacing) = - if let Some(replacing) = replace_clicked_block { - (clicked_block_pos, face.opposite(), replacing) - } else { - let block_pos = BlockPos(location.0 + face.to_offset()); - let (previous_block, previous_block_state) = world.get_block_and_state(&block_pos); - - let replace_previous_block = if previous_block == block { - world - .block_registry - .can_update_at( - &world, - previous_block, - previous_block_state.id, - &block_pos, - face.opposite(), - &use_item_on, - player, - ) - .then_some(BlockIsReplacing::Itself(previous_block_state.id)) - } else { - previous_block_state.replaceable().then(|| { - if previous_block == &Block::WATER { - let water_props = WaterLikeProperties::from_state_id( - previous_block_state.id, - previous_block, - ); - BlockIsReplacing::Water(water_props.level) - } else { - BlockIsReplacing::None - } - }) - }; - - match replace_previous_block { - Some(replacing) => (block_pos, face.opposite(), replacing), - None => { - // Don't place and don't decrement if the previous block is not replaceable - return Ok(false); - } - } - }; - - if !server.block_registry.can_place_at( - Some(server), - Some(&*world), - &*world, - Some(player), - block, - block.default_state, - &final_block_pos, - Some(final_face), - Some(&use_item_on), - ) { - return Ok(false); - } - - let new_state = server - .block_registry - .on_place( - server, - &world, - player, - block, - &final_block_pos, - final_face, - replacing, - &use_item_on, - ) - .await; - - // Mirror vanilla obstruction checks: only entities that block building should prevent - // placement. (e.g. arrows/xp orbs/displays/markers should not) - let state = BlockState::from_id(new_state); - for shape in state.get_block_collision_shapes() { - let placed_box = shape.at_pos(final_block_pos); - - if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) { - return Ok(false); + Ok(None) => Ok(false), + Err(crate::block::registry::BlockPlacingError::InvalidGamemode) => { + Err(BlockPlacingError::InvalidGamemode) + } + Err(crate::block::registry::BlockPlacingError::BlockOutOfWorld) => { + Err(BlockPlacingError::BlockOutOfWorld) } } - - let event = - BlockPlaceEvent::new(player.clone(), block, clicked_block, final_block_pos, true); - let event = server.plugin_manager.fire::(event).await; - if event.cancelled { - return Ok(false); - } - - let _replaced_id = world - .set_block_state(&final_block_pos, new_state, BlockFlags::NOTIFY_ALL) - .await; - self.send_packet_now(&CBlockUpdate::new( - final_block_pos, - VarInt(i32::from(new_state.as_u16())), - )) - .await; - - server - .block_registry - .player_placed(&world, block, new_state, &final_block_pos, face, player) - .await; - - // The block was placed successfully, so decrement their inventory - Ok(true) } /// Checks if the block placed was a sign, then opens a dialog. diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index f9e8232b9..ed678167b 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -79,7 +79,6 @@ use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData}; use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData}; -use pumpkin_protocol::bedrock::frame_set::FrameSet; use pumpkin_protocol::java::client::play::{ CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData, @@ -2222,79 +2221,74 @@ impl World { .await; }; - let mut frame_set = FrameSet::default(); client - .write_game_packet_to_set( - &CUpdateAttributes { - runtime_id: VarULong(runtime_id), - attributes: vec![ - Attribute { - min_value: 0.0, - max_value: 3.402_823_5E38, - current_value: 0.1, - default_min_value: 0.0, - default_max_value: 3.402_823_5E38, - default_value: 0.1, - name: "minecraft:movement".to_string(), - modifiers_list_size: VarUInt(0), - }, - Attribute { - min_value: 0.0, - max_value: 3.402_823_5E38, - current_value: 0.02, - default_min_value: 0.0, - default_max_value: 3.402_823_5E38, - default_value: 0.02, - name: "minecraft:underwater_movement".to_string(), - modifiers_list_size: VarUInt(0), - }, - Attribute { - min_value: 0.0, - max_value: 1.0, - current_value: 0.08, - default_min_value: 0.0, - default_max_value: 1.0, - default_value: 0.08, - name: "minecraft:gravity".to_string(), - modifiers_list_size: VarUInt(0), - }, - Attribute { - min_value: 0.0, - max_value: 400.0, - current_value: 400.0, - default_min_value: 0.0, - default_max_value: 400.0, - default_value: 400.0, - name: "minecraft:air".to_string(), - modifiers_list_size: VarUInt(0), - }, - Attribute { - min_value: 0.0, - max_value: 20.0, - current_value: 20.0, - default_min_value: 0.0, - default_max_value: 20.0, - default_value: 20.0, - name: "minecraft:health".to_string(), - modifiers_list_size: VarUInt(0), - }, - Attribute { - min_value: 0.0, - max_value: 20.0, - current_value: 20.0, - default_min_value: 0.0, - default_max_value: 20.0, - default_value: 20.0, - name: "minecraft:player.hunger".to_string(), - modifiers_list_size: VarUInt(0), - }, - ], - player_tick: VarULong(0), - }, - &mut frame_set, - ) + .enqueue_packet_internal(&CUpdateAttributes { + runtime_id: VarULong(runtime_id), + attributes: vec![ + Attribute { + min_value: 0.0, + max_value: 3.402_823_5E38, + current_value: 0.1, + default_min_value: 0.0, + default_max_value: 3.402_823_5E38, + default_value: 0.1, + name: "minecraft:movement".to_string(), + modifiers_list_size: VarUInt(0), + }, + Attribute { + min_value: 0.0, + max_value: 3.402_823_5E38, + current_value: 0.02, + default_min_value: 0.0, + default_max_value: 3.402_823_5E38, + default_value: 0.02, + name: "minecraft:underwater_movement".to_string(), + modifiers_list_size: VarUInt(0), + }, + Attribute { + min_value: 0.0, + max_value: 1.0, + current_value: 0.08, + default_min_value: 0.0, + default_max_value: 1.0, + default_value: 0.08, + name: "minecraft:gravity".to_string(), + modifiers_list_size: VarUInt(0), + }, + Attribute { + min_value: 0.0, + max_value: 400.0, + current_value: 400.0, + default_min_value: 0.0, + default_max_value: 400.0, + default_value: 400.0, + name: "minecraft:air".to_string(), + modifiers_list_size: VarUInt(0), + }, + Attribute { + min_value: 0.0, + max_value: 20.0, + current_value: 20.0, + default_min_value: 0.0, + default_max_value: 20.0, + default_value: 20.0, + name: "minecraft:health".to_string(), + modifiers_list_size: VarUInt(0), + }, + Attribute { + min_value: 0.0, + max_value: 20.0, + current_value: 20.0, + default_min_value: 0.0, + default_max_value: 20.0, + default_value: 20.0, + name: "minecraft:player.hunger".to_string(), + modifiers_list_size: VarUInt(0), + }, + ], + player_tick: VarULong(0), + }) .await; - client.send_frame_set(frame_set, 0x84).await; // --- MULTIPLAYER BROADCASTING ---