chore: implement all missing raknet packets
This commit is contained in:
parent
560a0ed77d
commit
4ec2315aef
@ -12,7 +12,7 @@ use pumpkin_protocol::bedrock::server::{
|
|||||||
raknet::{
|
raknet::{
|
||||||
connection::SConnectionRequest,
|
connection::SConnectionRequest,
|
||||||
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
||||||
unconnected_ping::SUnconnectedPing,
|
unconnected_ping::{SUnconnectedPing, SUnconnectedPingOpenConnections},
|
||||||
},
|
},
|
||||||
request_chunk_radius::SRequestChunkRadius,
|
request_chunk_radius::SRequestChunkRadius,
|
||||||
request_network_settings::SRequestNetworkSettings,
|
request_network_settings::SRequestNetworkSettings,
|
||||||
@ -60,6 +60,8 @@ fn fuzz_serverbound_packets(payload: &[u8]) {
|
|||||||
let _ = SOpenConnectionRequest2::read(&mut cursor);
|
let _ = SOpenConnectionRequest2::read(&mut cursor);
|
||||||
cursor.set_position(0);
|
cursor.set_position(0);
|
||||||
let _ = SUnconnectedPing::read(&mut cursor);
|
let _ = SUnconnectedPing::read(&mut cursor);
|
||||||
|
cursor.set_position(0);
|
||||||
|
let _ = SUnconnectedPingOpenConnections::read(&mut cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
25
pumpkin-protocol/src/bedrock/client/change_dimension.rs
Normal file
25
pumpkin-protocol/src/bedrock/client/change_dimension.rs
Normal file
@ -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<f32>,
|
||||||
|
pub respawn: bool,
|
||||||
|
pub has_loading_screen_id: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CChangeDimension {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(dimension: i32, position: Vector3<f32>, respawn: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
dimension: VarInt(dimension),
|
||||||
|
position,
|
||||||
|
respawn,
|
||||||
|
has_loading_screen_id: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ pub mod add_item_actor;
|
|||||||
pub mod add_player;
|
pub mod add_player;
|
||||||
pub mod available_commands;
|
pub mod available_commands;
|
||||||
pub mod boss_event;
|
pub mod boss_event;
|
||||||
|
pub mod change_dimension;
|
||||||
pub mod chunk_radius_update;
|
pub mod chunk_radius_update;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod container_open;
|
pub mod container_open;
|
||||||
@ -50,6 +51,7 @@ pub use add_item_actor::*;
|
|||||||
pub use add_player::*;
|
pub use add_player::*;
|
||||||
pub use available_commands::*;
|
pub use available_commands::*;
|
||||||
pub use boss_event::*;
|
pub use boss_event::*;
|
||||||
|
pub use change_dimension::*;
|
||||||
pub use chunk_radius_update::*;
|
pub use chunk_radius_update::*;
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use container_open::*;
|
pub use container_open::*;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::net::SocketAddr;
|
|||||||
|
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
use crate::serial::PacketWrite;
|
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x03)]
|
#[packet(0x03)]
|
||||||
pub struct CConnectedPong {
|
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;
|
||||||
|
|||||||
@ -44,6 +44,7 @@ impl PacketRead for NetworkItemDescriptor {
|
|||||||
|
|
||||||
let has_net_id = bool::read(buf)?;
|
let has_net_id = bool::read(buf)?;
|
||||||
if has_net_id {
|
if has_net_id {
|
||||||
|
let _variant = VarUInt::read(buf)?;
|
||||||
let _net_id = VarInt::read(buf)?;
|
let _net_id = VarInt::read(buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -226,7 +226,7 @@ mod tests {
|
|||||||
const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1;
|
const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1;
|
||||||
let payload = vec![0x2a; PAYLOAD_LEN];
|
let payload = vec![0x2a; PAYLOAD_LEN];
|
||||||
let mut wire_buf = Vec::new();
|
let mut wire_buf = Vec::new();
|
||||||
let mut network_encoder = UDPNetworkEncoder::new();
|
let network_encoder = UDPNetworkEncoder::new();
|
||||||
network_encoder
|
network_encoder
|
||||||
.write_game_packet(
|
.write_game_packet(
|
||||||
0x01,
|
0x01,
|
||||||
|
|||||||
@ -119,8 +119,12 @@ impl UDPNetworkEncoder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn encryptor_mut(&mut self) -> Option<&mut BedrockEncryptor> {
|
||||||
|
self.encryptor.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_game_packet(
|
pub fn write_game_packet(
|
||||||
&mut self,
|
&self,
|
||||||
packet_id: u16,
|
packet_id: u16,
|
||||||
sub_client_sender: SubClient,
|
sub_client_sender: SubClient,
|
||||||
sub_client_target: SubClient,
|
sub_client_target: SubClient,
|
||||||
@ -151,26 +155,22 @@ impl UDPNetworkEncoder {
|
|||||||
.write_u8(0xfe)
|
.write_u8(0xfe)
|
||||||
.map_err(|e| Error::other(e.to_string()))?; // Bedrock Game Packet Header
|
.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 {
|
if let Some((_threshold, level)) = self.compression {
|
||||||
// Write Compression Method (0x00 for Zlib)
|
// 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));
|
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(level));
|
||||||
encoder.write_all(&inner_buffer)?;
|
encoder.write_all(&inner_buffer)?;
|
||||||
let compressed_data = encoder.finish()?;
|
let compressed_data = encoder.finish()?;
|
||||||
|
|
||||||
data_to_encrypt.extend_from_slice(&compressed_data);
|
data_to_write.extend_from_slice(&compressed_data);
|
||||||
} else {
|
} else {
|
||||||
data_to_encrypt.extend_from_slice(&inner_buffer);
|
data_to_write.extend_from_slice(&inner_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(encryptor) = &mut self.encryptor {
|
writer.write_all(&data_to_write)?;
|
||||||
encryptor.encrypt(&mut data_to_encrypt);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write_all(&data_to_encrypt)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,8 @@ pub struct SPlayerAuthInput {
|
|||||||
pub tick: VarULong,
|
pub tick: VarULong,
|
||||||
pub delta: Vector3<f32>,
|
pub delta: Vector3<f32>,
|
||||||
pub block_actions: Option<Vec<PlayerBlockAction>>,
|
pub block_actions: Option<Vec<PlayerBlockAction>>,
|
||||||
|
pub item_interaction: Option<PlayerInventoryAction>,
|
||||||
|
pub item_stack_request: Option<crate::bedrock::server::item_stack_request::ItemStackRequest>,
|
||||||
pub vehicle_rotation: Option<Vector2<f32>>,
|
pub vehicle_rotation: Option<Vector2<f32>>,
|
||||||
pub vehicle_unique_id: Option<VarLong>,
|
pub vehicle_unique_id: Option<VarLong>,
|
||||||
pub analog_move: Vector2<f32>,
|
pub analog_move: Vector2<f32>,
|
||||||
@ -52,22 +54,18 @@ impl PacketRead for SPlayerAuthInput {
|
|||||||
let delta = Vector3::<f32>::read(reader)?;
|
let delta = Vector3::<f32>::read(reader)?;
|
||||||
|
|
||||||
// 1. Perform Item Interaction
|
// 1. Perform Item Interaction
|
||||||
if input_data.get(InputData::PerformItemInteraction as usize) {
|
let item_interaction = if input_data.get(InputData::PerformItemInteraction as usize) {
|
||||||
// protocol.UseItemTransactionData (Simplified skip)
|
Some(PlayerInventoryAction::read(reader)?)
|
||||||
let _action_type = VarUInt::read(reader)?;
|
} else {
|
||||||
let action_count = VarUInt::read(reader)?.0;
|
None
|
||||||
for _ in 0..action_count {
|
};
|
||||||
let _type = VarUInt::read(reader)?;
|
|
||||||
let _pos = BlockPos::read(reader)?;
|
|
||||||
let _face = VarInt::read(reader)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Item Stack Request
|
// 2. Item Stack Request
|
||||||
if input_data.get(InputData::PerformItemStackRequest as usize) {
|
let item_stack_request = if input_data.get(InputData::PerformItemStackRequest as usize) {
|
||||||
// protocol.Single ItemStackRequest
|
Some(crate::bedrock::server::item_stack_request::ItemStackRequest::read(reader)?)
|
||||||
return Err(Error::other("ItemStackRequest decoding not implemented"));
|
} else {
|
||||||
}
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// 3. Block Actions
|
// 3. Block Actions
|
||||||
let block_actions = if input_data.get(InputData::PerformBlockActions as usize) {
|
let block_actions = if input_data.get(InputData::PerformBlockActions as usize) {
|
||||||
@ -109,6 +107,8 @@ impl PacketRead for SPlayerAuthInput {
|
|||||||
tick,
|
tick,
|
||||||
delta,
|
delta,
|
||||||
block_actions,
|
block_actions,
|
||||||
|
item_interaction,
|
||||||
|
item_stack_request,
|
||||||
vehicle_rotation,
|
vehicle_rotation,
|
||||||
vehicle_unique_id,
|
vehicle_unique_id,
|
||||||
analog_move,
|
analog_move,
|
||||||
@ -118,6 +118,43 @@ impl PacketRead for SPlayerAuthInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PlayerInventoryAction {
|
||||||
|
pub legacy_request_id: VarInt,
|
||||||
|
pub legacy_slots: Vec<crate::bedrock::server::inventory_transaction::LegacySetItemSlot>,
|
||||||
|
pub actions: Vec<crate::bedrock::server::inventory_transaction::InventoryAction>,
|
||||||
|
pub transaction: crate::bedrock::server::inventory_transaction::UseItemTransactionData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketRead for PlayerInventoryAction {
|
||||||
|
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
||||||
|
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)]
|
#[derive(Debug, PacketRead)]
|
||||||
pub struct PlayerBlockAction {
|
pub struct PlayerBlockAction {
|
||||||
pub action: VarInt,
|
pub action: VarInt,
|
||||||
|
|||||||
@ -35,3 +35,6 @@ pub struct SNewIncomingConnection {
|
|||||||
|
|
||||||
#[packet(0x15)]
|
#[packet(0x15)]
|
||||||
pub struct SDisconnect;
|
pub struct SDisconnect;
|
||||||
|
|
||||||
|
#[packet(0x16)]
|
||||||
|
pub struct SConnectionLost;
|
||||||
|
|||||||
@ -12,3 +12,14 @@ pub struct SUnconnectedPing {
|
|||||||
#[serial(big_endian)]
|
#[serial(big_endian)]
|
||||||
pub client_guid: u64,
|
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,
|
||||||
|
}
|
||||||
|
|||||||
@ -385,7 +385,242 @@ pub struct BlockRegistry {
|
|||||||
fluids: FxHashMap<u16, Arc<dyn FluidBehaviour>>,
|
fluids: FxHashMap<u16, Arc<dyn FluidBehaviour>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BlockPlacingError {
|
||||||
|
InvalidGamemode,
|
||||||
|
BlockOutOfWorld,
|
||||||
|
}
|
||||||
|
|
||||||
impl BlockRegistry {
|
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<Player>,
|
||||||
|
placed_block: &'static Block,
|
||||||
|
server: &Server,
|
||||||
|
use_item_on: &SUseItemOn,
|
||||||
|
location: BlockPos,
|
||||||
|
face: BlockDirection,
|
||||||
|
) -> Result<Option<(BlockPos, BlockStateId)>, 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::<crate::plugin::block::block_place::BlockPlaceEvent>(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<T: BlockBehaviour + BlockMetadata + 'static>(&mut self, block: T) {
|
pub fn register<T: BlockBehaviour + BlockMetadata + 'static>(&mut self, block: T) {
|
||||||
let ids = T::ids();
|
let ids = T::ids();
|
||||||
let val = Arc::new(block);
|
let val = Arc::new(block);
|
||||||
|
|||||||
@ -2496,8 +2496,9 @@ impl Player {
|
|||||||
last_pos.y.round() as i32,
|
last_pos.y.round() as i32,
|
||||||
last_pos.z.round() as i32,
|
last_pos.z.round() as i32,
|
||||||
));
|
));
|
||||||
self.client
|
match self.client.as_ref() {
|
||||||
.send_packet_now(&CRespawn::new(
|
ClientPlatform::Java(java) => {
|
||||||
|
java.send_packet_now(&CRespawn::new(
|
||||||
PlayerSpawnData::new(
|
PlayerSpawnData::new(
|
||||||
new_world.dimension.clone(),
|
new_world.dimension.clone(),
|
||||||
biome::hash_seed(new_world.level.seed.0), // seed
|
biome::hash_seed(new_world.level.seed.0), // seed
|
||||||
@ -2511,6 +2512,26 @@ impl Player {
|
|||||||
),
|
),
|
||||||
CRespawn::KEEP_ALL_DATA,
|
CRespawn::KEEP_ALL_DATA,
|
||||||
)).await;
|
)).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();
|
self.send_permission_lvl_update();
|
||||||
|
|
||||||
@ -4931,7 +4952,6 @@ impl InventoryPlayer for Player {
|
|||||||
}
|
}
|
||||||
ClientPlatform::Bedrock(bedrock) => {
|
ClientPlatform::Bedrock(bedrock) => {
|
||||||
use pumpkin_protocol::bedrock::{
|
use pumpkin_protocol::bedrock::{
|
||||||
client::inventory_content::CInventoryContent,
|
|
||||||
client::inventory_slot::CInventorySlot,
|
client::inventory_slot::CInventorySlot,
|
||||||
network_item::{
|
network_item::{
|
||||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||||
@ -4948,23 +4968,20 @@ impl InventoryPlayer for Player {
|
|||||||
|
|
||||||
if window_id == 0 {
|
if window_id == 0 {
|
||||||
tracing::info!(
|
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 slot_idx = packet.slot as usize;
|
||||||
let main_inventory = &self.inventory().main_inventory;
|
let item_desc = NetworkItemStackDescriptor::from(&*packet.slot_data.0);
|
||||||
for s in main_inventory {
|
|
||||||
let stack = s.lock().await;
|
|
||||||
slots.push(NetworkItemStackDescriptor::from(&*stack));
|
|
||||||
}
|
|
||||||
|
|
||||||
let bedrock_packet = CInventoryContent {
|
let bedrock_packet = CInventorySlot {
|
||||||
container_id: VarUInt(0),
|
window_id: VarUInt(0),
|
||||||
slots,
|
inventory_slot: VarUInt(slot_idx as u32),
|
||||||
full_container_name: FullContainerName {
|
container_name: Some(FullContainerName {
|
||||||
container_name: ContainerName::Inventory,
|
container_name: ContainerName::Inventory,
|
||||||
dynamic_id: None,
|
dynamic_id: None,
|
||||||
},
|
}),
|
||||||
storage_item: NetworkItemStackDescriptor::default(),
|
storage: None,
|
||||||
|
item: item_desc,
|
||||||
};
|
};
|
||||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||||
} else {
|
} else {
|
||||||
@ -5073,7 +5090,7 @@ impl InventoryPlayer for Player {
|
|||||||
}
|
}
|
||||||
ClientPlatform::Bedrock(bedrock) => {
|
ClientPlatform::Bedrock(bedrock) => {
|
||||||
use pumpkin_protocol::bedrock::{
|
use pumpkin_protocol::bedrock::{
|
||||||
client::inventory_content::CInventoryContent,
|
client::inventory_slot::CInventorySlot,
|
||||||
network_item::{
|
network_item::{
|
||||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||||
},
|
},
|
||||||
@ -5081,24 +5098,21 @@ impl InventoryPlayer for Player {
|
|||||||
use pumpkin_protocol::codec::var_uint::VarUInt;
|
use pumpkin_protocol::codec::var_uint::VarUInt;
|
||||||
|
|
||||||
tracing::info!(
|
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
|
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 {
|
let item_stack = &*packet.item.0;
|
||||||
container_id: VarUInt(0),
|
let item_desc = NetworkItemStackDescriptor::from(item_stack);
|
||||||
slots,
|
let bedrock_packet = CInventorySlot {
|
||||||
full_container_name: FullContainerName {
|
window_id: VarUInt(0),
|
||||||
|
inventory_slot: VarUInt(packet.slot.0 as u32),
|
||||||
|
container_name: Some(FullContainerName {
|
||||||
container_name: ContainerName::Inventory,
|
container_name: ContainerName::Inventory,
|
||||||
dynamic_id: None,
|
dynamic_id: None,
|
||||||
},
|
}),
|
||||||
storage_item: NetworkItemStackDescriptor::default(),
|
storage: None,
|
||||||
|
item: item_desc,
|
||||||
};
|
};
|
||||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -517,7 +517,7 @@ impl PumpkinServer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let id = udp_buf[0];
|
let id = udp_buf[0];
|
||||||
let is_online = id & 128 != 0;
|
let is_online = id & pumpkin_protocol::bedrock::RAKNET_VALID != 0;
|
||||||
|
|
||||||
if is_online {
|
if is_online {
|
||||||
let be_clients = bedrock_clients.clone();
|
let be_clients = bedrock_clients.clone();
|
||||||
|
|||||||
@ -12,7 +12,6 @@ use pumpkin_protocol::bedrock::{
|
|||||||
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
||||||
start_game::Experiments,
|
start_game::Experiments,
|
||||||
},
|
},
|
||||||
frame_set::FrameSet,
|
|
||||||
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::bedrock::{
|
use pumpkin_protocol::bedrock::{
|
||||||
@ -256,9 +255,7 @@ impl BedrockClient {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut frame_set = FrameSet::default();
|
self.enqueue_packet_internal(&CPlayStatus::LoginSuccess)
|
||||||
|
|
||||||
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
|
|
||||||
.await;
|
.await;
|
||||||
let br_config = &server.advanced_config.resource_pack.bedrock;
|
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||||
|
|
||||||
@ -289,10 +286,7 @@ impl BedrockClient {
|
|||||||
world_template_version: String::new(),
|
world_template_version: String::new(),
|
||||||
resource_packs: entries,
|
resource_packs: entries,
|
||||||
};
|
};
|
||||||
self.write_game_packet_to_set(&packs_info, &mut frame_set)
|
self.enqueue_packet_internal(&packs_info).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
|
||||||
|
|
||||||
let new_config = PlayerConfig {
|
let new_config = PlayerConfig {
|
||||||
locale: client_data.language_code.clone(),
|
locale: client_data.language_code.clone(),
|
||||||
@ -327,10 +321,7 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
SResourcePackResponse::STATUS_HAVE_ALL_PACKS => {
|
SResourcePackResponse::STATUS_HAVE_ALL_PACKS => {
|
||||||
debug!("Bedrock: 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;
|
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||||
|
|
||||||
// Convert your config packs into protocol stack entries
|
// Convert your config packs into protocol stack entries
|
||||||
let resource_packs = if br_config.enabled {
|
let resource_packs = if br_config.enabled {
|
||||||
br_config
|
br_config
|
||||||
@ -346,8 +337,7 @@ impl BedrockClient {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.write_game_packet_to_set(
|
self.enqueue_packet_internal(&CResourcePackStackPacket::new(
|
||||||
&CResourcePackStackPacket::new(
|
|
||||||
br_config.force,
|
br_config.force,
|
||||||
resource_packs,
|
resource_packs,
|
||||||
CURRENT_BEDROCK_MC_VERSION.to_string(),
|
CURRENT_BEDROCK_MC_VERSION.to_string(),
|
||||||
@ -356,12 +346,8 @@ impl BedrockClient {
|
|||||||
experiments_ever_toggled: false,
|
experiments_ever_toggled: false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
),
|
))
|
||||||
&mut frame_set,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
|
||||||
}
|
}
|
||||||
SResourcePackResponse::STATUS_COMPLETED => {
|
SResourcePackResponse::STATUS_COMPLETED => {
|
||||||
debug!("Bedrock: SResourcePackResponse::STATUS_COMPLETED");
|
debug!("Bedrock: SResourcePackResponse::STATUS_COMPLETED");
|
||||||
|
|||||||
@ -18,14 +18,17 @@ use pumpkin_config::networking::compression::CompressionInfo;
|
|||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
BClientPacket, PacketDecodeError, RawPacket,
|
BClientPacket, PacketDecodeError, RawPacket,
|
||||||
bedrock::{
|
bedrock::{
|
||||||
MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RakReliability, SPLIT_FRAME_MAX_CONTENT,
|
MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RAKNET_VALID, RakReliability,
|
||||||
SubClient, UDP_HEADER_SIZE,
|
SPLIT_FRAME_MAX_CONTENT, SubClient, UDP_HEADER_SIZE,
|
||||||
ack::Acknowledge,
|
ack::Acknowledge,
|
||||||
client::{
|
client::{
|
||||||
disconnect_player::CDisconnectPlayer,
|
disconnect_player::CDisconnectPlayer,
|
||||||
level_chunk::CLevelChunk,
|
level_chunk::CLevelChunk,
|
||||||
play_status::CPlayStatus,
|
play_status::CPlayStatus,
|
||||||
raknet::connection::CConnectionRequestAccepted,
|
raknet::connection::{
|
||||||
|
CAlreadyConnected, CConnectionBanned, CConnectionRequestAccepted, CDisconnect,
|
||||||
|
CNoFreeIncomingConnections,
|
||||||
|
},
|
||||||
resource_packs_info::{CResourcePacksInfo, ResourcePackEntry},
|
resource_packs_info::{CResourcePacksInfo, ResourcePackEntry},
|
||||||
},
|
},
|
||||||
frame_set::{Frame, FrameSet},
|
frame_set::{Frame, FrameSet},
|
||||||
@ -48,10 +51,11 @@ use pumpkin_protocol::{
|
|||||||
player_auth_input::SPlayerAuthInput,
|
player_auth_input::SPlayerAuthInput,
|
||||||
raknet::{
|
raknet::{
|
||||||
connection::{
|
connection::{
|
||||||
SConnectedPing, SConnectionRequest, SDisconnect, SNewIncomingConnection,
|
SConnectedPing, SConnectionLost, SConnectionRequest, SDisconnect,
|
||||||
|
SNewIncomingConnection,
|
||||||
},
|
},
|
||||||
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
||||||
unconnected_ping::SUnconnectedPing,
|
unconnected_ping::{SUnconnectedPing, SUnconnectedPingOpenConnections},
|
||||||
},
|
},
|
||||||
request_ability::SRequestAbility,
|
request_ability::SRequestAbility,
|
||||||
request_chunk_radius::SRequestChunkRadius,
|
request_chunk_radius::SRequestChunkRadius,
|
||||||
@ -241,7 +245,7 @@ impl BedrockClient {
|
|||||||
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
|
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
|
||||||
|
|
||||||
while !client.close_token.is_cancelled() {
|
while !client.close_token.is_cancelled() {
|
||||||
let packet = tokio::select! {
|
let mut packet = tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
() = client.close_token.cancelled() => break,
|
() = client.close_token.cancelled() => break,
|
||||||
res = priority_packet_receiver.recv() => match res {
|
res = priority_packet_receiver.recv() => match res {
|
||||||
@ -283,7 +287,7 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !resend.is_empty() {
|
if !resend.is_empty() {
|
||||||
let encoder = client.network_writer.write().await;
|
let encoder = client.network_writer.read().await;
|
||||||
for (seq, id, data) in resend {
|
for (seq, id, data) in resend {
|
||||||
debug!("Resending reliable sequence {} (ID: {})", seq, id);
|
debug!("Resending reliable sequence {} (ID: {})", seq, id);
|
||||||
if let Err(err) = encoder.write_packet(&data, client.address, &client.socket).await {
|
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
|
client
|
||||||
.send_framed_packet_data(packet.data.to_vec(), RakReliability::ReliableOrdered)
|
.send_framed_packet_data(packet.data.to_vec(), RakReliability::ReliableOrdered)
|
||||||
.await;
|
.await;
|
||||||
@ -388,7 +405,7 @@ impl BedrockClient {
|
|||||||
|
|
||||||
let mut packets_to_enqueue = Vec::with_capacity(encoded_payloads.len());
|
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 {
|
for payload in encoded_payloads {
|
||||||
let mut packet_buf = Vec::new();
|
let mut packet_buf = Vec::new();
|
||||||
match encoder.write_game_packet(
|
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");
|
debug!("Failed to lock network writer for try_enqueue_packet");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -523,7 +540,7 @@ impl BedrockClient {
|
|||||||
let mut packet_payload = Vec::new();
|
let mut packet_payload = Vec::new();
|
||||||
packet.write_packet(&mut packet_payload)?;
|
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(
|
encoder.write_game_packet(
|
||||||
P::PACKET_ID as u16,
|
P::PACKET_ID as u16,
|
||||||
SubClient::Main,
|
SubClient::Main,
|
||||||
@ -707,6 +724,10 @@ impl BedrockClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.close_token.cancel();
|
self.close_token.cancel();
|
||||||
|
|
||||||
|
self.send_framed_packet(&CDisconnect, RakReliability::Unreliable)
|
||||||
|
.await;
|
||||||
|
|
||||||
self.be_clients.lock().await.remove(&self.address);
|
self.be_clients.lock().await.remove(&self.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,7 +779,7 @@ impl BedrockClient {
|
|||||||
RAKNET_NACK => {
|
RAKNET_NACK => {
|
||||||
self.handle_nack(&Acknowledge::read(reader)?).await;
|
self.handle_nack(&Acknowledge::read(reader)?).await;
|
||||||
}
|
}
|
||||||
0x80..=0x8d => {
|
RAKNET_VALID..=0x8d => {
|
||||||
self.handle_frame_set(server, FrameSet::read(reader)?)
|
self.handle_frame_set(server, FrameSet::read(reader)?)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@ -1020,8 +1041,7 @@ impl BedrockClient {
|
|||||||
};
|
};
|
||||||
let pending = self.pending_profile.lock().await.take();
|
let pending = self.pending_profile.lock().await.take();
|
||||||
if let Some((profile, new_config)) = pending {
|
if let Some((profile, new_config)) = pending {
|
||||||
let mut frame_set = FrameSet::default();
|
self.enqueue_packet_internal(&CPlayStatus::LoginSuccess)
|
||||||
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
|
|
||||||
.await;
|
.await;
|
||||||
let br_config = &server.advanced_config.resource_pack.bedrock;
|
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||||
|
|
||||||
@ -1052,10 +1072,7 @@ impl BedrockClient {
|
|||||||
world_template_version: String::new(),
|
world_template_version: String::new(),
|
||||||
resource_packs: entries,
|
resource_packs: entries,
|
||||||
};
|
};
|
||||||
self.write_game_packet_to_set(&packs_info, &mut frame_set)
|
self.enqueue_packet_internal(&packs_info).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
|
||||||
return PacketHandlerResult::ReadyToPlay(profile, new_config);
|
return PacketHandlerResult::ReadyToPlay(profile, new_config);
|
||||||
}
|
}
|
||||||
error!("Received ClientToServerHandshake but no pending profile was found.");
|
error!("Received ClientToServerHandshake but no pending profile was found.");
|
||||||
@ -1226,7 +1243,7 @@ impl BedrockClient {
|
|||||||
self.handle_connected_ping(SConnectedPing::read(reader)?)
|
self.handle_connected_ping(SConnectedPing::read(reader)?)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
SDisconnect::PACKET_ID => {
|
SDisconnect::PACKET_ID | SConnectionLost::PACKET_ID => {
|
||||||
self.close().await;
|
self.close().await;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -1236,6 +1253,7 @@ impl BedrockClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn handle_offline_packet(
|
pub async fn handle_offline_packet(
|
||||||
server: &Server,
|
server: &Server,
|
||||||
packet_id: u8,
|
packet_id: u8,
|
||||||
@ -1246,6 +1264,38 @@ impl BedrockClient {
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let packet_id_i32 = i32::from(packet_id);
|
let packet_id_i32 = i32::from(packet_id);
|
||||||
if packet_id_i32 == SOpenConnectionRequest1::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 old_client = {
|
||||||
let mut clients_guard = be_clients.lock().await;
|
let mut clients_guard = be_clients.lock().await;
|
||||||
clients_guard.remove(&addr)
|
clients_guard.remove(&addr)
|
||||||
@ -1269,6 +1319,20 @@ impl BedrockClient {
|
|||||||
)
|
)
|
||||||
.await;
|
.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 => {
|
SOpenConnectionRequest1::PACKET_ID => {
|
||||||
Self::handle_open_connection_1(
|
Self::handle_open_connection_1(
|
||||||
server,
|
server,
|
||||||
@ -1279,6 +1343,20 @@ impl BedrockClient {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
SOpenConnectionRequest2::PACKET_ID => {
|
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(
|
Self::handle_open_connection_2(
|
||||||
server,
|
server,
|
||||||
SOpenConnectionRequest2::read(payload)?,
|
SOpenConnectionRequest2::read(payload)?,
|
||||||
|
|||||||
@ -57,21 +57,45 @@ use crate::{
|
|||||||
use pumpkin_data::BlockDirection;
|
use pumpkin_data::BlockDirection;
|
||||||
use tracing::{debug, info};
|
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 {
|
if desc.id.0 == 0 || desc.stack_size == 0 {
|
||||||
ItemStack::EMPTY.clone()
|
ItemStack::EMPTY.clone()
|
||||||
} else if let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock(
|
} else {
|
||||||
|
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.id.0 as i16,
|
||||||
desc.aux_value.0,
|
desc.aux_value.0,
|
||||||
) {
|
)
|
||||||
ItemStack::new(desc.stack_size as u8, mapping.java_item)
|
{
|
||||||
} else {
|
mapped_item = Some(mapping.java_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_item.map_or_else(
|
||||||
|
|| {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Failed to map bedrock item id {} and data {} to Java item",
|
"Failed to map bedrock item id {} and data {} to Java item",
|
||||||
desc.id.0,
|
desc.id.0,
|
||||||
desc.aux_value.0
|
desc.aux_value.0
|
||||||
);
|
);
|
||||||
ItemStack::EMPTY.clone()
|
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) {
|
if input_data.get(InputData::StartFlying as usize) {
|
||||||
let mut abilities = player.abilities.lock().await;
|
let flying = { player.abilities.lock().await.flying };
|
||||||
if !abilities.flying {
|
if !flying {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerToggleFlightEvent::new(player.clone(), true);
|
PlayerToggleFlightEvent::new(player.clone(), true);
|
||||||
'after: {
|
'after: {
|
||||||
abilities.flying = true;
|
{
|
||||||
|
player.abilities.lock().await.flying = true;
|
||||||
|
};
|
||||||
player.send_abilities_update().await;
|
player.send_abilities_update().await;
|
||||||
}
|
}
|
||||||
'cancelled: {
|
'cancelled: {
|
||||||
@ -336,13 +362,15 @@ impl BedrockClient {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
} else if input_data.get(InputData::StopFlying as usize) {
|
} else if input_data.get(InputData::StopFlying as usize) {
|
||||||
let mut abilities = player.abilities.lock().await;
|
let flying = { player.abilities.lock().await.flying };
|
||||||
if abilities.flying {
|
if flying {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerToggleFlightEvent::new(player.clone(), false);
|
PlayerToggleFlightEvent::new(player.clone(), false);
|
||||||
'after: {
|
'after: {
|
||||||
abilities.flying = false;
|
{
|
||||||
|
player.abilities.lock().await.flying = false;
|
||||||
|
};
|
||||||
player.send_abilities_update().await;
|
player.send_abilities_update().await;
|
||||||
}
|
}
|
||||||
'cancelled: {
|
'cancelled: {
|
||||||
@ -504,12 +532,13 @@ impl BedrockClient {
|
|||||||
player_screen_handler.send_content_updates().await;
|
player_screen_handler.send_content_updates().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_creative = player.gamemode.load() == GameMode::Creative;
|
||||||
for action in &packet.actions {
|
for action in &packet.actions {
|
||||||
use pumpkin_protocol::bedrock::server::inventory_transaction::InventoryActionSource;
|
use pumpkin_protocol::bedrock::server::inventory_transaction::InventoryActionSource;
|
||||||
let source_type = InventoryActionSource::from(action.source_type);
|
let source_type = InventoryActionSource::from(action.source_type);
|
||||||
if source_type == InventoryActionSource::World {
|
if source_type == InventoryActionSource::World {
|
||||||
let old_stack = descriptor_to_stack(&action.old_item);
|
let old_stack = descriptor_to_stack(&action.old_item, is_creative);
|
||||||
let new_stack = descriptor_to_stack(&action.new_item);
|
let new_stack = descriptor_to_stack(&action.new_item, is_creative);
|
||||||
if old_stack.is_empty() && !new_stack.is_empty() {
|
if old_stack.is_empty() && !new_stack.is_empty() {
|
||||||
player.drop_item(new_stack).await;
|
player.drop_item(new_stack).await;
|
||||||
}
|
}
|
||||||
@ -517,7 +546,7 @@ impl BedrockClient {
|
|||||||
if let Some(screen_slot) =
|
if let Some(screen_slot) =
|
||||||
map_bedrock_slot_to_screen_handler(window_id, action.inventory_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;
|
let mut player_screen_handler = player.player_screen_handler.lock().await;
|
||||||
|
|
||||||
@ -610,7 +639,16 @@ impl BedrockClient {
|
|||||||
|
|
||||||
if data.action_type.0 == 0 {
|
if data.action_type.0 == 0 {
|
||||||
// Click block
|
// 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();
|
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
|
let result = server
|
||||||
.block_registry
|
.block_registry
|
||||||
@ -648,6 +686,53 @@ impl BedrockClient {
|
|||||||
)
|
)
|
||||||
.await;
|
.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) => {
|
TransactionData::UseItemOnEntity(data) => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use tracing::{Level, debug, error, info, trace, warn};
|
|||||||
|
|
||||||
use crate::block::BlockHitResult;
|
use crate::block::BlockHitResult;
|
||||||
use crate::block::registry::BlockActionResult;
|
use crate::block::registry::BlockActionResult;
|
||||||
use crate::block::{self, BlockIsReplacing};
|
use crate::block::{self};
|
||||||
use crate::entity::EntityBase;
|
use crate::entity::EntityBase;
|
||||||
use crate::entity::equipment_break_status;
|
use crate::entity::equipment_break_status;
|
||||||
use crate::entity::player::statistics::{CustomStatistic, StatisticCategory};
|
use crate::entity::player::statistics::{CustomStatistic, StatisticCategory};
|
||||||
@ -21,7 +21,6 @@ use crate::error::PumpkinError;
|
|||||||
use crate::log_at_level;
|
use crate::log_at_level;
|
||||||
use crate::net::PlayerConfig;
|
use crate::net::PlayerConfig;
|
||||||
use crate::net::java::JavaClient;
|
use crate::net::java::JavaClient;
|
||||||
use crate::plugin::block::block_place::BlockPlaceEvent;
|
|
||||||
use crate::plugin::player::changed_main_hand::PlayerChangedMainHandEvent;
|
use crate::plugin::player::changed_main_hand::PlayerChangedMainHandEvent;
|
||||||
use crate::plugin::player::fish::{PlayerFishEvent, PlayerFishState};
|
use crate::plugin::player::fish::{PlayerFishEvent, PlayerFishState};
|
||||||
use crate::plugin::player::item_held::PlayerItemHeldEvent;
|
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::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent;
|
||||||
use crate::server::{Server, seasonal_events};
|
use crate::server::{Server, seasonal_events};
|
||||||
use crate::world::{World, chunker};
|
use crate::world::{World, chunker};
|
||||||
use pumpkin_data::block_properties::{
|
use pumpkin_data::block_properties::{BlockProperties, CommandBlockLikeProperties};
|
||||||
BlockProperties, CommandBlockLikeProperties, WaterLikeProperties,
|
|
||||||
};
|
|
||||||
use pumpkin_data::data_component_impl::{
|
use pumpkin_data::data_component_impl::{
|
||||||
BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl,
|
BlocksAttacksImpl, ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl,
|
||||||
};
|
};
|
||||||
use pumpkin_data::entity::EntityType;
|
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
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::InventoryError;
|
||||||
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
||||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||||
@ -77,10 +73,8 @@ use pumpkin_protocol::java::server::play::{
|
|||||||
SSetJigsawBlock, SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity,
|
SSetJigsawBlock, SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity,
|
||||||
STestInstanceBlockAction, SUpdateSign, SUseItem, SUseItemOn, Status,
|
STestInstanceBlockAction, SUpdateSign, SUseItem, SUseItemOn, Status,
|
||||||
};
|
};
|
||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use pumpkin_util::math::{polynomial_rolling_hash, position::BlockPos, wrap_degrees};
|
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_util::{GameMode, text::TextComponent};
|
||||||
use pumpkin_world::generation::structure::structures::jigsaw::JigsawJointType;
|
use pumpkin_world::generation::structure::structures::jigsaw::JigsawJointType;
|
||||||
use pumpkin_world::world::BlockFlags;
|
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(
|
async fn run_is_block_place(
|
||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
@ -2769,172 +2714,28 @@ impl JavaClient {
|
|||||||
location: BlockPos,
|
location: BlockPos,
|
||||||
face: BlockDirection,
|
face: BlockDirection,
|
||||||
) -> Result<bool, BlockPlacingError> {
|
) -> Result<bool, BlockPlacingError> {
|
||||||
let entity = &player.get_entity();
|
match server
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
.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
|
.block_registry
|
||||||
.can_update_at(
|
.place_block(player, block, server, &use_item_on, location, face)
|
||||||
&world,
|
.await
|
||||||
clicked_block,
|
{
|
||||||
clicked_block_state.id,
|
Ok(Some((final_block_pos, new_state))) => {
|
||||||
&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)
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event =
|
|
||||||
BlockPlaceEvent::new(player.clone(), block, clicked_block, final_block_pos, true);
|
|
||||||
let event = server.plugin_manager.fire::<BlockPlaceEvent>(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(
|
self.send_packet_now(&CBlockUpdate::new(
|
||||||
final_block_pos,
|
final_block_pos,
|
||||||
VarInt(i32::from(new_state.as_u16())),
|
VarInt(i32::from(new_state.as_u16())),
|
||||||
))
|
))
|
||||||
.await;
|
.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)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
Ok(None) => Ok(false),
|
||||||
|
Err(crate::block::registry::BlockPlacingError::InvalidGamemode) => {
|
||||||
|
Err(BlockPlacingError::InvalidGamemode)
|
||||||
|
}
|
||||||
|
Err(crate::block::registry::BlockPlacingError::BlockOutOfWorld) => {
|
||||||
|
Err(BlockPlacingError::BlockOutOfWorld)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if the block placed was a sign, then opens a dialog.
|
/// Checks if the block placed was a sign, then opens a dialog.
|
||||||
pub async fn send_sign_packet(&self, block_position: BlockPos, is_front_text: bool) {
|
pub async fn send_sign_packet(&self, block_position: BlockPos, is_front_text: bool) {
|
||||||
|
|||||||
@ -79,7 +79,6 @@ use pumpkin_inventory::screen_handler::InventoryPlayer;
|
|||||||
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
|
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
|
||||||
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
|
use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
|
||||||
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
||||||
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
|
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
|
||||||
CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData,
|
CRespawn, CSetBlockDestroyStage, CWorldEvent, PlayerSpawnData,
|
||||||
@ -2222,10 +2221,8 @@ impl World {
|
|||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame_set = FrameSet::default();
|
|
||||||
client
|
client
|
||||||
.write_game_packet_to_set(
|
.enqueue_packet_internal(&CUpdateAttributes {
|
||||||
&CUpdateAttributes {
|
|
||||||
runtime_id: VarULong(runtime_id),
|
runtime_id: VarULong(runtime_id),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
Attribute {
|
Attribute {
|
||||||
@ -2290,11 +2287,8 @@ impl World {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
player_tick: VarULong(0),
|
player_tick: VarULong(0),
|
||||||
},
|
})
|
||||||
&mut frame_set,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
client.send_frame_set(frame_set, 0x84).await;
|
|
||||||
|
|
||||||
// --- MULTIPLAYER BROADCASTING ---
|
// --- MULTIPLAYER BROADCASTING ---
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user