1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

More packet fixes, tests, handle error (#61)

* Fix packet, fix tests, fixedbitsets

* Clippy: Nightmare Mode

* Fix mistake

* simplify impl Display and make thing pub

---------

Co-authored-by: mat <github@matdoes.dev>
This commit is contained in:
EightFactorial 2023-01-30 16:18:14 -08:00 committed by GitHub
parent 2539f948c7
commit 6e818852d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 499 additions and 333 deletions

View file

@ -24,6 +24,8 @@ pub enum ClientSessionServerError {
Unknown(String), Unknown(String),
#[error("Forbidden operation (expired session?)")] #[error("Forbidden operation (expired session?)")]
ForbiddenOperation, ForbiddenOperation,
#[error("RateLimiter disallowed request")]
RateLimited,
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")] #[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
UnexpectedResponse { status_code: u16, body: String }, UnexpectedResponse { status_code: u16, body: String },
} }
@ -95,6 +97,7 @@ pub async fn join(
_ => Err(ClientSessionServerError::Unknown(forbidden.error)), _ => Err(ClientSessionServerError::Unknown(forbidden.error)),
} }
} }
StatusCode::TOO_MANY_REQUESTS => Err(ClientSessionServerError::RateLimited),
status_code => { status_code => {
// log the headers // log the headers
debug!("Error headers: {:#?}", res.headers()); debug!("Error headers: {:#?}", res.headers());

View file

@ -150,7 +150,7 @@ async fn handle_connection(stream: TcpStream) -> anyhow::Result<()> {
if let Some(id) = hello.profile_id { if let Some(id) = hello.profile_id {
id.to_string() id.to_string()
} else { } else {
"".to_string() String::new()
} }
); );

View file

@ -189,6 +189,7 @@ where
} }
/// Split the reader and writer into two objects. This doesn't allocate. /// Split the reader and writer into two objects. This doesn't allocate.
#[must_use]
pub fn into_split(self) -> (ReadConnection<R>, WriteConnection<W>) { pub fn into_split(self) -> (ReadConnection<R>, WriteConnection<W>) {
(self.reader, self.writer) (self.reader, self.writer)
} }
@ -229,12 +230,14 @@ impl Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> {
/// Change our state from handshake to login. This is the state that is used /// Change our state from handshake to login. This is the state that is used
/// for logging in. /// for logging in.
#[must_use]
pub fn login(self) -> Connection<ClientboundLoginPacket, ServerboundLoginPacket> { pub fn login(self) -> Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
Connection::from(self) Connection::from(self)
} }
/// Change our state from handshake to status. This is the state that is /// Change our state from handshake to status. This is the state that is
/// used for pinging the server. /// used for pinging the server.
#[must_use]
pub fn status(self) -> Connection<ClientboundStatusPacket, ServerboundStatusPacket> { pub fn status(self) -> Connection<ClientboundStatusPacket, ServerboundStatusPacket> {
Connection::from(self) Connection::from(self)
} }
@ -265,6 +268,7 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
/// Change our state from login to game. This is the state that's used when /// Change our state from login to game. This is the state that's used when
/// you're actually in the game. /// you're actually in the game.
#[must_use]
pub fn game(self) -> Connection<ClientboundGamePacket, ServerboundGamePacket> { pub fn game(self) -> Connection<ClientboundGamePacket, ServerboundGamePacket> {
Connection::from(self) Connection::from(self)
} }
@ -343,12 +347,14 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
impl Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> { impl Connection<ServerboundHandshakePacket, ClientboundHandshakePacket> {
/// Change our state from handshake to login. This is the state that is used /// Change our state from handshake to login. This is the state that is used
/// for logging in. /// for logging in.
#[must_use]
pub fn login(self) -> Connection<ServerboundLoginPacket, ClientboundLoginPacket> { pub fn login(self) -> Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
Connection::from(self) Connection::from(self)
} }
/// Change our state from handshake to status. This is the state that is /// Change our state from handshake to status. This is the state that is
/// used for pinging the server. /// used for pinging the server.
#[must_use]
pub fn status(self) -> Connection<ServerboundStatusPacket, ClientboundStatusPacket> { pub fn status(self) -> Connection<ServerboundStatusPacket, ClientboundStatusPacket> {
Connection::from(self) Connection::from(self)
} }
@ -379,6 +385,7 @@ impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
/// Change our state from login to game. This is the state that's used when /// Change our state from login to game. This is the state that's used when
/// the client is actually in the game. /// the client is actually in the game.
#[must_use]
pub fn game(self) -> Connection<ServerboundGamePacket, ClientboundGamePacket> { pub fn game(self) -> Connection<ServerboundGamePacket, ClientboundGamePacket> {
Connection::from(self) Connection::from(self)
} }
@ -406,6 +413,7 @@ where
{ {
/// Creates a `Connection` of a type from a `Connection` of another type. /// Creates a `Connection` of a type from a `Connection` of another type.
/// Useful for servers or custom packets. /// Useful for servers or custom packets.
#[must_use]
pub fn from<R2, W2>(connection: Connection<R1, W1>) -> Connection<R2, W2> pub fn from<R2, W2>(connection: Connection<R1, W1>) -> Connection<R2, W2>
where where
R2: ProtocolPacket + Debug, R2: ProtocolPacket + Debug,

View file

@ -1,7 +1,7 @@
//! A low-level crate to send and receive Minecraft packets. //! A low-level crate to send and receive Minecraft packets.
//! //!
//! You should probably use [`azalea`] or [`azalea_client`] instead, as //! You should probably use [`azalea`] or [`azalea_client`] instead, as
//! azalea_protocol delegates much of the work, such as auth, to the user of //! `azalea_protocol` delegates much of the work, such as auth, to the user of
//! the crate. //! the crate.
//! //!
//! [`azalea`]: https://crates.io/crates/azalea //! [`azalea`]: https://crates.io/crates/azalea
@ -13,7 +13,7 @@
#![feature(error_generic_member_access)] #![feature(error_generic_member_access)]
#![feature(provide_any)] #![feature(provide_any)]
use std::{net::SocketAddr, str::FromStr}; use std::{fmt::Display, net::SocketAddr, str::FromStr};
#[cfg(feature = "connecting")] #[cfg(feature = "connecting")]
pub mod connect; pub mod connect;
@ -27,7 +27,7 @@ pub mod write;
/// ///
/// # Examples /// # Examples
/// ///
/// ServerAddress implements TryFrom<&str>, so you can use it like this: /// `ServerAddress` implements TryFrom<&str>, so you can use it like this:
/// ``` /// ```
/// use azalea_protocol::ServerAddress; /// use azalea_protocol::ServerAddress;
/// ///
@ -45,7 +45,7 @@ impl<'a> TryFrom<&'a str> for ServerAddress {
type Error = String; type Error = String;
/// Convert a Minecraft server address (host:port, the port is optional) to /// Convert a Minecraft server address (host:port, the port is optional) to
/// a ServerAddress /// a `ServerAddress`
fn try_from(string: &str) -> Result<Self, Self::Error> { fn try_from(string: &str) -> Result<Self, Self::Error> {
if string.is_empty() { if string.is_empty() {
return Err("Empty string".to_string()); return Err("Empty string".to_string());
@ -60,9 +60,9 @@ impl<'a> TryFrom<&'a str> for ServerAddress {
} }
impl From<SocketAddr> for ServerAddress { impl From<SocketAddr> for ServerAddress {
/// Convert an existing SocketAddr into a ServerAddress. This just converts /// Convert an existing `SocketAddr` into a `ServerAddress`. This just
/// the ip to a string and passes along the port. The resolver will realize /// converts the ip to a string and passes along the port. The resolver
/// it's already an IP address and not do any DNS requests. /// will realize it's already an IP address and not do any DNS requests.
fn from(addr: SocketAddr) -> Self { fn from(addr: SocketAddr) -> Self {
ServerAddress { ServerAddress {
host: addr.ip().to_string(), host: addr.ip().to_string(),
@ -71,6 +71,12 @@ impl From<SocketAddr> for ServerAddress {
} }
} }
impl Display for ServerAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.host, self.port)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io::Cursor; use std::io::Cursor;

View file

@ -2,6 +2,7 @@ use azalea_buf::{
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
}; };
use azalea_chat::Component; use azalea_chat::Component;
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use std::io::Cursor; use std::io::Cursor;
use std::io::Write; use std::io::Write;
@ -116,28 +117,28 @@ pub struct Properties {
impl McBufReadable for Properties { impl McBufReadable for Properties {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<3>::read_from(buf)?;
Ok(Self { Ok(Self {
darken_screen: byte & 1 != 0, darken_screen: set.index(0),
play_music: byte & 2 != 0, play_music: set.index(1),
create_world_fog: byte & 4 != 0, create_world_fog: set.index(2),
}) })
} }
} }
impl McBufWritable for Properties { impl McBufWritable for Properties {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut byte = 0; let mut set = FixedBitSet::<3>::new();
if self.darken_screen { if self.darken_screen {
byte |= 1; set.set(0);
} }
if self.play_music { if self.play_music {
byte |= 2; set.set(1);
} }
if self.create_world_fog { if self.create_world_fog {
byte |= 4; set.set(2);
} }
u8::write_into(&byte, buf)?; set.write_into(buf)?;
Ok(()) Ok(())
} }
} }

View file

@ -2,6 +2,7 @@ use azalea_buf::BufReadError;
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_buf::McBufVarReadable; use azalea_buf::McBufVarReadable;
use azalea_buf::{McBufReadable, McBufVarWritable, McBufWritable}; use azalea_buf::{McBufReadable, McBufVarWritable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_core::ResourceLocation; use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use log::warn; use log::warn;
@ -15,7 +16,7 @@ pub struct ClientboundCommandsPacket {
pub root_index: u32, pub root_index: u32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct BrigadierNodeStub { pub struct BrigadierNodeStub {
pub is_executable: bool, pub is_executable: bool,
pub children: Vec<u32>, pub children: Vec<u32>,
@ -23,7 +24,7 @@ pub struct BrigadierNodeStub {
pub node_type: NodeType, pub node_type: NodeType,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq)]
pub struct BrigadierNumber<T> { pub struct BrigadierNumber<T> {
pub min: Option<T>, pub min: Option<T>,
pub max: Option<T>, pub max: Option<T>,
@ -33,15 +34,29 @@ impl<T> BrigadierNumber<T> {
BrigadierNumber { min, max } BrigadierNumber { min, max }
} }
} }
impl<T: PartialEq> PartialEq for BrigadierNumber<T> {
fn eq(&self, other: &Self) -> bool {
match (&self.min, &self.max, &other.min, &other.max) {
(Some(f_min), None, Some(s_min), None) => f_min == s_min,
(None, Some(f_max), None, Some(s_max)) => f_max == s_max,
(Some(f_min), Some(f_max), Some(s_min), Some(s_max)) => {
f_min == s_min && f_max == s_max
}
(None, None, None, None) => true,
_ => false,
}
}
}
impl<T: McBufReadable> McBufReadable for BrigadierNumber<T> { impl<T: McBufReadable> McBufReadable for BrigadierNumber<T> {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let flags = u8::read_from(buf)?; let flags = FixedBitSet::<2>::read_from(buf)?;
let min = if flags & 0x01 != 0 { let min = if flags.index(0) {
Some(T::read_from(buf)?) Some(T::read_from(buf)?)
} else { } else {
None None
}; };
let max = if flags & 0x02 != 0 { let max = if flags.index(1) {
Some(T::read_from(buf)?) Some(T::read_from(buf)?)
} else { } else {
None None
@ -51,12 +66,12 @@ impl<T: McBufReadable> McBufReadable for BrigadierNumber<T> {
} }
impl<T: McBufWritable> McBufWritable for BrigadierNumber<T> { impl<T: McBufWritable> McBufWritable for BrigadierNumber<T> {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut flags: u8 = 0; let mut flags = FixedBitSet::<2>::new();
if self.min.is_some() { if self.min.is_some() {
flags |= 0x01; flags.set(0);
} }
if self.max.is_some() { if self.max.is_some() {
flags |= 0x02; flags.set(1);
} }
flags.write_into(buf)?; flags.write_into(buf)?;
if let Some(min) = &self.min { if let Some(min) = &self.min {
@ -69,7 +84,7 @@ impl<T: McBufWritable> McBufWritable for BrigadierNumber<T> {
} }
} }
#[derive(Debug, Clone, Copy, McBuf)] #[derive(Debug, Clone, Copy, McBuf, PartialEq, Eq)]
pub enum BrigadierString { pub enum BrigadierString {
/// Reads a single word /// Reads a single word
SingleWord = 0, SingleWord = 0,
@ -80,7 +95,7 @@ pub enum BrigadierString {
GreedyPhrase = 2, GreedyPhrase = 2,
} }
#[derive(Debug, Clone, McBuf)] #[derive(Debug, Clone, McBuf, PartialEq)]
pub enum BrigadierParser { pub enum BrigadierParser {
Bool, Bool,
Float(BrigadierNumber<f32>), Float(BrigadierNumber<f32>),
@ -132,28 +147,28 @@ pub enum BrigadierParser {
Uuid, Uuid,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntityParser { pub struct EntityParser {
pub single: bool, pub single: bool,
pub players_only: bool, pub players_only: bool,
} }
impl McBufReadable for EntityParser { impl McBufReadable for EntityParser {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let flags = u8::read_from(buf)?; let flags = FixedBitSet::<2>::read_from(buf)?;
Ok(EntityParser { Ok(EntityParser {
single: flags & 0x01 != 0, single: flags.index(0),
players_only: flags & 0x02 != 0, players_only: flags.index(1),
}) })
} }
} }
impl McBufWritable for EntityParser { impl McBufWritable for EntityParser {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut flags: u8 = 0; let mut flags = FixedBitSet::<2>::new();
if self.single { if self.single {
flags |= 0x01; flags.set(0);
} }
if self.players_only { if self.players_only {
flags |= 0x02; flags.set(1);
} }
flags.write_into(buf)?; flags.write_into(buf)?;
Ok(()) Ok(())
@ -163,17 +178,15 @@ impl McBufWritable for EntityParser {
// TODO: BrigadierNodeStub should have more stuff // TODO: BrigadierNodeStub should have more stuff
impl McBufReadable for BrigadierNodeStub { impl McBufReadable for BrigadierNodeStub {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let flags = u8::read_from(buf)?; let flags = FixedBitSet::<8>::read_from(buf)?;
if flags > 31 { if flags.index(5) || flags.index(6) || flags.index(7) {
warn!( warn!("Warning: The flags from a Brigadier node are over 31. This is probably a bug.",);
"Warning: The flags from a Brigadier node are over 31 ({flags}; {flags:#b}). This is probably a bug.",
);
} }
let node_type = flags & 0x03; let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2);
let is_executable = flags & 0x04 != 0; let is_executable = flags.index(2);
let has_redirect = flags & 0x08 != 0; let has_redirect = flags.index(3);
let has_suggestions_type = flags & 0x10 != 0; let has_suggestions_type = flags.index(4);
let children = Vec::<u32>::var_read_from(buf)?; let children = Vec::<u32>::var_read_from(buf)?;
let redirect_node = if has_redirect { let redirect_node = if has_redirect {
@ -224,16 +237,17 @@ impl McBufReadable for BrigadierNodeStub {
impl McBufWritable for BrigadierNodeStub { impl McBufWritable for BrigadierNodeStub {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut flags = FixedBitSet::<4>::new();
if self.is_executable {
flags.set(2);
}
if self.redirect_node.is_some() {
flags.set(3);
}
match &self.node_type { match &self.node_type {
NodeType::Root => { NodeType::Root => {
let mut flags = 0x00; flags.write_into(buf)?;
if self.is_executable {
flags |= 0x04;
}
if self.redirect_node.is_some() {
flags |= 0x08;
}
flags.var_write_into(buf)?;
self.children.var_write_into(buf)?; self.children.var_write_into(buf)?;
@ -242,14 +256,8 @@ impl McBufWritable for BrigadierNodeStub {
} }
} }
NodeType::Literal { name } => { NodeType::Literal { name } => {
let mut flags = 0x01; flags.set(0);
if self.is_executable { flags.write_into(buf)?;
flags |= 0x04;
}
if self.redirect_node.is_some() {
flags |= 0x08;
}
flags.var_write_into(buf)?;
self.children.var_write_into(buf)?; self.children.var_write_into(buf)?;
@ -264,17 +272,11 @@ impl McBufWritable for BrigadierNodeStub {
parser, parser,
suggestions_type, suggestions_type,
} => { } => {
let mut flags = 0x02; flags.set(1);
if self.is_executable {
flags |= 0x04;
}
if self.redirect_node.is_some() {
flags |= 0x08;
}
if suggestions_type.is_some() { if suggestions_type.is_some() {
flags |= 0x10; flags.set(4);
} }
flags.var_write_into(buf)?; flags.write_into(buf)?;
self.children.var_write_into(buf)?; self.children.var_write_into(buf)?;
@ -294,7 +296,7 @@ impl McBufWritable for BrigadierNodeStub {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum NodeType { pub enum NodeType {
Root, Root,
Literal { Literal {
@ -308,11 +310,67 @@ pub enum NodeType {
} }
impl BrigadierNodeStub { impl BrigadierNodeStub {
#[must_use]
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match &self.node_type { match &self.node_type {
NodeType::Root => None, NodeType::Root => None,
NodeType::Literal { name } => Some(name), NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name),
NodeType::Argument { name, .. } => Some(name),
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_brigadier_node_stub_root() {
let data = BrigadierNodeStub {
is_executable: false,
children: vec![1, 2],
redirect_node: None,
node_type: NodeType::Root,
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data = BrigadierNodeStub::read_from(&mut data_cursor).unwrap();
assert_eq!(data, read_data);
}
#[test]
fn test_brigadier_node_stub_literal() {
let data = BrigadierNodeStub {
is_executable: true,
children: vec![],
redirect_node: None,
node_type: NodeType::Literal {
name: "String".to_string(),
},
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data = BrigadierNodeStub::read_from(&mut data_cursor).unwrap();
assert_eq!(data, read_data);
}
#[test]
fn test_brigadier_node_stub_argument() {
let data = BrigadierNodeStub {
is_executable: false,
children: vec![6, 9],
redirect_node: Some(5),
node_type: NodeType::Argument {
name: "position".to_string(),
parser: BrigadierParser::Vec3,
suggestions_type: Some(ResourceLocation::new("minecraft:test_suggestion").unwrap()),
},
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data = BrigadierNodeStub::read_from(&mut data_cursor).unwrap();
assert_eq!(data, read_data);
}
}

View file

@ -31,9 +31,9 @@ impl McBufReadable for ClientboundExplodePacket {
let mut to_blow = Vec::with_capacity(to_blow_len as usize); let mut to_blow = Vec::with_capacity(to_blow_len as usize);
for _ in 0..to_blow_len { for _ in 0..to_blow_len {
// the bytes are offsets from the main x y z // the bytes are offsets from the main x y z
let x = x_floor + i8::read_from(buf)? as i32; let x = x_floor + i32::from(i8::read_from(buf)?);
let y = y_floor + i8::read_from(buf)? as i32; let y = y_floor + i32::from(i8::read_from(buf)?);
let z = z_floor + i8::read_from(buf)? as i32; let z = z_floor + i32::from(i8::read_from(buf)?);
to_blow.push(BlockPos { x, y, z }); to_blow.push(BlockPos { x, y, z });
} }

View file

@ -1,71 +1,15 @@
use azalea_buf::{BufReadError, McBuf}; use azalea_buf::{McBuf, McBufReadable, McBufWritable};
use azalea_buf::{McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
use azalea_chat::Component; use azalea_chat::Component;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write};
#[derive(Clone, Debug, ClientboundGamePacket)] #[derive(Clone, Debug, ClientboundGamePacket, McBuf)]
pub struct ClientboundMapItemDataPacket { pub struct ClientboundMapItemDataPacket {
// #[var] #[var]
pub map_id: u32, pub map_id: u32,
pub scale: u8, pub scale: u8,
pub locked: bool, pub locked: bool,
pub decorations: Vec<MapDecoration>, pub decorations: Option<Vec<MapDecoration>>,
pub color_patch: Option<MapPatch>, pub color_patch: OptionalMapPatch,
}
impl McBufReadable for ClientboundMapItemDataPacket {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let map_id = u32::var_read_from(buf)?;
let scale = u8::read_from(buf)?;
let locked = bool::read_from(buf)?;
let decorations = Option::<Vec<MapDecoration>>::read_from(buf)?.unwrap_or_default();
let width = u8::read_from(buf)?;
let color_patch = if width == 0 {
None
} else {
let height = u8::read_from(buf)?;
let start_x = u8::read_from(buf)?;
let start_y = u8::read_from(buf)?;
let map_colors = Vec::<u8>::read_from(buf)?;
Some(MapPatch {
width,
height,
start_x,
start_y,
map_colors,
})
};
Ok(Self {
map_id,
scale,
locked,
decorations,
color_patch,
})
}
}
impl McBufWritable for ClientboundMapItemDataPacket {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.map_id.var_write_into(buf)?;
self.scale.write_into(buf)?;
self.locked.write_into(buf)?;
(!self.decorations.is_empty()).write_into(buf)?;
self.decorations.write_into(buf)?;
if let Some(color_patch) = &self.color_patch {
color_patch.width.write_into(buf)?;
color_patch.height.write_into(buf)?;
color_patch.start_x.write_into(buf)?;
color_patch.start_y.write_into(buf)?;
color_patch.map_colors.write_into(buf)?;
} else {
0u8.write_into(buf)?;
}
Ok(())
}
} }
#[derive(Clone, Debug, McBuf)] #[derive(Clone, Debug, McBuf)]
@ -80,11 +24,35 @@ pub struct MapDecoration {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OptionalMapPatch(pub Option<MapPatch>);
impl McBufReadable for OptionalMapPatch {
fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let pos = buf.position();
Ok(Self(if u8::read_from(buf)? == 0 {
None
} else {
buf.set_position(pos);
Some(MapPatch::read_from(buf)?)
}))
}
}
impl McBufWritable for OptionalMapPatch {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
match &self.0 {
None => 0u8.write_into(buf),
Some(m) => m.write_into(buf),
}
}
}
#[derive(Debug, Clone, McBuf)]
pub struct MapPatch { pub struct MapPatch {
pub start_x: u8,
pub start_y: u8,
pub width: u8, pub width: u8,
pub height: u8, pub height: u8,
pub start_x: u8,
pub start_y: u8,
pub map_colors: Vec<u8>, pub map_colors: Vec<u8>,
} }

View file

@ -1,5 +1,6 @@
use azalea_buf::{BufReadError, McBuf}; use azalea_buf::{BufReadError, McBuf};
use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
@ -21,31 +22,31 @@ pub struct PlayerAbilitiesFlags {
impl McBufReadable for PlayerAbilitiesFlags { impl McBufReadable for PlayerAbilitiesFlags {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<4>::read_from(buf)?;
Ok(PlayerAbilitiesFlags { Ok(PlayerAbilitiesFlags {
invulnerable: byte & 1 != 0, invulnerable: set.index(0),
flying: byte & 2 != 0, flying: set.index(1),
can_fly: byte & 4 != 0, can_fly: set.index(2),
instant_break: byte & 8 != 0, instant_break: set.index(3),
}) })
} }
} }
impl McBufWritable for PlayerAbilitiesFlags { impl McBufWritable for PlayerAbilitiesFlags {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut byte = 0; let mut set = FixedBitSet::<4>::new();
if self.invulnerable { if self.invulnerable {
byte |= 0b1; set.set(0);
} }
if self.flying { if self.flying {
byte |= 0b10; set.set(1);
} }
if self.can_fly { if self.can_fly {
byte |= 0b100; set.set(2);
} }
if self.instant_break { if self.instant_break {
byte |= 0b1000; set.set(3);
} }
u8::write_into(&byte, buf) set.write_into(buf)
} }
} }

View file

@ -90,6 +90,7 @@ impl ClientboundPlayerChatPacket {
/// Returns the content of the message. If you want to get the Component /// Returns the content of the message. If you want to get the Component
/// for the whole message including the sender part, use /// for the whole message including the sender part, use
/// [`ClientboundPlayerChatPacket::message`]. /// [`ClientboundPlayerChatPacket::message`].
#[must_use]
pub fn content(&self) -> Component { pub fn content(&self) -> Component {
self.unsigned_content self.unsigned_content
.clone() .clone()
@ -97,6 +98,7 @@ impl ClientboundPlayerChatPacket {
} }
/// Get the full message, including the sender part. /// Get the full message, including the sender part.
#[must_use]
pub fn message(&self) -> Component { pub fn message(&self) -> Component {
let sender = self.chat_type.name.clone(); let sender = self.chat_type.name.clone();
let content = self.content(); let content = self.content();
@ -119,6 +121,7 @@ impl ClientboundPlayerChatPacket {
} }
impl ChatType { impl ChatType {
#[must_use]
pub fn chat_translation_key(&self) -> &'static str { pub fn chat_translation_key(&self) -> &'static str {
match self { match self {
ChatType::Chat => "chat.type.text", ChatType::Chat => "chat.type.text",
@ -131,15 +134,11 @@ impl ChatType {
} }
} }
#[must_use]
pub fn narrator_translation_key(&self) -> &'static str { pub fn narrator_translation_key(&self) -> &'static str {
match self { match self {
ChatType::Chat => "chat.type.text.narrate",
ChatType::SayCommand => "chat.type.text.narrate",
ChatType::MsgCommandIncoming => "chat.type.text.narrate",
ChatType::MsgCommandOutgoing => "chat.type.text.narrate",
ChatType::TeamMsgCommandIncoming => "chat.type.text.narrate",
ChatType::TeamMsgCommandOutgoing => "chat.type.text.narrate",
ChatType::EmoteCommand => "chat.type.emote", ChatType::EmoteCommand => "chat.type.emote",
_ => "chat.type.text.narrate",
} }
} }
} }

View file

@ -1,5 +1,6 @@
use azalea_buf::{BufReadError, McBuf}; use azalea_buf::{BufReadError, McBuf};
use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
@ -29,35 +30,35 @@ pub struct RelativeArguments {
impl McBufReadable for RelativeArguments { impl McBufReadable for RelativeArguments {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<5>::read_from(buf)?;
Ok(RelativeArguments { Ok(RelativeArguments {
x: byte & 0b1 != 0, x: set.index(0),
y: byte & 0b10 != 0, y: set.index(1),
z: byte & 0b100 != 0, z: set.index(2),
y_rot: byte & 0b1000 != 0, y_rot: set.index(3),
x_rot: byte & 0b10000 != 0, x_rot: set.index(4),
}) })
} }
} }
impl McBufWritable for RelativeArguments { impl McBufWritable for RelativeArguments {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut byte = 0; let mut set = FixedBitSet::<5>::new();
if self.x { if self.x {
byte |= 0b1; set.set(0);
} }
if self.y { if self.y {
byte |= 0b10; set.set(1);
} }
if self.z { if self.z {
byte |= 0b100; set.set(2);
} }
if self.y_rot { if self.y_rot {
byte |= 0b1000; set.set(3);
} }
if self.x_rot { if self.x_rot {
byte |= 0b10000; set.set(4);
} }
u8::write_into(&byte, buf) set.write_into(buf)
} }
} }

View file

@ -38,7 +38,7 @@ impl McBufReadable for BlockStateWithPosition {
impl McBufWritable for BlockStateWithPosition { impl McBufWritable for BlockStateWithPosition {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let data = (self.state as u64) << 12 let data = (self.state as u64) << 12
| ((self.pos.x as u64) << 8 | (self.pos.z as u64) << 4 | (self.pos.y as u64)); | (u64::from(self.pos.x) << 8 | u64::from(self.pos.z) << 4 | u64::from(self.pos.y));
u64::var_write_into(&data, buf)?; u64::var_write_into(&data, buf)?;
Ok(()) Ok(())
} }

View file

@ -65,6 +65,7 @@ pub enum EquipmentSlot {
} }
impl EquipmentSlot { impl EquipmentSlot {
#[must_use]
pub fn from_byte(byte: u8) -> Option<Self> { pub fn from_byte(byte: u8) -> Option<Self> {
match byte { match byte {
0 => Some(EquipmentSlot::MainHand), 0 => Some(EquipmentSlot::MainHand),

View file

@ -22,7 +22,7 @@ impl McBufReadable for Method {
0 => Method::Add(DisplayInfo::read_from(buf)?), 0 => Method::Add(DisplayInfo::read_from(buf)?),
1 => Method::Remove, 1 => Method::Remove,
2 => Method::Change(DisplayInfo::read_from(buf)?), 2 => Method::Change(DisplayInfo::read_from(buf)?),
id => return Err(BufReadError::UnexpectedEnumVariant { id: id as i32 }), id => return Err(BufReadError::UnexpectedEnumVariant { id: i32::from(id) }),
}) })
} }
} }

View file

@ -26,7 +26,7 @@ impl McBufReadable for Method {
2 => Method::Change(Parameters::read_from(buf)?), 2 => Method::Change(Parameters::read_from(buf)?),
3 => Method::Join(PlayerList::read_from(buf)?), 3 => Method::Join(PlayerList::read_from(buf)?),
4 => Method::Leave(PlayerList::read_from(buf)?), 4 => Method::Leave(PlayerList::read_from(buf)?),
id => return Err(BufReadError::UnexpectedEnumVariant { id: id as i32 }), id => return Err(BufReadError::UnexpectedEnumVariant { id: i32::from(id) }),
}) })
} }
} }

View file

@ -45,7 +45,7 @@ impl McBufWritable for ClientboundSetScorePacket {
// convert None to an empty string // convert None to an empty string
self.objective_name self.objective_name
.as_ref() .as_ref()
.unwrap_or(&"".to_string()) .unwrap_or(&String::new())
.write_into(buf)?; .write_into(buf)?;
if let Method::Change { score } = self.method { if let Method::Change { score } = self.method {
score.var_write_into(buf)?; score.var_write_into(buf)?;

View file

@ -1,5 +1,5 @@
use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::ResourceLocation; use azalea_core::{FixedBitSet, ResourceLocation};
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
@ -13,13 +13,13 @@ pub struct ClientboundStopSoundPacket {
impl McBufReadable for ClientboundStopSoundPacket { impl McBufReadable for ClientboundStopSoundPacket {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<2>::read_from(buf)?;
let source = if byte & 1 != 0 { let source = if set.index(0) {
Some(SoundSource::read_from(buf)?) Some(SoundSource::read_from(buf)?)
} else { } else {
None None
}; };
let name = if byte & 2 != 0 { let name = if set.index(1) {
Some(ResourceLocation::read_from(buf)?) Some(ResourceLocation::read_from(buf)?)
} else { } else {
None None
@ -31,14 +31,14 @@ impl McBufReadable for ClientboundStopSoundPacket {
impl McBufWritable for ClientboundStopSoundPacket { impl McBufWritable for ClientboundStopSoundPacket {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut byte = 0u8; let mut set = FixedBitSet::<2>::new();
if self.source.is_some() { if self.source.is_some() {
byte |= 1; set.set(0);
} }
if self.name.is_some() { if self.name.is_some() {
byte |= 2; set.set(1);
} }
byte.write_into(buf)?; set.write_into(buf)?;
if let Some(source) = &self.source { if let Some(source) = &self.source {
source.write_into(buf)?; source.write_into(buf)?;
} }

View file

@ -17,10 +17,8 @@ pub struct ClientboundUpdateAdvancementsPacket {
pub struct Advancement { pub struct Advancement {
parent_id: Option<ResourceLocation>, parent_id: Option<ResourceLocation>,
display: Option<DisplayInfo>, display: Option<DisplayInfo>,
// rewards: AdvancementRewards.EMPTY,
criteria: HashMap<ResourceLocation, Criterion>, criteria: HashMap<ResourceLocation, Criterion>,
requirements: Vec<Vec<String>>, requirements: Vec<Vec<String>>,
// requirements_strategy: RequirementsStrategy.AND
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -87,11 +85,11 @@ impl azalea_buf::McBufReadable for DisplayInfo {
description, description,
icon, icon,
frame, frame,
show_toast,
hidden,
background, background,
x, x,
y, y,
hidden,
show_toast,
}) })
} }
} }
@ -114,57 +112,77 @@ pub struct CriterionProgress {
date: Option<u64>, date: Option<u64>,
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use super::*; use super::*;
// use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
// use azalea_core::ResourceLocation; use azalea_core::ResourceLocation;
// use azalea_protocol_macros::ClientboundGamePacket; use std::io::Cursor;
// use std::io::Cursor;
// #[test] #[test]
// fn test() { fn test() {
// let mut buf = Cursor::new(Vec::new()); let packet = ClientboundUpdateAdvancementsPacket {
// let packet = ClientboundUpdateAdvancementsPacket { reset: true,
// reset: true, added: [(
// added: [( ResourceLocation::new("minecraft:test").unwrap(),
// ResourceLocation::new("minecraft:test").unwrap(), Advancement {
// Advancement { parent_id: None,
// parent_id: None, display: Some(DisplayInfo {
// display: Some(DisplayInfo { title: Component::from("title".to_string()),
// title: Component::from("title".to_string()), description: Component::from("description".to_string()),
// description: icon: Slot::Empty,
// Component::from("description".to_string()), icon: frame: FrameType::Task,
// Slot::Empty, frame: FrameType::Task, show_toast: true,
// show_toast: true, hidden: false,
// hidden: false, background: None,
// background: None, x: 0.0,
// x: 0.0, y: 0.0,
// y: 0.0, }),
// }), criteria: HashMap::new(),
// criteria: HashMap::new(), requirements: Vec::new(),
// requirements: Vec::new(), },
// }, )]
// )] .into_iter()
// .into_iter() .collect(),
// .collect(), removed: vec![ResourceLocation::new("minecraft:test2").unwrap()],
// removed: vec![ResourceLocation::new("minecraft:test2").unwrap()], progress: [(
// progress: [( ResourceLocation::new("minecraft:test3").unwrap(),
// ResourceLocation::new("minecraft:test3").unwrap(), [(
// [( ResourceLocation::new("minecraft:test4").unwrap(),
// ResourceLocation::new("minecraft:test4").unwrap(), CriterionProgress {
// CriterionProgress { date: Some(123456789),
// date: Some(123456789), },
// }, )]
// )] .into_iter()
// .into_iter() .collect(),
// .collect(), )]
// )] .into_iter()
// .into_iter() .collect(),
// .collect(), };
// };
// packet.write_into(&mut buf).unwrap(); let mut data = Vec::new();
// let packet = ClientboundUpdateAdvancementsPacket::read_from(&mut packet.write_into(&mut data).unwrap();
// buf).unwrap(); assert_eq!(packet.reset, true); let mut buf: Cursor<&[u8]> = Cursor::new(&data);
// }
// } let read_packet = ClientboundUpdateAdvancementsPacket::read_from(&mut buf).unwrap();
assert_eq!(packet.reset, read_packet.reset);
assert_eq!(packet.removed, read_packet.removed);
let advancement = packet
.added
.get(&ResourceLocation::new("minecraft:test").unwrap())
.unwrap()
.clone();
let read_advancement = read_packet
.added
.get(&ResourceLocation::new("minecraft:test").unwrap())
.unwrap()
.clone();
assert_eq!(advancement.parent_id, read_advancement.parent_id);
let display = advancement.display.unwrap();
let read_display = read_advancement.display.unwrap();
assert_eq!(display.title, read_display.title);
assert_eq!(display.description, read_display.description);
}
}

View file

@ -1,7 +1,8 @@
use azalea_buf::McBuf; use azalea_buf::{McBuf, McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)] #[derive(Clone, Debug, McBuf, ServerboundGamePacket, PartialEq, Eq)]
pub struct ServerboundClientInformationPacket { pub struct ServerboundClientInformationPacket {
/// The locale of the client. /// The locale of the client.
pub language: String, pub language: String,
@ -14,7 +15,7 @@ pub struct ServerboundClientInformationPacket {
/// Whether the messages sent from the server should have colors. Note that /// Whether the messages sent from the server should have colors. Note that
/// many servers ignore this and always send colored messages. /// many servers ignore this and always send colored messages.
pub chat_colors: bool, pub chat_colors: bool,
pub model_customisation: u8, pub model_customisation: ModelCustomization,
pub main_hand: HumanoidArm, pub main_hand: HumanoidArm,
pub text_filtering_enabled: bool, pub text_filtering_enabled: bool,
/// Whether the client should show up as "Anonymous Player" in the server /// Whether the client should show up as "Anonymous Player" in the server
@ -27,9 +28,9 @@ impl Default for ServerboundClientInformationPacket {
Self { Self {
language: "en_us".to_string(), language: "en_us".to_string(),
view_distance: 8, view_distance: 8,
chat_visibility: ChatVisibility::Full, chat_visibility: ChatVisibility::default(),
chat_colors: true, chat_colors: true,
model_customisation: 0, model_customisation: ModelCustomization::default(),
main_hand: HumanoidArm::Right, main_hand: HumanoidArm::Right,
text_filtering_enabled: false, text_filtering_enabled: false,
allows_listing: false, allows_listing: false,
@ -37,9 +38,10 @@ impl Default for ServerboundClientInformationPacket {
} }
} }
#[derive(McBuf, Clone, Copy, Debug)] #[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ChatVisibility { pub enum ChatVisibility {
/// All chat messages should be sent to the client. /// All chat messages should be sent to the client.
#[default]
Full = 0, Full = 0,
/// Chat messages from other players should be not sent to the client, only /// Chat messages from other players should be not sent to the client, only
/// messages from the server like "Player joined the game" should be sent. /// messages from the server like "Player joined the game" should be sent.
@ -48,8 +50,125 @@ pub enum ChatVisibility {
Hidden = 2, Hidden = 2,
} }
#[derive(McBuf, Clone, Copy, Debug)] #[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum HumanoidArm { pub enum HumanoidArm {
Left = 0, Left = 0,
#[default]
Right = 1, Right = 1,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModelCustomization {
pub cape: bool,
pub jacket: bool,
pub left_sleeve: bool,
pub right_sleeve: bool,
pub left_pants: bool,
pub right_pants: bool,
pub hat: bool,
}
impl Default for ModelCustomization {
fn default() -> Self {
Self {
cape: true,
jacket: true,
left_sleeve: true,
right_sleeve: true,
left_pants: true,
right_pants: true,
hat: true,
}
}
}
impl McBufReadable for ModelCustomization {
fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let set = FixedBitSet::<7>::read_from(buf)?;
Ok(Self {
cape: set.index(0),
jacket: set.index(1),
left_sleeve: set.index(2),
right_sleeve: set.index(3),
left_pants: set.index(4),
right_pants: set.index(5),
hat: set.index(6),
})
}
}
impl McBufWritable for ModelCustomization {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
let mut set = FixedBitSet::<7>::new();
if self.cape {
set.set(0);
}
if self.jacket {
set.set(1);
}
if self.left_sleeve {
set.set(2);
}
if self.right_sleeve {
set.set(3);
}
if self.left_pants {
set.set(4);
}
if self.right_pants {
set.set(5);
}
if self.hat {
set.set(6);
}
set.write_into(buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_client_information_packet() {
{
let data = ServerboundClientInformationPacket::default();
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data =
ServerboundClientInformationPacket::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
{
let data = ServerboundClientInformationPacket {
language: "en_gb".to_string(),
view_distance: 24,
chat_visibility: ChatVisibility::Hidden,
chat_colors: false,
model_customisation: ModelCustomization {
cape: false,
jacket: false,
left_sleeve: true,
right_sleeve: false,
left_pants: true,
right_pants: false,
hat: true,
},
main_hand: HumanoidArm::Left,
text_filtering_enabled: true,
allows_listing: true,
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data =
ServerboundClientInformationPacket::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
}
}

View file

@ -63,9 +63,9 @@ impl McBufReadable for ActionType {
let hand = InteractionHand::read_from(buf)?; let hand = InteractionHand::read_from(buf)?;
Ok(ActionType::InteractAt { Ok(ActionType::InteractAt {
location: Vec3 { location: Vec3 {
x: x as f64, x: f64::from(x),
y: y as f64, y: f64::from(y),
z: z as f64, z: f64::from(z),
}, },
hand, hand,
}) })

View file

@ -1,29 +1,29 @@
use crate::packets::BufReadError; use crate::packets::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
use std::io::Cursor; use std::io::Cursor;
#[derive(Clone, Debug, ServerboundGamePacket)] #[derive(Clone, Debug, ServerboundGamePacket)]
pub struct ServerboundPlayerAbilitiesPacket { pub struct ServerboundPlayerAbilitiesPacket {
is_flying: bool, pub is_flying: bool,
} }
impl McBufReadable for ServerboundPlayerAbilitiesPacket { impl McBufReadable for ServerboundPlayerAbilitiesPacket {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<2>::read_from(buf)?;
Ok(Self { Ok(Self {
is_flying: byte & 2 != 0, is_flying: set.index(1),
}) })
} }
} }
impl McBufWritable for ServerboundPlayerAbilitiesPacket { impl McBufWritable for ServerboundPlayerAbilitiesPacket {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
let mut byte = 0; let mut set = FixedBitSet::<2>::new();
if self.is_flying { if self.is_flying {
byte |= 2; set.set(1);
} }
u8::write_into(&byte, buf)?; set.write_into(buf)
Ok(())
} }
} }

View file

@ -2,6 +2,7 @@ use std::io::Cursor;
use azalea_buf::BufReadError; use azalea_buf::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
#[derive(Clone, Debug, ServerboundGamePacket)] #[derive(Clone, Debug, ServerboundGamePacket)]
@ -16,14 +17,12 @@ impl McBufReadable for ServerboundPlayerInputPacket {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let xxa = f32::read_from(buf)?; let xxa = f32::read_from(buf)?;
let zza = f32::read_from(buf)?; let zza = f32::read_from(buf)?;
let byte = u8::read_from(buf)?; let set = FixedBitSet::<2>::read_from(buf)?;
let is_jumping = byte & 1 != 0;
let is_shift_key_down = byte & 2 != 0;
Ok(Self { Ok(Self {
xxa, xxa,
zza, zza,
is_jumping, is_jumping: set.index(0),
is_shift_key_down, is_shift_key_down: set.index(1),
}) })
} }
} }
@ -32,14 +31,13 @@ impl McBufWritable for ServerboundPlayerInputPacket {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
self.xxa.write_into(buf)?; self.xxa.write_into(buf)?;
self.zza.write_into(buf)?; self.zza.write_into(buf)?;
let mut byte = 0u8; let mut set = FixedBitSet::<2>::new();
if self.is_jumping { if self.is_jumping {
byte |= 1; set.set(0);
} }
if self.is_shift_key_down { if self.is_shift_key_down {
byte |= 2; set.set(1);
} }
byte.write_into(buf)?; set.write_into(buf)
Ok(())
} }
} }

View file

@ -1,6 +1,6 @@
use crate::packets::McBufWritable; use crate::packets::McBufWritable;
use azalea_buf::{BufReadError, McBuf, McBufReadable}; use azalea_buf::{BufReadError, McBuf, McBufReadable};
use azalea_core::BlockPos; use azalea_core::{BlockPos, FixedBitSet};
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
use std::io::Cursor; use std::io::Cursor;
@ -28,17 +28,14 @@ impl McBufReadable for ServerboundSetCommandBlockPacket {
let command = String::read_from(buf)?; let command = String::read_from(buf)?;
let mode = Mode::read_from(buf)?; let mode = Mode::read_from(buf)?;
let byte = u8::read_from(buf)?; let set = FixedBitSet::<3>::read_from(buf)?;
let track_output = byte & 1 != 0;
let conditional = byte & 2 != 0;
let automatic = byte & 4 != 0;
Ok(Self { Ok(Self {
pos, pos,
command, command,
mode, mode,
track_output, track_output: set.index(0),
conditional, conditional: set.index(1),
automatic, automatic: set.index(2),
}) })
} }
} }
@ -49,17 +46,16 @@ impl McBufWritable for ServerboundSetCommandBlockPacket {
self.command.write_into(buf)?; self.command.write_into(buf)?;
self.mode.write_into(buf)?; self.mode.write_into(buf)?;
let mut byte: u8 = 0; let mut set = FixedBitSet::<3>::new();
if self.track_output { if self.track_output {
byte |= 1; set.set(0);
} }
if self.conditional { if self.conditional {
byte |= 2; set.set(1);
} }
if self.automatic { if self.automatic {
byte |= 4; set.set(2);
} }
byte.write_into(buf)?; set.write_into(buf)
Ok(())
} }
} }

View file

@ -1,7 +1,7 @@
use crate::packets::BufReadError; use crate::packets::BufReadError;
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_buf::{McBufReadable, McBufWritable}; use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::BlockPos; use azalea_core::{BlockPos, FixedBitSet};
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
@ -69,28 +69,28 @@ pub struct Flags {
impl McBufReadable for Flags { impl McBufReadable for Flags {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let byte = u8::read_from(buf)?; let set = FixedBitSet::<3>::read_from(buf)?;
Ok(Self { Ok(Self {
ignore_entities: byte & 1 != 0, ignore_entities: set.index(0),
show_air: byte & 2 != 0, show_air: set.index(1),
show_bounding_box: byte & 4 != 0, show_bounding_box: set.index(2),
}) })
} }
} }
impl McBufWritable for Flags { impl McBufWritable for Flags {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut byte = 0; let mut set = FixedBitSet::<3>::new();
if self.ignore_entities { if self.ignore_entities {
byte |= 1; set.set(0);
} }
if self.show_air { if self.show_air {
byte |= 2; set.set(1);
} }
if self.show_bounding_box { if self.show_bounding_box {
byte |= 4; set.set(2);
} }
u8::write_into(&byte, buf)?; set.write_into(buf)?;
Ok(()) Ok(())
} }
} }

View file

@ -24,9 +24,9 @@ impl McBufWritable for BlockHitResult {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.block_pos.write_into(buf)?; self.block_pos.write_into(buf)?;
self.direction.write_into(buf)?; self.direction.write_into(buf)?;
f32::write_into(&((self.location.x - (self.block_pos.x as f64)) as f32), buf)?; f32::write_into(&((self.location.x - f64::from(self.block_pos.x)) as f32), buf)?;
f32::write_into(&((self.location.y - (self.block_pos.y as f64)) as f32), buf)?; f32::write_into(&((self.location.y - f64::from(self.block_pos.y)) as f32), buf)?;
f32::write_into(&((self.location.z - (self.block_pos.z as f64)) as f32), buf)?; f32::write_into(&((self.location.z - f64::from(self.block_pos.z)) as f32), buf)?;
self.inside.write_into(buf)?; self.inside.write_into(buf)?;
Ok(()) Ok(())
} }
@ -44,9 +44,9 @@ impl McBufReadable for BlockHitResult {
block_pos, block_pos,
direction, direction,
location: Vec3 { location: Vec3 {
x: block_pos.x as f64 + cursor_x as f64, x: f64::from(block_pos.x) + f64::from(cursor_x),
y: block_pos.y as f64 + cursor_y as f64, y: f64::from(block_pos.y) + f64::from(cursor_y),
z: block_pos.z as f64 + cursor_z as f64, z: f64::from(block_pos.z) + f64::from(cursor_z),
}, },
inside, inside,
}) })

View file

@ -21,6 +21,7 @@ pub enum ConnectionProtocol {
} }
impl ConnectionProtocol { impl ConnectionProtocol {
#[must_use]
pub fn from_i32(i: i32) -> Option<Self> { pub fn from_i32(i: i32) -> Option<Self> {
match i { match i {
-1 => Some(ConnectionProtocol::Handshake), -1 => Some(ConnectionProtocol::Handshake),
@ -39,7 +40,7 @@ where
{ {
fn id(&self) -> u32; fn id(&self) -> u32;
/// Read a packet by its id, ConnectionProtocol, and flow /// Read a packet by its id, `ConnectionProtocol`, and flow
fn read(id: u32, buf: &mut Cursor<&[u8]>) -> Result<Self, Box<ReadPacketError>>; fn read(id: u32, buf: &mut Cursor<&[u8]>) -> Result<Self, Box<ReadPacketError>>;
fn write(&self, buf: &mut impl Write) -> Result<(), std::io::Error>; fn write(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;

View file

@ -76,8 +76,8 @@ pub enum FrameSplitterError {
ConnectionClosed, ConnectionClosed,
} }
/// Read a length, then read that amount of bytes from BytesMut. If there's not /// Read a length, then read that amount of bytes from `BytesMut`. If there's
/// enough data, return None /// not enough data, return None
fn parse_frame(buffer: &mut BytesMut) -> Result<BytesMut, FrameSplitterError> { fn parse_frame(buffer: &mut BytesMut) -> Result<BytesMut, FrameSplitterError> {
// copy the buffer first and read from the copy, then once we make sure // copy the buffer first and read from the copy, then once we make sure
// the packet is all good we read it fully // the packet is all good we read it fully
@ -258,41 +258,28 @@ where
Ok(packet) Ok(packet)
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use super::*; use super::*;
// use crate::packets::game::{clientbound_player_chat_packet::ChatType, use crate::packets::game::ClientboundGamePacket;
// ClientboundGamePacket}; use std::io::Cursor; use std::io::Cursor;
// #[tokio::test] #[tokio::test]
// async fn test_read_packet() { async fn test_read_packet() {
// let mut buf: Cursor<&[u8]> = Cursor::new(&[ let mut buf: Cursor<&[u8]> = Cursor::new(&[
// 51, 0, 12, 177, 250, 155, 132, 106, 60, 218, 161, 217, 90, 157, 56, 64, 85, 58, 141, 138, 71, 146, 193, 64, 88, 0, 0, 0, 0, 0, 0, 64, 60, 224, 105, 34,
// 105, 57, 206, 20, 0, 5, 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 119, 8, 228, 67, 50, 51, 68, 194, 177, 230, 101, 0, 17, 0,
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 116, 123, 34, 101, 120, ]);
// 116, 114, 97, 34, 58, 91, 123, 34, 99, 111, 108, 111, 114, 34, 58, let packet = packet_decoder::<ClientboundGamePacket>(&mut buf).unwrap();
// 34, 103, 114, 97, 121, 34, 44, 34, 116, 101, 120, 116, 34, 58, match &packet {
// 34, 91, 77, 69, 77, 66, 69, 82, 93, 32, 112, 108, 97, 121, 101, ClientboundGamePacket::PlayerPosition(p) => {
// 114, 49, 34, 125, 44, 123, 34, 116, 101, 120, 116, 34, 58, 34, assert_eq!(p.id, 17);
// 32, 34, 125, 44, 123, 34, 99, 111, 108, 111, 114, 34, 58, 34, 103, assert_eq!(p.x, 84.91488892545296);
// 114, 97, 121, 34, 44, 34, 116, 101, 120, 116, 34, 58, 34, 92, assert_eq!(p.y, 96.0);
// 117, 48, 48, 51, 101, 32, 104, 101, 108, 108, 111, 34, 125, 93, assert_eq!(p.z, 28.876604227124417);
// 44, 34, 116, 101, 120, 116, 34, 58, 34, 34, 125, 0, 7, 64, 123, assert_eq!(p.dismount_vehicle, false);
// 34, 101, 120, 116, 114, 97, 34, 58, 91, 123, 34, 99, 111, 108, 111, 114, }
// 34, 58, 34, 103, 114, 97, 121, 34, 44, 34, 116, 101, 120, 116, _ => panic!("Wrong packet type"),
// 34, 58, 34, 91, 77, 69, 77, 66, 69, 82, 93, 32, 112, 108, 97, }
// 121, 101, 114, 49, 34, 125, 93, 44, 34, 116, 101, 120, 116, 34, }
// 58, 34, 34, 125, 0, ]); }
// let packet = packet_decoder::<ClientboundGamePacket>(&mut
// buf).unwrap(); match &packet {
// ClientboundGamePacket::PlayerChat(m) => {
// assert_eq!(
// m.chat_type.chat_type,
// ChatType::Chat,
// "Enums should default if they're invalid"
// );
// }
// _ => panic!("Wrong packet type"),
// }
// }
// }

View file

@ -19,6 +19,7 @@ pub enum ResolverError {
/// Resolve a Minecraft server address into an IP address and port. /// Resolve a Minecraft server address into an IP address and port.
/// If it's already an IP address, it's returned as-is. /// If it's already an IP address, it's returned as-is.
#[must_use]
#[async_recursion] #[async_recursion]
pub async fn resolve_address(address: &ServerAddress) -> Result<SocketAddr, ResolverError> { pub async fn resolve_address(address: &ServerAddress) -> Result<SocketAddr, ResolverError> {
// If the address.host is already in the format of an ip address, return it. // If the address.host is already in the format of an ip address, return it.