diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index b29ead3c..77303930 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -117,6 +117,9 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { GamePacket::ClientboundUpdateRecipesPacket(p) => { println!("Got update recipes packet {:?}", p); } + GamePacket::ClientboundEntityEventPacket(p) => { + println!("Got entity event packet {:?}", p); + } }, Err(e) => { panic!("Error: {:?}", e); diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs index 4c126b7e..d3382e0a 100755 --- a/azalea-protocol/src/mc_buf/read.rs +++ b/azalea-protocol/src/mc_buf/read.rs @@ -28,6 +28,7 @@ pub trait Readable { async fn read_resource_location(&mut self) -> Result; async fn read_short(&mut self) -> Result; async fn read_float(&mut self) -> Result; + async fn read_double(&mut self) -> Result; } #[async_trait] @@ -130,7 +131,6 @@ where self.read_exact(&mut buffer) .await .map_err(|_| "Invalid UTF-8".to_string())?; - string.push_str(std::str::from_utf8(&buffer).unwrap()); if string.len() > length as usize { return Err(format!( @@ -200,6 +200,13 @@ where Err(_) => Err("Error reading float".to_string()), } } + + async fn read_double(&mut self) -> Result { + match AsyncReadExt::read_f64(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading double".to_string()), + } + } } #[async_trait] @@ -399,6 +406,17 @@ impl McBufReadable for f32 { } } +// f64 +#[async_trait] +impl McBufReadable for f64 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_double().await + } +} + // GameType #[async_trait] impl McBufReadable for GameType { diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs index 2c46157b..b57ad786 100755 --- a/azalea-protocol/src/mc_buf/write.rs +++ b/azalea-protocol/src/mc_buf/write.rs @@ -42,6 +42,7 @@ pub trait Writable { location: &ResourceLocation, ) -> Result<(), std::io::Error>; fn write_float(&mut self, n: f32) -> Result<(), std::io::Error>; + fn write_double(&mut self, n: f64) -> Result<(), std::io::Error>; } #[async_trait] @@ -152,6 +153,10 @@ impl Writable for Vec { WriteBytesExt::write_f32::(self, n) } + fn write_double(&mut self, n: f64) -> Result<(), std::io::Error> { + WriteBytesExt::write_f64::(self, n) + } + fn write_resource_location( &mut self, location: &ResourceLocation, @@ -291,6 +296,13 @@ impl McBufWritable for f32 { } } +// f64 +impl McBufWritable for f64 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_double(*self) + } +} + // GameType impl McBufWritable for GameType { fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { diff --git a/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs index 2df78ce9..0a16440e 100755 --- a/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs @@ -1,8 +1,9 @@ use super::GamePacket; -use crate::mc_buf::{McBufReadable, Readable}; +use crate::mc_buf::{McBufReadable, McBufWritable, Readable}; use async_trait::async_trait; +use azalea_core::resource_location::ResourceLocation; use std::hash::Hash; -use tokio::io::AsyncRead; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; #[derive(Hash, Clone, Debug)] pub struct ClientboundDeclareCommandsPacket { @@ -23,6 +24,7 @@ impl ClientboundDeclareCommandsPacket { buf: &mut T, ) -> Result { let node_count = buf.read_varint().await?; + println!("node_count: {}", node_count); let mut nodes = Vec::with_capacity(node_count as usize); for _ in 0..node_count { let node = BrigadierNodeStub::read_into(buf).await?; @@ -41,6 +43,268 @@ impl ClientboundDeclareCommandsPacket { #[derive(Hash, Debug, Clone)] pub struct BrigadierNodeStub {} +#[derive(Debug, Clone)] +pub struct BrigadierNumber { + min: Option, + max: Option, +} +#[async_trait] +impl McBufReadable for BrigadierNumber { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let flags = buf.read_byte().await?; + let min = if flags & 0x01 != 0 { + Some(T::read_into(buf).await?) + } else { + None + }; + let max = if flags & 0x02 != 0 { + Some(T::read_into(buf).await?) + } else { + None + }; + Ok(BrigadierNumber { min, max }) + } +} +impl McBufWritable for BrigadierNumber { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + let mut flags = 0; + if self.min.is_some() { + flags |= 0x01; + } + if self.max.is_some() { + flags |= 0x02; + } + buf.write_i8(flags); + if let Some(min) = &self.min { + min.write_into(buf)?; + } + if let Some(max) = &self.max { + max.write_into(buf)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum BrigadierString { + /// Reads a single word + SingleWord = 0, + // If it starts with a ", keeps reading until another " (allowing escaping with \). Otherwise behaves the same as SINGLE_WORD + QuotablePhrase = 1, + // Reads the rest of the content after the cursor. Quotes will not be removed. + GreedyPhrase = 2, +} + +#[async_trait] +impl McBufReadable for BrigadierString { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let id = buf.read_byte().await?; + Ok(match id { + 0 => BrigadierString::SingleWord, + 1 => BrigadierString::QuotablePhrase, + 2 => BrigadierString::GreedyPhrase, + _ => panic!("Unknown BrigadierString id: {}", id), + }) + } +} +impl McBufWritable for BrigadierString { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_i8(*self as i8); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub enum BrigadierParser { + Bool, + Double(BrigadierNumber), + Float(BrigadierNumber), + Integer(BrigadierNumber), + Long(BrigadierNumber), + String(BrigadierString), + Entity { single: bool, players_only: bool }, + GameProfile, + BlockPos, + ColumnPos, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Message, + Nbt, + NbtPath, + Objective, + ObjectiveCriteira, + Operation, + Particle, + Rotation, + Angle, + ScoreboardSlot, + ScoreHolder { allows_multiple: bool }, + Swizzle, + Team, + ItemSlot, + ResourceLocation, + MobEffect, + Function, + EntityAnchor, + Range { decimals_allowed: bool }, + IntRange, + FloatRange, + ItemEnchantment, + EntitySummon, + Dimension, + Uuid, + NbtTag, + NbtCompoundTag, + Time, + ResourceOrTag { registry_key: ResourceLocation }, + Resource { registry_key: ResourceLocation }, +} + +#[async_trait] +impl McBufReadable for BrigadierParser { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let parser = buf.read_resource_location().await?; + + if parser == ResourceLocation::new("brigadier:bool")? { + Ok(BrigadierParser::Bool) + } else if parser == ResourceLocation::new("brigadier:double")? { + Ok(BrigadierParser::Double( + BrigadierNumber::read_into(buf).await?, + )) + } else if parser == ResourceLocation::new("brigadier:float")? { + Ok(BrigadierParser::Float( + BrigadierNumber::read_into(buf).await?, + )) + } else if parser == ResourceLocation::new("brigadier:integer")? { + Ok(BrigadierParser::Integer( + BrigadierNumber::read_into(buf).await?, + )) + } else if parser == ResourceLocation::new("brigadier:long")? { + Ok(BrigadierParser::Long( + BrigadierNumber::read_into(buf).await?, + )) + } else if parser == ResourceLocation::new("brigadier:string")? { + Ok(BrigadierParser::String( + BrigadierString::read_into(buf).await?, + )) + } else if parser == ResourceLocation::new("minecraft:entity")? { + let flags = buf.read_byte().await?; + Ok(BrigadierParser::Entity { + single: flags & 0x01 != 0, + players_only: flags & 0x02 != 0, + }) + } else if parser == ResourceLocation::new("minecraft:game_profile")? { + Ok(BrigadierParser::GameProfile) + } else if parser == ResourceLocation::new("minecraft:block_pos")? { + Ok(BrigadierParser::BlockPos) + } else if parser == ResourceLocation::new("minecraft:column_pos")? { + Ok(BrigadierParser::ColumnPos) + } else if parser == ResourceLocation::new("minecraft:vec3")? { + Ok(BrigadierParser::Vec3) + } else if parser == ResourceLocation::new("minecraft:vec2")? { + Ok(BrigadierParser::Vec2) + } else if parser == ResourceLocation::new("minecraft:block_state")? { + Ok(BrigadierParser::BlockState) + } else if parser == ResourceLocation::new("minecraft:block_predicate")? { + Ok(BrigadierParser::BlockPredicate) + } else if parser == ResourceLocation::new("minecraft:item_stack")? { + Ok(BrigadierParser::ItemStack) + } else if parser == ResourceLocation::new("minecraft:item_predicate")? { + Ok(BrigadierParser::ItemPredicate) + } else if parser == ResourceLocation::new("minecraft:color")? { + Ok(BrigadierParser::Color) + } else if parser == ResourceLocation::new("minecraft:component")? { + Ok(BrigadierParser::Component) + } else if parser == ResourceLocation::new("minecraft:message")? { + Ok(BrigadierParser::Message) + } else if parser == ResourceLocation::new("minecraft:nbt")? { + Ok(BrigadierParser::Nbt) + } else if parser == ResourceLocation::new("minecraft:nbt_path")? { + Ok(BrigadierParser::NbtPath) + } else if parser == ResourceLocation::new("minecraft:objective")? { + Ok(BrigadierParser::Objective) + } else if parser == ResourceLocation::new("minecraft:objective_criteria")? { + Ok(BrigadierParser::ObjectiveCriteira) + } else if parser == ResourceLocation::new("minecraft:operation")? { + Ok(BrigadierParser::Operation) + } else if parser == ResourceLocation::new("minecraft:particle")? { + Ok(BrigadierParser::Particle) + } else if parser == ResourceLocation::new("minecraft:rotation")? { + Ok(BrigadierParser::Rotation) + } else if parser == ResourceLocation::new("minecraft:angle")? { + Ok(BrigadierParser::Angle) + } else if parser == ResourceLocation::new("minecraft:scoreboard_slot")? { + Ok(BrigadierParser::ScoreboardSlot) + } else if parser == ResourceLocation::new("minecraft:score_holder")? { + let flags = buf.read_byte().await?; + Ok(BrigadierParser::ScoreHolder { + allows_multiple: flags & 0x01 != 0, + }) + } else if parser == ResourceLocation::new("minecraft:swizzle")? { + Ok(BrigadierParser::Swizzle) + } else if parser == ResourceLocation::new("minecraft:team")? { + Ok(BrigadierParser::Team) + } else if parser == ResourceLocation::new("minecraft:item_slot")? { + Ok(BrigadierParser::ItemSlot) + } else if parser == ResourceLocation::new("minecraft:resource_location")? { + Ok(BrigadierParser::ResourceLocation) + } else if parser == ResourceLocation::new("minecraft:mob_effect")? { + Ok(BrigadierParser::MobEffect) + } else if parser == ResourceLocation::new("minecraft:function")? { + Ok(BrigadierParser::Function) + } else if parser == ResourceLocation::new("minecraft:entity_anchor")? { + Ok(BrigadierParser::EntityAnchor) + } else if parser == ResourceLocation::new("minecraft:range")? { + Ok(BrigadierParser::Range { + decimals_allowed: buf.read_boolean().await?, + }) + } else if parser == ResourceLocation::new("minecraft:int_range")? { + Ok(BrigadierParser::IntRange) + } else if parser == ResourceLocation::new("minecraft:float_range")? { + Ok(BrigadierParser::FloatRange) + } else if parser == ResourceLocation::new("minecraft:item_enchantment")? { + Ok(BrigadierParser::ItemEnchantment) + } else if parser == ResourceLocation::new("minecraft:entity_summon")? { + Ok(BrigadierParser::EntitySummon) + } else if parser == ResourceLocation::new("minecraft:dimension")? { + Ok(BrigadierParser::Dimension) + } else if parser == ResourceLocation::new("minecraft:uuid")? { + Ok(BrigadierParser::Uuid) + } else if parser == ResourceLocation::new("minecraft:nbt_tag")? { + Ok(BrigadierParser::NbtTag) + } else if parser == ResourceLocation::new("minecraft:nbt_compound_tag")? { + Ok(BrigadierParser::NbtCompoundTag) + } else if parser == ResourceLocation::new("minecraft:time")? { + Ok(BrigadierParser::Time) + } else if parser == ResourceLocation::new("minecraft:resource_or_tag")? { + Ok(BrigadierParser::ResourceOrTag { + registry_key: buf.read_resource_location().await?, + }) + } else if parser == ResourceLocation::new("minecraft:resource")? { + Ok(BrigadierParser::Resource { + registry_key: buf.read_resource_location().await?, + }) + } else { + panic!("Unknown Brigadier parser: {}", parser) + } + } +} + // azalea_brigadier::tree::CommandNode #[async_trait] impl McBufReadable for BrigadierNodeStub { @@ -49,33 +313,46 @@ impl McBufReadable for BrigadierNodeStub { R: AsyncRead + std::marker::Unpin + std::marker::Send, { let flags = u8::read_into(buf).await?; + if flags > 31 { + println!( + "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; + println!("flags: {}, node_type: {}, is_executable: {}, has_redirect: {}, has_suggestions_type: {}", flags, node_type, is_executable, has_redirect, has_suggestions_type); let children = buf.read_int_id_list().await?; + println!("children: {:?}", children); let redirect_node = if has_redirect { buf.read_varint().await? } else { 0 }; + println!("redirect_node: {}", redirect_node); + // argument node if node_type == 2 { let name = buf.read_utf().await?; + println!("name: {}", name); - let resource_location = if has_suggestions_type { + let parser = BrigadierParser::read_into(buf).await?; + + let suggestions_type = if has_suggestions_type { Some(buf.read_resource_location().await?) } else { None }; println!( - "node_type=2, flags={}, name={}, resource_location={:?}", - flags, name, resource_location + "node_type=2, flags={}, name={}, parser={:?}, suggestions_type={:?}", + flags, name, parser, suggestions_type ); return Ok(BrigadierNodeStub {}); } + // literal node if node_type == 1 { let name = buf.read_utf().await?; println!("node_type=1, flags={}, name={}", flags, name); diff --git a/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs new file mode 100644 index 00000000..82b0072d --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs @@ -0,0 +1,9 @@ +use azalea_core::{game_type::GameType, resource_location::ResourceLocation}; +use packet_macros::GamePacket; + +// we can't identify the status in azalea-protocol since they vary depending on the entity +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundEntityEventPacket { + pub entity_id: i32, + pub entity_status: i8, +} diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs index 986a7010..fefeb08b 100755 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -2,6 +2,7 @@ pub mod clientbound_change_difficulty_packet; pub mod clientbound_custom_payload_packet; pub mod clientbound_declare_commands_packet; pub mod clientbound_disconnect_packet; +pub mod clientbound_entity_event_packet; pub mod clientbound_login_packet; pub mod clientbound_player_abilities_packet; pub mod clientbound_set_carried_item_packet; @@ -18,6 +19,7 @@ declare_state_packets!( 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, 0x12: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket, 0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket, + 0x1b: clientbound_entity_event_packet::ClientboundEntityEventPacket, 0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket, 0x26: clientbound_login_packet::ClientboundLoginPacket, 0x32: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, diff --git a/bot/src/main.rs b/bot/src/main.rs index b13a608b..d7905661 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -3,7 +3,7 @@ async fn main() { println!("Hello, world!"); // let address = "95.111.249.143:10000"; - let address = "localhost:57308"; + let address = "localhost:53810"; // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // .await // .unwrap();