Initial Bedrock support

You can't join the World, But the base is ready
This commit is contained in:
Alexander Medvedev
2025-06-29 00:33:44 +02:00
parent 6a620e4a9e
commit e0c91b3edc
326 changed files with 1772 additions and 797 deletions

8
Cargo.lock generated
View File

@@ -1295,9 +1295,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown 0.15.4",
@@ -1476,9 +1476,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "lru"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198"
checksum = "0281c2e25e62316a5c9d98f2d2e9e95a37841afdaf4383c177dbb5c1dfab0568"
dependencies = [
"hashbrown 0.15.4",
]

View File

@@ -21,7 +21,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi
## Goals
- **Performance**: Leveraging multi-threading for maximum speed and efficiency.
- **Compatibility**: Supports the latest Minecraft server version while adhering to Vanilla game mechanics.
- **Compatibility**: Supports the latest Java & Bedrock/Pocket Minecraft server version while adhering to Vanilla game mechanics.
- **Security**: Prioritizes security by preventing known security exploits.
- **Flexibility**: Highly configurable, with the ability to disable unnecessary features.
- **Extensibility**: Provides a foundation for plugin development.

View File

@@ -108,8 +108,14 @@ pub struct AdvancedConfiguration {
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct BasicConfiguration {
/// The address to bind the server to.
pub server_address: SocketAddr,
// Whether Java Edition Client's are Accepted
pub java_edition: bool,
/// The address and port to which the Java Edition server will bind
pub java_edition_address: SocketAddr,
// Whether Bedrock/Pocket Edition Client's are Accepted
pub bedrock_edition: bool,
/// The address and port to which the Bedrock/Pocket Edition server will bind
pub bedrock_edition_address: SocketAddr,
/// The seed for world generation.
pub seed: String,
/// The maximum number of players allowed on the server. Specifying `0` disables the limit.
@@ -157,7 +163,10 @@ pub struct BasicConfiguration {
impl Default for BasicConfiguration {
fn default() -> Self {
Self {
server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565),
java_edition: true,
java_edition_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565),
bedrock_edition: true,
bedrock_edition_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 19132),
seed: "".to_string(),
max_players: 100000,
view_distance: NonZeroU8::new(10).unwrap(),

View File

@@ -1,9 +1,19 @@
use std::net::{Ipv4Addr, SocketAddr};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Default)]
#[derive(Deserialize, Serialize)]
#[serde(default)]
pub struct QueryConfig {
pub enabled: bool,
/// Optional; if not specified, the port the server is running on will be used.
pub port: Option<u16>,
pub address: SocketAddr,
}
impl Default for QueryConfig {
fn default() -> Self {
Self {
enabled: true,
address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565),
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::InventoryError;
use pumpkin_protocol::server::play::SlotActionType;
use pumpkin_protocol::java::server::play::SlotActionType;
use pumpkin_world::item::ItemStack;
#[derive(Debug)]

View File

@@ -2,7 +2,7 @@ use crate::entity_equipment::EntityEquipment;
use crate::equipment_slot::EquipmentSlot;
use crate::screen_handler::InventoryPlayer;
use async_trait::async_trait;
use pumpkin_protocol::client::play::CSetPlayerInventory;
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
use pumpkin_world::inventory::split_stack;
use pumpkin_world::inventory::{Clearable, Inventory};
use pumpkin_world::item::ItemStack;

View File

@@ -2,12 +2,14 @@ use async_trait::async_trait;
use log::warn;
use pumpkin_data::screen::WindowType;
use pumpkin_protocol::{
client::play::{
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
CSetPlayerInventory, CSetSelectedSlot,
},
codec::item_stack_seralizer::OptionalItemStackHash,
server::play::SlotActionType,
java::{
client::play::{
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
CSetPlayerInventory, CSetSelectedSlot,
},
server::play::SlotActionType,
},
};
use pumpkin_util::text::TextComponent;
use pumpkin_world::inventory::{ComparableInventory, Inventory};

View File

@@ -1,13 +1,13 @@
use std::sync::Arc;
use pumpkin_protocol::{
client::play::{
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
},
codec::{
item_stack_seralizer::{ItemStackSerializer, OptionalItemStackHash},
var_int::VarInt,
},
java::client::play::{
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
},
};
use pumpkin_world::item::ItemStack;
use tokio::sync::Mutex;

View File

@@ -167,7 +167,7 @@ pub fn packet(input: TokenStream, item: TokenStream) -> TokenStream {
let code = quote! {
#item
impl #impl_generics crate::ser::packet::Packet for #name #ty_generics {
impl #impl_generics crate::packet::Packet for #name #ty_generics {
const PACKET_ID: i32 = #input;
}
};

View File

@@ -4,8 +4,7 @@ version.workspace = true
edition.workspace = true
[features]
default = ["packets", "query"]
packets = ["serverbound", "clientbound"]
default = ["query"]
serverbound = []
clientbound = []
query = []

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
pub mod connection;
pub mod open_connection;
pub mod unconnected_pong;

View File

@@ -0,0 +1,46 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bedrock::RAKNET_MAGIC, codec::socket_address::SocketAddress};
#[derive(Serialize)]
#[packet(0x06)]
pub struct COpenConnectionReply1 {
magic: [u8; 16],
server_guid: u64,
has_server_security: bool,
cookie: u32,
mtu: u16,
}
impl COpenConnectionReply1 {
pub fn new(server_guid: u64, has_server_security: bool, cookie: u32, mtu: u16) -> Self {
Self {
magic: RAKNET_MAGIC,
server_guid,
has_server_security,
cookie,
mtu,
}
}
}
#[derive(Serialize)]
#[packet(0x08)]
pub struct COpenConnectionReply2 {
server_guid: u64,
client_address: SocketAddress,
mtu: u16,
security: bool,
}
impl COpenConnectionReply2 {
pub fn new(server_guid: u64, client_address: SocketAddress, mtu: u16, security: bool) -> Self {
Self {
server_guid,
client_address,
mtu,
security,
}
}
}

View File

@@ -0,0 +1,63 @@
use core::fmt;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::codec::ascii_string::AsciiString;
#[derive(Serialize)]
#[packet(0x1c)]
pub struct CUnconnectedPong {
time: i64,
server_guid: u64,
magic: [u8; 16],
server_id: AsciiString,
}
pub struct ServerInfo {
/// (MCPE or MCEE for Education Edition)
pub edition: &'static str,
pub motd_line_1: &'static str,
pub protocol_version: u32,
pub version_name: &'static str,
pub player_count: i32,
pub max_player_count: u32,
pub server_unique_id: u64,
pub motd_line_2: &'static str,
pub game_mode: &'static str,
pub game_mode_numeric: u32,
pub port_ipv4: u16,
pub port_ipv6: u16,
}
impl fmt::Display for ServerInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{};{};{};{};{};{};{};{};{};{};{};{}",
self.edition,
self.motd_line_1,
self.protocol_version,
self.version_name,
self.player_count,
self.max_player_count,
self.server_unique_id,
self.motd_line_2,
self.game_mode,
self.game_mode_numeric,
self.port_ipv4,
self.port_ipv6
)
}
}
impl CUnconnectedPong {
pub fn new(time: i64, server_guid: u64, magic: [u8; 16], server_id: AsciiString) -> Self {
Self {
time,
server_guid,
magic,
server_id,
}
}
}

