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),
#[error("Forbidden operation (expired session?)")]
ForbiddenOperation,
#[error("RateLimiter disallowed request")]
RateLimited,
#[error("Unexpected response from sessionserver (status code {status_code}): {body}")]
UnexpectedResponse { status_code: u16, body: String },
}
@ -95,6 +97,7 @@ pub async fn join(
_ => Err(ClientSessionServerError::Unknown(forbidden.error)),
}
}
StatusCode::TOO_MANY_REQUESTS => Err(ClientSessionServerError::RateLimited),
status_code => {
// log the 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 {
id.to_string()
} else {
"".to_string()
String::new()
}
);

View file

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

View file

@ -1,7 +1,7 @@
//! A low-level crate to send and receive Minecraft packets.
//!
//! 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.
//!
//! [`azalea`]: https://crates.io/crates/azalea
@ -13,7 +13,7 @@
#![feature(error_generic_member_access)]
#![feature(provide_any)]
use std::{net::SocketAddr, str::FromStr};
use std::{fmt::Display, net::SocketAddr, str::FromStr};
#[cfg(feature = "connecting")]
pub mod connect;
@ -27,7 +27,7 @@ pub mod write;
///
/// # 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;
///
@ -45,7 +45,7 @@ impl<'a> TryFrom<&'a str> for ServerAddress {
type Error = String;
/// 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> {
if string.is_empty() {
return Err("Empty string".to_string());
@ -60,9 +60,9 @@ impl<'a> TryFrom<&'a str> for ServerAddress {
}
impl From<SocketAddr> for ServerAddress {
/// Convert an existing SocketAddr into a ServerAddress. This just converts
/// the ip to a string and passes along the port. The resolver will realize
/// it's already an IP address and not do any DNS requests.
/// Convert an existing `SocketAddr` into a `ServerAddress`. This just
/// converts the ip to a string and passes along the port. The resolver
/// will realize it's already an IP address and not do any DNS requests.
fn from(addr: SocketAddr) -> Self {
ServerAddress {
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)]
mod tests {
use std::io::Cursor;

View file

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

View file

@ -2,6 +2,7 @@ use azalea_buf::BufReadError;
use azalea_buf::McBuf;
use azalea_buf::McBufVarReadable;
use azalea_buf::{McBufReadable, McBufVarWritable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundGamePacket;
use log::warn;
@ -15,7 +16,7 @@ pub struct ClientboundCommandsPacket {
pub root_index: u32,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct BrigadierNodeStub {
pub is_executable: bool,
pub children: Vec<u32>,
@ -23,7 +24,7 @@ pub struct BrigadierNodeStub {
pub node_type: NodeType,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq)]
pub struct BrigadierNumber<T> {
pub min: Option<T>,
pub max: Option<T>,
@ -33,15 +34,29 @@ impl<T> BrigadierNumber<T> {
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> {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let flags = u8::read_from(buf)?;
let min = if flags & 0x01 != 0 {
let flags = FixedBitSet::<2>::read_from(buf)?;
let min = if flags.index(0) {
Some(T::read_from(buf)?)
} else {
None
};
let max = if flags & 0x02 != 0 {
let max = if flags.index(1) {
Some(T::read_from(buf)?)
} else {
None
@ -51,12 +66,12 @@ impl<T: McBufReadable> McBufReadable for BrigadierNumber<T> {
}
impl<T: McBufWritable> McBufWritable for BrigadierNumber<T> {
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() {
flags |= 0x01;
flags.set(0);
}
if self.max.is_some() {
flags |= 0x02;
flags.set(1);
}
flags.write_into(buf)?;
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 {
/// Reads a single word
SingleWord = 0,
@ -80,7 +95,7 @@ pub enum BrigadierString {
GreedyPhrase = 2,
}
#[derive(Debug, Clone, McBuf)]
#[derive(Debug, Clone, McBuf, PartialEq)]
pub enum BrigadierParser {
Bool,
Float(BrigadierNumber<f32>),
@ -132,28 +147,28 @@ pub enum BrigadierParser {
Uuid,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntityParser {
pub single: bool,
pub players_only: bool,
}
impl McBufReadable for EntityParser {
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 {
single: flags & 0x01 != 0,
players_only: flags & 0x02 != 0,
single: flags.index(0),
players_only: flags.index(1),
})
}
}
impl McBufWritable for EntityParser {
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 {
flags |= 0x01;
flags.set(0);
}
if self.players_only {
flags |= 0x02;
flags.set(1);
}
flags.write_into(buf)?;
Ok(())
@ -163,17 +178,15 @@ impl McBufWritable for EntityParser {
// TODO: BrigadierNodeStub should have more stuff
impl McBufReadable for BrigadierNodeStub {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let flags = u8::read_from(buf)?;
if flags > 31 {
warn!(
"Warning: The flags from a Brigadier node are over 31 ({flags}; {flags:#b}). This is probably a bug.",
);
let flags = FixedBitSet::<8>::read_from(buf)?;
if flags.index(5) || flags.index(6) || flags.index(7) {
warn!("Warning: The flags from a Brigadier node are over 31. This is probably a bug.",);
}
let node_type = flags & 0x03;
let is_executable = flags & 0x04 != 0;
let has_redirect = flags & 0x08 != 0;
let has_suggestions_type = flags & 0x10 != 0;
let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2);
let is_executable = flags.index(2);
let has_redirect = flags.index(3);
let has_suggestions_type = flags.index(4);
let children = Vec::<u32>::var_read_from(buf)?;
let redirect_node = if has_redirect {
@ -224,16 +237,17 @@ impl McBufReadable for BrigadierNodeStub {
impl McBufWritable for BrigadierNodeStub {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
match &self.node_type {
NodeType::Root => {
let mut flags = 0x00;
let mut flags = FixedBitSet::<4>::new();
if self.is_executable {
flags |= 0x04;
flags.set(2);
}
if self.redirect_node.is_some() {
flags |= 0x08;
flags.set(3);
}
flags.var_write_into(buf)?;
match &self.node_type {
NodeType::Root => {
flags.write_into(buf)?;
self.children.var_write_into(buf)?;
@ -242,14 +256,8 @@ impl McBufWritable for BrigadierNodeStub {
}
}
NodeType::Literal { name } => {
let mut flags = 0x01;
if self.is_executable {
flags |= 0x04;
}
if self.redirect_node.is_some() {
flags |= 0x08;
}
flags.var_write_into(buf)?;
flags.set(0);
flags.write_into(buf)?;
self.children.var_write_into(buf)?;
@ -264,17 +272,11 @@ impl McBufWritable for BrigadierNodeStub {
parser,
suggestions_type,
} => {
let mut flags = 0x02;
if self.is_executable {
flags |= 0x04;
}
if self.redirect_node.is_some() {
flags |= 0x08;
}
flags.set(1);
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)?;
@ -294,7 +296,7 @@ impl McBufWritable for BrigadierNodeStub {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum NodeType {
Root,
Literal {
@ -308,11 +310,67 @@ pub enum NodeType {
}
impl BrigadierNodeStub {
#[must_use]
pub fn name(&self) -> Option<&str> {
match &self.node_type {
NodeType::Root => None,
NodeType::Literal { name } => Some(name),
NodeType::Argument { name, .. } => Some(name),
NodeType::Literal { 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);
for _ in 0..to_blow_len {
// the bytes are offsets from the main x y z
let x = x_floor + i8::read_from(buf)? as i32;
let y = y_floor + i8::read_from(buf)? as i32;
let z = z_floor + i8::read_from(buf)? as i32;
let x = x_floor + i32::from(i8::read_from(buf)?);
let y = y_floor + i32::from(i8::read_from(buf)?);
let z = z_floor + i32::from(i8::read_from(buf)?);
to_blow.push(BlockPos { x, y, z });
}

View file

@ -1,71 +1,15 @@
use azalea_buf::{BufReadError, McBuf};
use azalea_buf::{McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
use azalea_buf::{McBuf, McBufReadable, McBufWritable};
use azalea_chat::Component;
use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write};
#[derive(Clone, Debug, ClientboundGamePacket)]
#[derive(Clone, Debug, ClientboundGamePacket, McBuf)]
pub struct ClientboundMapItemDataPacket {
// #[var]
#[var]
pub map_id: u32,
pub scale: u8,
pub locked: bool,
pub decorations: Vec<MapDecoration>,
pub color_patch: Option<MapPatch>,
}
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(())
}
pub decorations: Option<Vec<MapDecoration>>,
pub color_patch: OptionalMapPatch,
}
#[derive(Clone, Debug, McBuf)]
@ -80,11 +24,35 @@ pub struct MapDecoration {
}
#[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 start_x: u8,
pub start_y: u8,
pub width: u8,
pub height: u8,
pub start_x: u8,
pub start_y: u8,
pub map_colors: Vec<u8>,
}

View file

@ -1,5 +1,6 @@
use azalea_buf::{BufReadError, McBuf};
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ClientboundGamePacket;
use std::io::{Cursor, Write};
@ -21,31 +22,31 @@ pub struct PlayerAbilitiesFlags {
impl McBufReadable for PlayerAbilitiesFlags {
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 {
invulnerable: byte & 1 != 0,
flying: byte & 2 != 0,
can_fly: byte & 4 != 0,
instant_break: byte & 8 != 0,
invulnerable: set.index(0),
flying: set.index(1),
can_fly: set.index(2),
instant_break: set.index(3),
})
}
}
impl McBufWritable for PlayerAbilitiesFlags {
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 {
byte |= 0b1;
set.set(0);
}
if self.flying {
byte |= 0b10;
set.set(1);
}
if self.can_fly {
byte |= 0b100;
set.set(2);
}
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
/// for the whole message including the sender part, use
/// [`ClientboundPlayerChatPacket::message`].
#[must_use]
pub fn content(&self) -> Component {
self.unsigned_content
.clone()
@ -97,6 +98,7 @@ impl ClientboundPlayerChatPacket {
}
/// Get the full message, including the sender part.
#[must_use]
pub fn message(&self) -> Component {
let sender = self.chat_type.name.clone();
let content = self.content();
@ -119,6 +121,7 @@ impl ClientboundPlayerChatPacket {
}
impl ChatType {
#[must_use]
pub fn chat_translation_key(&self) -> &'static str {
match self {
ChatType::Chat => "chat.type.text",
@ -131,15 +134,11 @@ impl ChatType {
}
}
#[must_use]
pub fn narrator_translation_key(&self) -> &'static str {
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",
_ => "chat.type.text.narrate",
}
}
}

View file

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

View file

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

View file

@ -22,7 +22,7 @@ impl McBufReadable for Method {
0 => Method::Add(DisplayInfo::read_from(buf)?),
1 => Method::Remove,
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)?),
3 => Method::Join(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
self.objective_name
.as_ref()
.unwrap_or(&"".to_string())
.unwrap_or(&String::new())
.write_into(buf)?;
if let Method::Change { score } = self.method {
score.var_write_into(buf)?;

View file

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

View file

@ -17,10 +17,8 @@ pub struct ClientboundUpdateAdvancementsPacket {
pub struct Advancement {
parent_id: Option<ResourceLocation>,
display: Option<DisplayInfo>,
// rewards: AdvancementRewards.EMPTY,
criteria: HashMap<ResourceLocation, Criterion>,
requirements: Vec<Vec<String>>,
// requirements_strategy: RequirementsStrategy.AND
}
#[derive(Clone, Debug)]
@ -87,11 +85,11 @@ impl azalea_buf::McBufReadable for DisplayInfo {
description,
icon,
frame,
show_toast,
hidden,
background,
x,
y,
hidden,
show_toast,
})
}
}
@ -114,57 +112,77 @@ pub struct CriterionProgress {
date: Option<u64>,
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use azalea_buf::{McBufReadable, McBufWritable};
// use azalea_core::ResourceLocation;
// use azalea_protocol_macros::ClientboundGamePacket;
// use std::io::Cursor;
#[cfg(test)]
mod tests {
use super::*;
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::ResourceLocation;
use std::io::Cursor;
// #[test]
// fn test() {
// let mut buf = Cursor::new(Vec::new());
// let packet = ClientboundUpdateAdvancementsPacket {
// reset: true,
// added: [(
// ResourceLocation::new("minecraft:test").unwrap(),
// Advancement {
// parent_id: None,
// display: Some(DisplayInfo {
// title: Component::from("title".to_string()),
// description:
// Component::from("description".to_string()), icon:
// Slot::Empty, frame: FrameType::Task,
// show_toast: true,
// hidden: false,
// background: None,
// x: 0.0,
// y: 0.0,
// }),
// criteria: HashMap::new(),
// requirements: Vec::new(),
// },
// )]
// .into_iter()
// .collect(),
// removed: vec![ResourceLocation::new("minecraft:test2").unwrap()],
// progress: [(
// ResourceLocation::new("minecraft:test3").unwrap(),
// [(
// ResourceLocation::new("minecraft:test4").unwrap(),
// CriterionProgress {
// date: Some(123456789),
// },
// )]
// .into_iter()
// .collect(),
// )]
// .into_iter()
// .collect(),
// };
// packet.write_into(&mut buf).unwrap();
// let packet = ClientboundUpdateAdvancementsPacket::read_from(&mut
// buf).unwrap(); assert_eq!(packet.reset, true);
// }
// }
#[test]
fn test() {
let packet = ClientboundUpdateAdvancementsPacket {
reset: true,
added: [(
ResourceLocation::new("minecraft:test").unwrap(),
Advancement {
parent_id: None,
display: Some(DisplayInfo {
title: Component::from("title".to_string()),
description: Component::from("description".to_string()),
icon: Slot::Empty,
frame: FrameType::Task,
show_toast: true,
hidden: false,
background: None,
x: 0.0,
y: 0.0,
}),
criteria: HashMap::new(),
requirements: Vec::new(),
},
)]
.into_iter()
.collect(),
removed: vec![ResourceLocation::new("minecraft:test2").unwrap()],
progress: [(
ResourceLocation::new("minecraft:test3").unwrap(),
[(
ResourceLocation::new("minecraft:test4").unwrap(),
CriterionProgress {
date: Some(123456789),
},
)]
.into_iter()
.collect(),
)]
.into_iter()
.collect(),
};
let mut data = Vec::new();
packet.write_into(&mut data).unwrap();
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;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
#[derive(Clone, Debug, McBuf, ServerboundGamePacket, PartialEq, Eq)]
pub struct ServerboundClientInformationPacket {
/// The locale of the client.
pub language: String,
@ -14,7 +15,7 @@ pub struct ServerboundClientInformationPacket {
/// Whether the messages sent from the server should have colors. Note that
/// many servers ignore this and always send colored messages.
pub chat_colors: bool,
pub model_customisation: u8,
pub model_customisation: ModelCustomization,
pub main_hand: HumanoidArm,
pub text_filtering_enabled: bool,
/// Whether the client should show up as "Anonymous Player" in the server
@ -27,9 +28,9 @@ impl Default for ServerboundClientInformationPacket {
Self {
language: "en_us".to_string(),
view_distance: 8,
chat_visibility: ChatVisibility::Full,
chat_visibility: ChatVisibility::default(),
chat_colors: true,
model_customisation: 0,
model_customisation: ModelCustomization::default(),
main_hand: HumanoidArm::Right,
text_filtering_enabled: 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 {
/// All chat messages should be sent to the client.
#[default]
Full = 0,
/// 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.
@ -48,8 +50,125 @@ pub enum ChatVisibility {
Hidden = 2,
}
#[derive(McBuf, Clone, Copy, Debug)]
#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum HumanoidArm {
Left = 0,
#[default]
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)?;
Ok(ActionType::InteractAt {
location: Vec3 {
x: x as f64,
y: y as f64,
z: z as f64,
x: f64::from(x),
y: f64::from(y),
z: f64::from(z),
},
hand,
})

View file

@ -1,29 +1,29 @@
use crate::packets::BufReadError;
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundGamePacket;
use std::io::Cursor;
#[derive(Clone, Debug, ServerboundGamePacket)]
pub struct ServerboundPlayerAbilitiesPacket {
is_flying: bool,
pub is_flying: bool,
}
impl McBufReadable for ServerboundPlayerAbilitiesPacket {
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 {
is_flying: byte & 2 != 0,
is_flying: set.index(1),
})
}
}
impl McBufWritable for ServerboundPlayerAbilitiesPacket {
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 {
byte |= 2;
set.set(1);
}
u8::write_into(&byte, buf)?;
Ok(())
set.write_into(buf)
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -21,6 +21,7 @@ pub enum ConnectionProtocol {
}
impl ConnectionProtocol {
#[must_use]
pub fn from_i32(i: i32) -> Option<Self> {
match i {
-1 => Some(ConnectionProtocol::Handshake),
@ -39,7 +40,7 @@ where
{
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 write(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;

View file

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

View file

@ -19,6 +19,7 @@ pub enum ResolverError {
/// Resolve a Minecraft server address into an IP address and port.
/// If it's already an IP address, it's returned as-is.
#[must_use]
#[async_recursion]
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.