View File

@@ -0,0 +1,8 @@
pub mod client;
pub mod packet_decoder;
pub mod packet_encoder;
pub mod server;
pub const RAKNET_MAGIC: [u8; 16] = [
0x00, 0xff, 0xff, 0x0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78,
];

View File

@@ -0,0 +1,122 @@
use std::io::Cursor;
use async_compression::tokio::bufread::ZlibDecoder;
use bytes::Buf;
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
use crate::{Aes128Cfb8Dec, CompressionThreshold, PacketDecodeError, RawPacket, StreamDecryptor};
// decrypt -> decompress -> raw
pub enum DecompressionReader<R: AsyncRead + Unpin> {
Decompress(ZlibDecoder<BufReader<R>>),
None(R),
}
impl<R: AsyncRead + Unpin> AsyncRead for DecompressionReader<R> {
#[inline]
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match self.get_mut() {
Self::Decompress(reader) => {
let reader = std::pin::Pin::new(reader);
reader.poll_read(cx, buf)
}
Self::None(reader) => {
let reader = std::pin::Pin::new(reader);
reader.poll_read(cx, buf)
}
}
}
}
pub enum DecryptionReader<R: AsyncRead + Unpin> {
Decrypt(Box<StreamDecryptor<R>>),
None(R),
}
impl<R: AsyncRead + Unpin> DecryptionReader<R> {
pub fn upgrade(self, cipher: Aes128Cfb8Dec) -> Self {
match self {
Self::None(stream) => Self::Decrypt(Box::new(StreamDecryptor::new(cipher, stream))),
_ => panic!("Cannot upgrade a stream that already has a cipher!"),
}
}
}
impl<R: AsyncRead + Unpin> AsyncRead for DecryptionReader<R> {
#[inline]
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match self.get_mut() {
Self::Decrypt(reader) => {
let reader = std::pin::Pin::new(reader);
reader.poll_read(cx, buf)
}
Self::None(reader) => {
let reader = std::pin::Pin::new(reader);
reader.poll_read(cx, buf)
}
}
}
}
/// Decoder: Client -> Server
/// Supports ZLib decoding/decompression
/// Supports Aes128 Encryption
pub struct UDPNetworkDecoder {
compression: Option<CompressionThreshold>,
}
impl Default for UDPNetworkDecoder {
fn default() -> Self {
Self::new()
}
}
impl UDPNetworkDecoder {
pub fn new() -> Self {
Self { compression: None }
}
pub fn set_compression(&mut self, threshold: CompressionThreshold) {
self.compression = Some(threshold);
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, _key: &[u8; 16]) {
// if matches!(self.reader, DecryptionReader::Decrypt(_)) {
// panic!("Cannot upgrade a stream that already has a cipher!");
// }
// let cipher = Aes128Cfb8Dec::new_from_slices(key, key).expect("invalid key");
// take_mut::take(&mut self.reader, |decoder| decoder.upgrade(cipher));
}
pub async fn get_raw_packet(
&mut self,
mut reader: Cursor<Vec<u8>>,
) -> Result<RawPacket, PacketDecodeError> {
// TODO: Serde is sync so we need to write to a buffer here :(
// Is there a way to deserialize in an asynchronous manner?
let packet_id = reader
.try_get_u8()
.map_err(|_| PacketDecodeError::DecodeID)?;
let mut payload = Vec::new();
reader
.read_to_end(&mut payload)
.await
.map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?;
Ok(RawPacket {
id: packet_id as i32,
payload: payload.into(),
})
}
}

View File

@@ -0,0 +1,124 @@
use std::net::SocketAddr;
use bytes::Bytes;
use thiserror::Error;
use tokio::{io::AsyncWrite, net::UdpSocket};
use crate::{
Aes128Cfb8Enc, CompressionLevel, CompressionThreshold, PacketEncodeError, StreamEncryptor,
};
// raw -> compress -> encrypt
pub enum EncryptionWriter<W: AsyncWrite + Unpin> {
Encrypt(Box<StreamEncryptor<W>>),
None(W),
}
impl<W: AsyncWrite + Unpin> EncryptionWriter<W> {
pub fn upgrade(self, cipher: Aes128Cfb8Enc) -> Self {
match self {
Self::None(stream) => Self::Encrypt(Box::new(StreamEncryptor::new(cipher, stream))),
_ => panic!("Cannot upgrade a stream that already has a cipher!"),
}
}
}
impl<W: AsyncWrite + Unpin> AsyncWrite for EncryptionWriter<W> {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
match self.get_mut() {
Self::Encrypt(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_write(cx, buf)
}
Self::None(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_write(cx, buf)
}
}
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
match self.get_mut() {
Self::Encrypt(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_flush(cx)
}
Self::None(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_flush(cx)
}
}
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
match self.get_mut() {
Self::Encrypt(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_shutdown(cx)
}
Self::None(writer) => {
let writer = std::pin::Pin::new(writer);
writer.poll_shutdown(cx)
}
}
}
}
/// Encoder: Server -> Client
/// Supports ZLib endecoding/compression
/// Supports Aes128 Encryption
pub struct UDPNetworkEncoder {
// compression and compression threshold
compression: Option<(CompressionThreshold, CompressionLevel)>,
}
impl Default for UDPNetworkEncoder {
fn default() -> Self {
Self::new()
}
}
impl UDPNetworkEncoder {
pub fn new() -> Self {
Self { compression: None }
}
pub fn set_compression(&mut self, compression_info: (CompressionThreshold, CompressionLevel)) {
self.compression = Some(compression_info);
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, _key: &[u8; 16]) {
// if matches!(self.writer, EncryptionWriter::Encrypt(_)) {
// panic!("Cannot upgrade a stream that already has a cipher!");
// }
// let cipher = Aes128Cfb8Enc::new_from_slices(key, key).expect("invalid key");
// take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
}
pub async fn write_packet(
&mut self,
packet_data: Bytes,
addr: SocketAddr,
socket: &UdpSocket,
) -> Result<(), PacketEncodeError> {
socket.send_to(&packet_data, addr).await.unwrap();
Ok(())
}
}
#[derive(Error, Debug)]
#[error("Invalid compression Level")]
pub struct CompressionLevelError;

View File

@@ -0,0 +1,10 @@
use pumpkin_macros::packet;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[packet(0x09)]
pub struct SConnectionRequest {
pub client_guid: u64,
pub time: u64,
pub security: bool,
}

View File

@@ -0,0 +1,3 @@
pub mod connection;
pub mod open_connection;
pub mod unconnected_ping;

View File

@@ -0,0 +1,22 @@
use pumpkin_macros::packet;
use serde::{Deserialize, Serialize};
use crate::codec::socket_address::SocketAddress;
#[derive(Serialize, Deserialize)]
#[packet(0x05)]
/// The client sends this when attempting to join the server
pub struct SOpenConnectionRequest1 {
pub magic: [u8; 16],
pub protocol_version: u8,
pub mtu: u16,
}
#[derive(Serialize, Deserialize)]
#[packet(0x07)]
pub struct SOpenConnectionRequest2 {
pub magic: [u8; 16],
pub server_address: SocketAddress,
pub mtu: u16,
pub client_guid: u64,
}

View File

@@ -0,0 +1,11 @@
use pumpkin_macros::packet;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[packet(0x01)]
/// Used to request Server information like MOTD
pub struct SUnconnectedPing {
pub time: i64,
pub magic: [u8; 16],
pub client_guid: i64,
}

View File

@@ -0,0 +1,21 @@
use std::io::Write;
use bytes::BufMut;
use serde::{Serialize, Serializer};
pub struct AsciiString(pub String);
impl Serialize for AsciiString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut buf = Vec::new();
// Prefixed by a short
buf.put_u16(self.0.len() as u16);
buf.write_all(self.0.as_bytes()).unwrap();
serializer.serialize_bytes(&buf)
}
}

View File

@@ -3,10 +3,10 @@ use std::io::Write;
use serde::{Serialize, Serializer};
use crate::ReadingError;
use crate::WritingError;
use crate::ser::NetworkReadExt;
use crate::ser::NetworkWriteExt;
use crate::ser::ReadingError;
use crate::ser::WritingError;
pub struct BitSet(pub Box<[i64]>);

View File

@@ -1,4 +1,6 @@
pub mod ascii_string;
pub mod bit_set;
pub mod item_stack_seralizer;
pub mod socket_address;
pub mod var_int;
pub mod var_long;

View File

@@ -0,0 +1,93 @@
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use bytes::BufMut;
use serde::{
Deserialize, Serialize, Serializer,
de::{self, SeqAccess},
};
pub struct SocketAddress(pub SocketAddr);
impl Serialize for SocketAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut buf = Vec::new();
let version = match self.0 {
SocketAddr::V4(_) => 4,
SocketAddr::V6(_) => 6,
};
let ip = match self.0 {
SocketAddr::V4(addr) => addr.ip().to_bits(),
SocketAddr::V6(addr) => addr.ip().to_bits() as u32,
};
buf.put_u8(version);
buf.put_u32(ip);
buf.put_u16(self.0.port());
serializer.serialize_bytes(&buf)
}
}
impl<'de> Deserialize<'de> for SocketAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = SocketAddress;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid socket addr")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
if let Some(version) = seq.next_element::<u8>()? {
match version {
4 => {
let ip = seq.next_element::<u32>()?.unwrap();
let port = seq.next_element::<u16>()?.unwrap();
return Ok(SocketAddress(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::from_bits(ip),
port,
))));
}
6 => {
let _family = seq.next_element::<u16>()?.unwrap();
let port = seq.next_element::<u16>()?.unwrap();
let flowinfo = seq.next_element::<u32>()?.unwrap();
let ip = seq.next_element::<u128>()?.unwrap();
let scope_id = seq.next_element::<u32>()?.unwrap();
return Ok(SocketAddress(SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::from_bits(ip),
port,
flowinfo,
scope_id,
))));
}
_ => {
return Err(serde::de::Error::custom(format!(
"Wrong Socket Address version {version}"
)));
}
}
}
Err(serde::de::Error::custom("Incomplete Socket Address"))
}
}
deserializer.deserialize_seq(Visitor)
}
}

View File

@@ -4,8 +4,6 @@ use std::{
ops::Deref,
};
use crate::ser::{NetworkReadExt, NetworkWriteExt, ReadingError, WritingError};
use bytes::BufMut;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
@@ -13,6 +11,8 @@ use serde::{
};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use crate::ser::{NetworkReadExt, NetworkWriteExt, ReadingError, WritingError};
pub type VarIntType = i32;
/**

View File

@@ -4,13 +4,16 @@ use std::{
ops::Deref,
};
use crate::ser::{NetworkReadExt, NetworkWriteExt, ReadingError, WritingError};
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, SeqAccess, Visitor},
};
use crate::{
WritingError,
ser::{NetworkReadExt, NetworkWriteExt, ReadingError},
};
pub type VarLongType = i64;
/**

View File

@@ -1,9 +1,6 @@
use std::io::Write;
use crate::{
ClientPacket,
ser::{NetworkWriteExt, WritingError},
};
use crate::{ClientPacket, WritingError, ser::NetworkWriteExt};
use pumpkin_data::{
block_properties::get_block,

View File

@@ -1,8 +1,8 @@
use std::io::Write;
use crate::ClientPacket;
use crate::client::play::bossevent_action::BosseventAction;
use crate::ser::{NetworkWriteExt, WritingError};
use crate::java::client::play::BosseventAction;
use crate::ser::NetworkWriteExt;
use crate::{ClientPacket, WritingError};
use pumpkin_data::packet::clientbound::PLAY_BOSS_EVENT;
use pumpkin_macros::packet;

View File

@@ -1,8 +1,6 @@
use crate::WritingError;
use crate::codec::bit_set::BitSet;
use crate::{
ClientPacket, VarInt,
ser::{NetworkWriteExt, WritingError},
};
use crate::{ClientPacket, VarInt, ser::NetworkWriteExt};
use pumpkin_data::packet::clientbound::PLAY_LEVEL_CHUNK_WITH_LIGHT;
use pumpkin_macros::packet;
use pumpkin_nbt::END_ID;

View File

@@ -3,10 +3,7 @@ use std::io::Write;
use pumpkin_data::packet::clientbound::PLAY_COMMANDS;
use pumpkin_macros::packet;
use crate::{
ClientPacket, VarInt,
ser::{NetworkWriteExt, WritingError},
};
use crate::{ClientPacket, VarInt, WritingError, ser::NetworkWriteExt};
#[packet(PLAY_COMMANDS)]
pub struct CCommands<'a> {

View File

@@ -5,9 +5,9 @@ use pumpkin_macros::packet;
use pumpkin_util::text::TextComponent;
use crate::{
ClientPacket,
ClientPacket, WritingError,
codec::{bit_set::BitSet, var_int::VarInt},
ser::{NetworkWriteExt, WritingError},
ser::NetworkWriteExt,
};
#[packet(PLAY_PLAYER_CHAT)]

View File

@@ -4,10 +4,7 @@ use bitflags::bitflags;
use pumpkin_data::packet::clientbound::PLAY_PLAYER_INFO_UPDATE;
use pumpkin_macros::packet;
use crate::{
ClientPacket, Property,
ser::{NetworkWriteExt, WritingError},
};
use crate::{ClientPacket, Property, WritingError, ser::NetworkWriteExt};
use super::PlayerAction;

View File

@@ -4,10 +4,7 @@ use pumpkin_data::packet::clientbound::PLAY_PLAYER_POSITION;
use pumpkin_macros::packet;
use pumpkin_util::math::vector3::Vector3;
use crate::{
ClientPacket, PositionFlag, VarInt,
ser::{NetworkWriteExt, WritingError},
};
use crate::{ClientPacket, PositionFlag, VarInt, WritingError, ser::NetworkWriteExt};
#[packet(PLAY_PLAYER_POSITION)]
pub struct CPlayerPosition<'a> {

Some files were not shown because too many files have changed in this diff Show More