aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-06-25 05:09:26 +0000
committerGitHub <noreply@github.com>2022-06-25 05:09:26 +0000
commit7d3e57763e32ac9cf94180b1c714704cfbc3034d (patch)
tree2dcfe72bf09a42f6614f9dc988dc0254162ea0bf
parent69c47eda4c496b13dadd80976bffd2fab7ea5894 (diff)
parentca7067e173129f3044ebc8c77634f06da29a086e (diff)
downloadazalea-drasl-7d3e57763e32ac9cf94180b1c714704cfbc3034d.tar.xz
Merge pull request #10 from mat-1/azalea-entity
azalea-entity
-rwxr-xr-xCargo.lock67
-rw-r--r--Cargo.toml2
-rwxr-xr-xazalea-auth/Cargo.toml2
-rw-r--r--azalea-buf/Cargo.toml12
-rw-r--r--azalea-buf/README.md3
-rw-r--r--azalea-buf/buf-macros/Cargo.toml13
-rw-r--r--azalea-buf/buf-macros/src/lib.rs177
-rw-r--r--azalea-buf/src/definitions.rs56
-rw-r--r--azalea-buf/src/lib.rs (renamed from azalea-protocol/src/mc_buf/mod.rs)56
-rw-r--r--azalea-buf/src/read.rs (renamed from azalea-protocol/src/mc_buf/read.rs)149
-rw-r--r--[-rwxr-xr-x]azalea-buf/src/serializable_uuid.rs (renamed from azalea-core/src/serializable_uuid.rs)27
-rw-r--r--azalea-buf/src/write.rs (renamed from azalea-protocol/src/mc_buf/write.rs)154
-rwxr-xr-xazalea-chat/Cargo.toml1
-rwxr-xr-xazalea-chat/src/component.rs30
-rwxr-xr-xazalea-client/Cargo.toml3
-rw-r--r--azalea-client/src/account.rs20
-rw-r--r--azalea-client/src/client.rs673
-rwxr-xr-xazalea-client/src/connect.rs477
-rw-r--r--azalea-client/src/entity.rs8
-rwxr-xr-xazalea-client/src/lib.rs9
-rw-r--r--azalea-client/src/movement.rs45
-rw-r--r--azalea-client/src/player.rs26
-rwxr-xr-xazalea-core/Cargo.toml1
-rw-r--r--azalea-core/src/delta.rs68
-rwxr-xr-xazalea-core/src/difficulty.rs27
-rw-r--r--azalea-core/src/direction.rs4
-rwxr-xr-xazalea-core/src/game_type.rs54
-rwxr-xr-xazalea-core/src/lib.rs18
-rw-r--r--azalea-core/src/particle/mod.rs (renamed from azalea-protocol/src/mc_buf/definitions.rs)184
-rw-r--r--azalea-core/src/position.rs258
-rwxr-xr-xazalea-core/src/resource_location.rs33
-rw-r--r--azalea-core/src/slot.rs30
-rw-r--r--azalea-crypto/Cargo.toml1
-rw-r--r--azalea-crypto/src/signing.rs19
-rw-r--r--azalea-entity/Cargo.toml13
-rw-r--r--azalea-entity/src/data.rs144
-rw-r--r--azalea-entity/src/lib.rs59
-rwxr-xr-xazalea-nbt/Cargo.toml1
-rwxr-xr-xazalea-nbt/src/decode.rs11
-rwxr-xr-xazalea-nbt/src/encode.rs8
-rwxr-xr-xazalea-nbt/src/lib.rs34
-rwxr-xr-xazalea-protocol/Cargo.toml21
-rwxr-xr-xazalea-protocol/packet-macros/src/lib.rs181
-rwxr-xr-xazalea-protocol/src/lib.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs19
-rw-r--r--azalea-protocol/src/packets/game/clientbound_add_player_packet.rs22
-rw-r--r--azalea-protocol/src/packets/game/clientbound_animate_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_block_changed_ack_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_block_update_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs5
-rw-r--r--azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs7
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs15
-rw-r--r--azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_entity_velocity_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_game_event_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_initialize_border_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_keep_alive_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_level_event_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_level_particles_packet.rs4
-rw-r--r--azalea-protocol/src/packets/game/clientbound_light_update_packet.rs5
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_login_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs10
-rw-r--r--azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs11
-rw-r--r--azalea-protocol/src/packets/game/clientbound_move_entity_rot_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs5
-rw-r--r--azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_player_info_packet.rs5
-rw-r--r--azalea-protocol/src/packets/game/clientbound_player_position_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_recipe_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_remove_entities_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_rotate_head_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_section_blocks_update_packet.rs5
-rw-r--r--azalea-protocol/src/packets/game/clientbound_server_data_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_chunk_cache_center_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_display_chat_preview_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_entity_link_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_equipment_packet.rs5
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_experience_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_health_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_time_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_sound_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs6
-rw-r--r--azalea-protocol/src/packets/game/clientbound_update_attributes_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs16
-rw-r--r--azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs11
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_update_tags_packet.rs33
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/game/mod.rs14
-rw-r--r--azalea-protocol/src/packets/game/serverbound_accept_teleportation_packet.rs8
-rw-r--r--azalea-protocol/src/packets/game/serverbound_chat_command_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/serverbound_chat_preview_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/serverbound_custom_payload_packet.rs7
-rw-r--r--azalea-protocol/src/packets/game/serverbound_keep_alive_packet.rs3
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_packet_pos.rs10
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_packet_pos_rot.rs12
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_packet_rot.rs9
-rw-r--r--azalea-protocol/src/packets/game/serverbound_move_player_packet_status_only.rs7
-rwxr-xr-xazalea-protocol/src/packets/handshake/client_intention_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/login/clientbound_custom_query_packet.rs6
-rwxr-xr-xazalea-protocol/src/packets/login/clientbound_game_profile_packet.rs4
-rwxr-xr-xazalea-protocol/src/packets/login/clientbound_hello_packet.rs2
-rwxr-xr-xazalea-protocol/src/packets/login/clientbound_login_compression_packet.rs2
-rw-r--r--azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs3
-rwxr-xr-xazalea-protocol/src/packets/login/serverbound_hello_packet.rs3
-rw-r--r--azalea-protocol/src/packets/login/serverbound_key_packet.rs5
-rwxr-xr-xazalea-protocol/src/packets/mod.rs11
-rwxr-xr-xazalea-protocol/src/packets/status/clientbound_status_response_packet.rs2
-rwxr-xr-xazalea-protocol/src/packets/status/serverbound_status_request_packet.rs3
-rwxr-xr-xazalea-protocol/src/read.rs7
-rwxr-xr-xazalea-protocol/src/write.rs3
-rw-r--r--azalea-world/Cargo.toml11
-rw-r--r--azalea-world/src/bit_storage.rs6
-rw-r--r--azalea-world/src/chunk.rs221
-rw-r--r--azalea-world/src/entity.rs145
-rw-r--r--azalea-world/src/lib.rs276
-rw-r--r--azalea-world/src/palette.rs2
-rwxr-xr-xbot/Cargo.toml1
-rw-r--r--bot/src/main.rs54
-rw-r--r--codegen/lib/code/packet.py67
-rw-r--r--examples/craft_dig_straight_down.rs6
-rw-r--r--examples/pvp.rs33
130 files changed, 2790 insertions, 1657 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9e4f14f5..5911b133 100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -90,9 +90,20 @@ name = "azalea-brigadier"
version = "0.1.0"
[[package]]
+name = "azalea-buf"
+version = "0.1.0"
+dependencies = [
+ "buf-macros",
+ "byteorder",
+ "tokio",
+ "uuid",
+]
+
+[[package]]
name = "azalea-chat"
version = "0.1.0"
dependencies = [
+ "azalea-buf",
"azalea-language",
"lazy_static",
"serde",
@@ -106,15 +117,19 @@ dependencies = [
"azalea-auth",
"azalea-core",
"azalea-crypto",
+ "azalea-entity",
"azalea-protocol",
"azalea-world",
+ "owning_ref",
"tokio",
+ "uuid",
]
[[package]]
name = "azalea-core"
version = "0.1.0"
dependencies = [
+ "azalea-buf",
"azalea-chat",
"azalea-nbt",
"uuid",
@@ -125,6 +140,7 @@ name = "azalea-crypto"
version = "0.1.0"
dependencies = [
"aes",
+ "azalea-buf",
"cfb8",
"num-bigint",
"rand",
@@ -133,6 +149,17 @@ dependencies = [
]
[[package]]
+name = "azalea-entity"
+version = "0.1.0"
+dependencies = [
+ "azalea-buf",
+ "azalea-chat",
+ "azalea-core",
+ "azalea-nbt",
+ "uuid",
+]
+
+[[package]]
name = "azalea-language"
version = "0.1.0"
dependencies = [
@@ -145,6 +172,7 @@ dependencies = [
name = "azalea-nbt"
version = "0.1.0"
dependencies = [
+ "azalea-buf",
"byteorder",
"criterion",
"flate2",
@@ -160,9 +188,11 @@ dependencies = [
"async-recursion",
"azalea-auth",
"azalea-brigadier",
+ "azalea-buf",
"azalea-chat",
"azalea-core",
"azalea-crypto",
+ "azalea-entity",
"azalea-nbt",
"byteorder",
"bytes",
@@ -180,9 +210,13 @@ name = "azalea-world"
version = "0.1.0"
dependencies = [
"azalea-block",
+ "azalea-buf",
"azalea-core",
+ "azalea-entity",
"azalea-nbt",
- "azalea-protocol",
+ "log",
+ "nohash-hasher",
+ "uuid",
]
[[package]]
@@ -217,6 +251,7 @@ dependencies = [
"azalea-core",
"azalea-protocol",
"tokio",
+ "uuid",
]
[[package]]
@@ -232,6 +267,15 @@ dependencies = [
]
[[package]]
+name = "buf-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -745,6 +789,12 @@ dependencies = [
]
[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -854,6 +904,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
+name = "owning_ref"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
name = "packet-macros"
version = "0.1.0"
dependencies = [
@@ -1189,6 +1248,12 @@ dependencies = [
]
[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
name = "syn"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 9718244e..c40178b3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,8 @@ members = [
"azalea-world",
"azalea-language",
"azalea-block",
+ "azalea-entity",
+ "azalea-buf",
]
[profile.release]
diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml
index a7a11b53..fe306186 100755
--- a/azalea-auth/Cargo.toml
+++ b/azalea-auth/Cargo.toml
@@ -6,4 +6,4 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-uuid = "^1.1.2"
+uuid = "1.1.2"
diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml
new file mode 100644
index 00000000..4321dace
--- /dev/null
+++ b/azalea-buf/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+edition = "2021"
+name = "azalea-buf"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+buf-macros = {path = "./buf-macros"}
+byteorder = "1.4.3"
+tokio = {version = "^1.19.2", features = ["io-util", "net", "macros"]}
+uuid = "1.1.2"
diff --git a/azalea-buf/README.md b/azalea-buf/README.md
new file mode 100644
index 00000000..c988bcdb
--- /dev/null
+++ b/azalea-buf/README.md
@@ -0,0 +1,3 @@
+# Azalea Buf
+
+An implementation of Minecraft's FriendlyByteBuf. This is used frequently in the game for serialization and deserialization of data.
diff --git a/azalea-buf/buf-macros/Cargo.toml b/azalea-buf/buf-macros/Cargo.toml
new file mode 100644
index 00000000..fecf64ed
--- /dev/null
+++ b/azalea-buf/buf-macros/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+edition = "2021"
+name = "buf-macros"
+version = "0.1.0"
+
+[lib]
+proc-macro = true
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+proc-macro2 = "^1.0.36"
+quote = "^1.0.10"
+syn = "^1.0.82"
diff --git a/azalea-buf/buf-macros/src/lib.rs b/azalea-buf/buf-macros/src/lib.rs
new file mode 100644
index 00000000..4cc397a2
--- /dev/null
+++ b/azalea-buf/buf-macros/src/lib.rs
@@ -0,0 +1,177 @@
+use proc_macro::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{self, parse_macro_input, Data, DeriveInput, FieldsNamed, Ident};
+
+fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
+ match data {
+ syn::Data::Struct(syn::DataStruct { fields, .. }) => {
+ let FieldsNamed { named, .. } = match fields {
+ syn::Fields::Named(f) => f,
+ _ => panic!("#[derive(McBuf)] can only be used on structs with named fields"),
+ };
+
+ let read_fields = named
+ .iter()
+ .map(|f| {
+ let field_name = &f.ident;
+ let field_type = &f.ty;
+ // do a different buf.write_* for each field depending on the type
+ // if it's a string, use buf.write_string
+ match field_type {
+ syn::Type::Path(_) => {
+ if f.attrs.iter().any(|a| a.path.is_ident("var")) {
+ quote! {
+ let #field_name = azalea_buf::McBufVarReadable::var_read_into(buf)?;
+ }
+ } else {
+ quote! {
+ let #field_name = azalea_buf::McBufReadable::read_into(buf)?;
+ }
+ }
+ }
+ _ => panic!(
+ "Error reading field {}: {}",
+ field_name.clone().unwrap(),
+ field_type.to_token_stream()
+ ),
+ }
+ })
+ .collect::<Vec<_>>();
+ let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
+
+ quote! {
+ impl azalea_buf::McBufReadable for #ident {
+ fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String> {
+ #(#read_fields)*
+ Ok(#ident {
+ #(#read_field_names: #read_field_names),*
+ })
+ }
+ }
+ }
+ }
+ syn::Data::Enum(syn::DataEnum { variants, .. }) => {
+ let mut match_contents = quote!();
+ let mut variant_discrim: u32 = 0;
+ for variant in variants {
+ let variant_name = &variant.ident;
+ match &variant.discriminant.as_ref() {
+ Some(d) => {
+ variant_discrim = match &d.1 {
+ syn::Expr::Lit(e) => match &e.lit {
+ syn::Lit::Int(i) => i.base10_parse().unwrap(),
+ _ => panic!("Error parsing enum discriminant"),
+ },
+ _ => panic!("Error parsing enum discriminant"),
+ }
+ }
+ None => {
+ variant_discrim += 1;
+ }
+ }
+ match_contents.extend(quote! {
+ #variant_discrim => Ok(Self::#variant_name),
+ });
+ }
+
+ quote! {
+ impl azalea_buf::McBufReadable for #ident {
+ fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String>
+ {
+ let id = azalea_buf::McBufVarReadable::var_read_into(buf)?;
+ match id {
+ #match_contents
+ _ => Err(format!("Unknown enum variant {}", id)),
+ }
+ }
+ }
+ }
+ }
+ _ => panic!("#[derive(McBuf)] can only be used on structs"),
+ }
+}
+
+fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
+ match data {
+ syn::Data::Struct(syn::DataStruct { fields, .. }) => {
+ let FieldsNamed { named, .. } = match fields {
+ syn::Fields::Named(f) => f,
+ _ => panic!("#[derive(McBuf)] can only be used on structs with named fields"),
+ };
+
+ let write_fields = named
+ .iter()
+ .map(|f| {
+ let field_name = &f.ident;
+ let field_type = &f.ty;
+ // do a different buf.write_* for each field depending on the type
+ // if it's a string, use buf.write_string
+ match field_type {
+ syn::Type::Path(_) => {
+ if f.attrs.iter().any(|attr| attr.path.is_ident("var")) {
+ quote! {
+ azalea_buf::McBufVarWritable::var_write_into(&self.#field_name, buf)?;
+ }
+ } else {
+ quote! {
+ azalea_buf::McBufWritable::write_into(&self.#field_name, buf)?;
+ }
+ }
+ }
+ _ => panic!(
+ "Error writing field {}: {}",
+ field_name.clone().unwrap(),
+ field_type.to_token_stream()
+ ),
+ }
+ })
+ .collect::<Vec<_>>();
+
+ quote! {
+ impl azalea_buf::McBufWritable for #ident {
+ fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
+ #(#write_fields)*
+ Ok(())
+ }
+ }
+ }
+ }
+ syn::Data::Enum(syn::DataEnum { .. }) => {
+ quote! {
+ impl azalea_buf::McBufWritable for #ident {
+ fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
+ azalea_buf::Writable::write_varint(buf, *self as i32)
+ }
+ }
+ }
+ }
+ _ => panic!("#[derive(McBuf)] can only be used on structs"),
+ }
+}
+
+#[proc_macro_derive(McBufReadable, attributes(var))]
+pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream {
+ let DeriveInput { ident, data, .. } = parse_macro_input!(input);
+
+ create_impl_mcbufreadable(&ident, &data).into()
+}
+
+#[proc_macro_derive(McBufWritable, attributes(var))]
+pub fn derive_mcbufwritable(input: TokenStream) -> TokenStream {
+ let DeriveInput { ident, data, .. } = parse_macro_input!(input);
+
+ create_impl_mcbufwritable(&ident, &data).into()
+}
+
+#[proc_macro_derive(McBuf, attributes(var))]
+pub fn derive_mcbuf(input: TokenStream) -> TokenStream {
+ let DeriveInput { ident, data, .. } = parse_macro_input!(input);
+
+ let writable = create_impl_mcbufwritable(&ident, &data);
+ let readable = create_impl_mcbufreadable(&ident, &data);
+ quote! {
+ #writable
+ #readable
+ }
+ .into()
+}
diff --git a/azalea-buf/src/definitions.rs b/azalea-buf/src/definitions.rs
new file mode 100644
index 00000000..921fb63d
--- /dev/null
+++ b/azalea-buf/src/definitions.rs
@@ -0,0 +1,56 @@
+use crate::{McBufReadable, McBufWritable};
+use std::{
+ io::{Read, Write},
+ ops::Deref,
+};
+
+/// A Vec<u8> that isn't prefixed by a VarInt with the size.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct UnsizedByteArray(Vec<u8>);
+
+impl Deref for UnsizedByteArray {
+ type Target = Vec<u8>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl From<Vec<u8>> for UnsizedByteArray {
+ fn from(vec: Vec<u8>) -> Self {
+ Self(vec)
+ }
+}
+
+impl From<&str> for UnsizedByteArray {
+ fn from(s: &str) -> Self {
+ Self(s.as_bytes().to_vec())
+ }
+}
+
+/// Represents Java's BitSet, a list of bits.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BitSet {
+ data: Vec<u64>,
+}
+
+// the Index trait requires us to return a reference, but we can't do that
+impl BitSet {
+ pub fn index(&self, index: usize) -> bool {
+ (self.data[index / 64] & (1u64 << (index % 64))) != 0
+ }
+}
+
+impl McBufReadable for BitSet {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ Ok(Self {
+ data: Vec::<u64>::read_into(buf)?,
+ })
+ }
+}
+
+impl McBufWritable for BitSet {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.data.write_into(buf)
+ }
+}
diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-buf/src/lib.rs
index 548ba7c2..cd72f22e 100644
--- a/azalea-protocol/src/mc_buf/mod.rs
+++ b/azalea-buf/src/lib.rs
@@ -1,26 +1,27 @@
//! Utilities for reading and writing for the Minecraft protocol
+#![feature(min_specialization)]
+#![feature(arbitrary_enum_discriminant)]
+
mod definitions;
mod read;
+mod serializable_uuid;
mod write;
-pub use definitions::{BitSet, EntityMetadata, ParticleData, UnsizedByteArray};
+pub use buf_macros::*;
+pub use definitions::*;
pub use read::{read_varint_async, McBufReadable, McBufVarReadable, Readable};
+pub use serializable_uuid::*;
pub use write::{McBufVarWritable, McBufWritable, Writable};
// const DEFAULT_NBT_QUOTA: u32 = 2097152;
const MAX_STRING_LENGTH: u16 = 32767;
// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144;
-// TODO: maybe get rid of the readable/writable traits so there's not two ways to do the same thing and improve McBufReadable/McBufWritable
-
-// TODO: have a definitions.rs in mc_buf that contains UnsizedByteArray and BitSet
-
#[cfg(test)]
mod tests {
use super::*;
- use azalea_core::resource_location::ResourceLocation;
- use std::{collections::HashMap, io::Cursor};
+ use std::io::Cursor;
#[test]
fn test_write_varint() {
@@ -181,33 +182,6 @@ mod tests {
}
#[test]
- fn test_nbt() {
- let mut buf = Vec::new();
- buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "hello world".to_string(),
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "name".to_string(),
- azalea_nbt::Tag::String("Bananrama".to_string()),
- )])),
- )])))
- .unwrap();
-
- let mut buf = Cursor::new(buf);
-
- let result = buf.read_nbt().unwrap();
- assert_eq!(
- result,
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "hello world".to_string(),
- azalea_nbt::Tag::Compound(HashMap::from_iter(vec![(
- "name".to_string(),
- azalea_nbt::Tag::String("Bananrama".to_string()),
- )])),
- )]))
- );
- }
-
- #[test]
fn test_long() {
let mut buf = Vec::new();
buf.write_long(123456).unwrap();
@@ -216,18 +190,4 @@ mod tests {
assert_eq!(buf.read_long().unwrap(), 123456);
}
-
- #[test]
- fn test_resource_location() {
- let mut buf = Vec::new();
- buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap())
- .unwrap();
-
- let mut buf = Cursor::new(buf);
-
- assert_eq!(
- buf.read_resource_location().unwrap(),
- ResourceLocation::new("minecraft:dirt").unwrap()
- );
- }
}
diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-buf/src/read.rs
index ac8d5ccb..569a5b1d 100644
--- a/azalea-protocol/src/mc_buf/read.rs
+++ b/azalea-buf/src/read.rs
@@ -1,16 +1,9 @@
use super::{UnsizedByteArray, MAX_STRING_LENGTH};
-use azalea_chat::component::Component;
-use azalea_core::{
- difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
- serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, GlobalPos, Slot,
- SlotData,
-};
-use azalea_crypto::SaltSignaturePair;
use byteorder::{ReadBytesExt, BE};
-use serde::Deserialize;
use std::{collections::HashMap, hash::Hash, io::Read};
use tokio::io::{AsyncRead, AsyncReadExt};
-use uuid::Uuid;
+
+// TODO: get rid of Readable and use McBufReadable everywhere
pub trait Readable {
fn read_int_id_list(&mut self) -> Result<Vec<i32>, String>;
@@ -25,13 +18,10 @@ pub trait Readable {
fn read_byte(&mut self) -> Result<u8, String>;
fn read_int(&mut self) -> Result<i32, String>;
fn read_boolean(&mut self) -> Result<bool, String>;
- fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String>;
fn read_long(&mut self) -> Result<i64, String>;
- fn read_resource_location(&mut self) -> Result<ResourceLocation, String>;
fn read_short(&mut self) -> Result<i16, String>;
fn read_float(&mut self) -> Result<f32, String>;
fn read_double(&mut self) -> Result<f64, String>;
- fn read_uuid(&mut self) -> Result<Uuid, String>;
}
impl<R> Readable for R
@@ -160,14 +150,6 @@ where
}
}
- fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String> {
- match azalea_nbt::Tag::read(self) {
- Ok(r) => Ok(r),
- // Err(e) => Err(e.to_string()),
- Err(e) => Err(e.to_string()).unwrap(),
- }
- }
-
fn read_long(&mut self) -> Result<i64, String> {
match self.read_i64::<BE>() {
Ok(r) => Ok(r),
@@ -175,13 +157,6 @@ where
}
}
- fn read_resource_location(&mut self) -> Result<ResourceLocation, String> {
- // get the resource location from the string
- let location_string = self.read_utf()?;
- let location = ResourceLocation::new(&location_string)?;
- Ok(location)
- }
-
fn read_short(&mut self) -> Result<i16, String> {
match self.read_i16::<BE>() {
Ok(r) => Ok(r),
@@ -202,15 +177,6 @@ where
Err(_) => Err("Error reading double".to_string()),
}
}
-
- fn read_uuid(&mut self) -> Result<Uuid, String> {
- Ok(Uuid::from_int_array([
- Readable::read_int(self)? as u32,
- Readable::read_int(self)? as u32,
- Readable::read_int(self)? as u32,
- Readable::read_int(self)? as u32,
- ]))
- }
}
// fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67
@@ -319,12 +285,6 @@ impl McBufReadable for String {
}
}
-impl McBufReadable for ResourceLocation {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- buf.read_resource_location()
- }
-}
-
impl McBufReadable for u32 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
Readable::read_int(buf).map(|i| i as u32)
@@ -408,18 +368,6 @@ impl McBufReadable for f64 {
}
}
-impl McBufReadable for GameType {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- GameType::from_id(buf.read_byte()?)
- }
-}
-
-impl McBufReadable for Option<GameType> {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- GameType::from_optional_id(buf.read_byte()? as i8)
- }
-}
-
impl<T: McBufReadable> McBufReadable for Option<T> {
default fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let present = buf.read_boolean()?;
@@ -430,96 +378,3 @@ impl<T: McBufReadable> McBufReadable for Option<T> {
})
}
}
-
-impl McBufReadable for azalea_nbt::Tag {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- buf.read_nbt()
- }
-}
-
-impl McBufReadable for Difficulty {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- Ok(Difficulty::by_id(u8::read_into(buf)?))
- }
-}
-
-impl McBufReadable for Component {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let string = buf.read_utf()?;
- let json: serde_json::Value = serde_json::from_str(string.as_str())
- .map_err(|_| "Component isn't valid JSON".to_string())?;
- let component = Component::deserialize(json).map_err(|e| e.to_string())?;
- Ok(component)
- }
-}
-
-impl McBufReadable for Slot {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let present = buf.read_boolean()?;
- if !present {
- return Ok(Slot::Empty);
- }
- let id = buf.read_varint()?;
- let count = buf.read_byte()?;
- let nbt = buf.read_nbt()?;
- Ok(Slot::Present(SlotData { id, count, nbt }))
- }
-}
-
-impl McBufReadable for Uuid {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- buf.read_uuid()
- }
-}
-
-impl McBufReadable for BlockPos {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let val = u64::read_into(buf)?;
- let x = (val >> 38) as i32;
- let y = (val & 0xFFF) as i32;
- let z = ((val >> 12) & 0x3FFFFFF) as i32;
- Ok(BlockPos { x, y, z })
- }
-}
-
-impl McBufReadable for GlobalPos {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- Ok(GlobalPos {
- dimension: ResourceLocation::read_into(buf)?,
- pos: BlockPos::read_into(buf)?,
- })
- }
-}
-
-impl McBufReadable for Direction {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- match buf.read_varint()? {
- 0 => Ok(Self::Down),
- 1 => Ok(Self::Up),
- 2 => Ok(Self::North),
- 3 => Ok(Self::South),
- 4 => Ok(Self::West),
- 5 => Ok(Self::East),
- _ => Err("Invalid direction".to_string()),
- }
- }
-}
-
-impl McBufReadable for ChunkSectionPos {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let long = i64::read_into(buf)?;
- Ok(ChunkSectionPos {
- x: (long >> 42) as i32,
- y: (long << 44 >> 44) as i32,
- z: (long << 22 >> 42) as i32,
- })
- }
-}
-
-impl McBufReadable for SaltSignaturePair {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let salt = u64::read_into(buf)?;
- let signature = Vec::<u8>::read_into(buf)?;
- Ok(SaltSignaturePair { salt, signature })
- }
-}
diff --git a/azalea-core/src/serializable_uuid.rs b/azalea-buf/src/serializable_uuid.rs
index f8c03b60..10921aa6 100755..100644
--- a/azalea-core/src/serializable_uuid.rs
+++ b/azalea-buf/src/serializable_uuid.rs
@@ -1,3 +1,5 @@
+use crate::{McBufReadable, McBufWritable, Readable};
+use std::io::{Read, Write};
use uuid::Uuid;
pub trait SerializableUuid {
@@ -30,7 +32,30 @@ impl SerializableUuid for Uuid {
}
}
-#[cfg(tests)]
+impl McBufReadable for Uuid {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ Ok(Uuid::from_int_array([
+ Readable::read_int(buf)? as u32,
+ Readable::read_int(buf)? as u32,
+ Readable::read_int(buf)? as u32,
+ Readable::read_int(buf)? as u32,
+ ]))
+ }
+}
+
+impl McBufWritable for Uuid {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ let [a, b, c, d] = self.to_int_array();
+ a.write_into(buf)?;
+ b.write_into(buf)?;
+ c.write_into(buf)?;
+ d.write_into(buf)?;
+ Ok(())
+ }
+}
+
+// TODO: add a test for Uuid in McBuf
+#[cfg(test)]
mod tests {
use super::*;
diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-buf/src/write.rs
index 945477d0..38ddcf49 100644
--- a/azalea-protocol/src/mc_buf/write.rs
+++ b/azalea-buf/src/write.rs
@@ -1,13 +1,8 @@
use super::{UnsizedByteArray, MAX_STRING_LENGTH};
-use azalea_chat::component::Component;
-use azalea_core::{
- difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
- serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, GlobalPos, Slot,
-};
-use azalea_crypto::SaltSignaturePair;
use byteorder::{BigEndian, WriteBytesExt};
use std::{collections::HashMap, io::Write};
-use uuid::Uuid;
+
+// TODO: get rid of Writable and use McBufWritable everywhere
pub trait Writable: Write {
fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error>
@@ -101,14 +96,6 @@ pub trait Writable: Write {
self.write_byte(if b { 1 } else { 0 })
}
- fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>
- where
- Self: Sized,
- {
- nbt.write(self)
- .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
- }
-
fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> {
WriteBytesExt::write_i64::<BigEndian>(self, n)
}
@@ -120,25 +107,6 @@ pub trait Writable: Write {
fn write_double(&mut self, n: f64) -> Result<(), std::io::Error> {
WriteBytesExt::write_f64::<BigEndian>(self, n)
}
-
- fn write_resource_location(
- &mut self,
- location: &ResourceLocation,
- ) -> Result<(), std::io::Error> {
- self.write_utf(&location.to_string())
- }
-
- fn write_uuid(&mut self, uuid: &Uuid) -> Result<(), std::io::Error>
- where
- Self: Sized,
- {
- let [a, b, c, d] = uuid.to_int_array();
- a.write_into(self)?;
- b.write_into(self)?;
- c.write_into(self)?;
- d.write_into(self)?;
- Ok(())
- }
}
impl<W: Write + ?Sized> Writable for W {}
@@ -199,12 +167,6 @@ impl McBufWritable for String {
}
}
-impl McBufWritable for ResourceLocation {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_resource_location(self)
- }
-}
-
impl McBufWritable for u32 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i16::write_into(&(*self as i16), buf)
@@ -220,7 +182,6 @@ impl McBufVarWritable for u32 {
impl McBufVarWritable for i64 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut buffer = [0];
- let mut cnt = 0;
let mut value = *self;
while value != 0 {
buffer[0] = (value & 0b0111_1111) as u8;
@@ -228,7 +189,9 @@ impl McBufVarWritable for i64 {
if value != 0 {
buffer[0] |= 0b1000_0000;
}
- cnt += buf.write(&mut buffer)?;
+ // this only writes a single byte, so write_all isn't necessary
+ // the let _ = is so clippy doesn't complain
+ let _ = buf.write(&mut buffer)?;
}
Ok(())
}
@@ -310,18 +273,6 @@ impl McBufWritable for f64 {
}
}
-impl McBufWritable for GameType {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- u8::write_into(&self.to_id(), buf)
- }
-}
-
-impl McBufWritable for Option<GameType> {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_byte(GameType::to_optional_id(self) as u8)
- }
-}
-
impl<T: McBufWritable> McBufWritable for Option<T> {
default fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
if let Some(s) = self {
@@ -333,98 +284,3 @@ impl<T: McBufWritable> McBufWritable for Option<T> {
Ok(())
}
}
-
-impl McBufWritable for azalea_nbt::Tag {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_nbt(self)
- }
-}
-
-impl McBufWritable for Difficulty {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- u8::write_into(&self.id(), buf)
- }
-}
-
-impl McBufWritable for Component {
- // async fn read_into(buf: &mut impl Read) -> Result<Self, String>
- // where
- // R: AsyncRead + std::marker::Unpin + std::marker::Send,
- // {
- // let string = buf.read_utf().await?;
- // let json: serde_json::Value = serde_json::from_str(string.as_str())
- // .map_err(|e| "Component isn't valid JSON".to_string())?;
- // let component = Component::deserialize(json).map_err(|e| e.to_string())?;
- // Ok(component)
- // }
- fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
- // component doesn't have serialize implemented yet
- todo!()
- }
-}
-
-impl McBufWritable for Slot {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- match self {
- Slot::Empty => buf.write_byte(0)?,
- Slot::Present(i) => {
- buf.write_varint(i.id)?;
- buf.write_byte(i.count)?;
- buf.write_nbt(&i.nbt)?;
- }
- }
-
- Ok(())
- }
-}
-
-impl McBufWritable for Uuid {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_uuid(self)?;
-
- Ok(())
- }
-}
-
-impl McBufWritable for BlockPos {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_long(
- (((self.x & 0x3FFFFFF) as i64) << 38)
- | (((self.z & 0x3FFFFFF) as i64) << 12)
- | ((self.y & 0xFFF) as i64),
- )
- }
-}
-
-impl McBufWritable for GlobalPos {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- ResourceLocation::write_into(&self.dimension, buf)?;
- BlockPos::write_into(&self.pos, buf)?;
-
- Ok(())
- }
-}
-
-impl McBufWritable for Direction {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- buf.write_varint(*self as i32)
- }
-}
-
-impl McBufWritable for ChunkSectionPos {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- let long = (((self.x & 0x3FFFFF) as i64) << 42)
- | (self.y & 0xFFFFF) as i64
- | (((self.z & 0x3FFFFF) as i64) << 20);
- long.write_into(buf)?;
- Ok(())
- }
-}
-
-impl McBufWritable for SaltSignaturePair {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- self.salt.write_into(buf)?;
- self.signature.write_into(buf)?;
- Ok(())
- }
-}
diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml
index 192d3405..5b85f6c2 100755
--- a/azalea-chat/Cargo.toml
+++ b/azalea-chat/Cargo.toml
@@ -6,6 +6,7 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+azalea-buf = {path = "../azalea-buf"}
azalea-language = {path = "../azalea-language"}
lazy_static = "1.4.0"
serde = "^1.0.130"
diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs
index 36709cc0..d307bcbc 100755
--- a/azalea-chat/src/component.rs
+++ b/azalea-chat/src/component.rs
@@ -1,3 +1,6 @@
+use std::io::{Read, Write};
+
+use azalea_buf::{McBufReadable, McBufWritable};
use serde::{de, Deserialize, Deserializer};
use crate::{
@@ -264,3 +267,30 @@ impl<'de> Deserialize<'de> for Component {
Ok(component)
}
}
+
+impl McBufReadable for Component {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let string = String::read_into(buf)?;
+ let json: serde_json::Value = serde_json::from_str(string.as_str())
+ .map_err(|_| "Component isn't valid JSON".to_string())?;
+ let component = Component::deserialize(json).map_err(|e| e.to_string())?;
+ Ok(component)
+ }
+}
+
+impl McBufWritable for Component {
+ // async fn read_into(buf: &mut impl Read) -> Result<Self, String>
+ // where
+ // R: AsyncRead + std::marker::Unpin + std::marker::Send,
+ // {
+ // let string = buf.read_utf().await?;
+ // let json: serde_json::Value = serde_json::from_str(string.as_str())
+ // .map_err(|e| "Component isn't valid JSON".to_string())?;
+ // let component = Component::deserialize(json).map_err(|e| e.to_string())?;
+ // Ok(component)
+ // }
+ fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
+ // component doesn't have serialize implemented yet
+ todo!()
+ }
+}
diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml
index eaf7b0dc..b39d6a49 100755
--- a/azalea-client/Cargo.toml
+++ b/azalea-client/Cargo.toml
@@ -9,6 +9,9 @@ version = "0.1.0"
azalea-auth = {path = "../azalea-auth"}
azalea-core = {path = "../azalea-core"}
azalea-crypto = {path = "../azalea-crypto"}
+azalea-entity = {path = "../azalea-entity"}
azalea-protocol = {path = "../azalea-protocol"}
azalea-world = {path = "../azalea-world"}
+owning_ref = "0.4.1"
tokio = {version = "1.19.2", features = ["sync"]}
+uuid = "1.1.2"
diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs
new file mode 100644
index 00000000..56f4918a
--- /dev/null
+++ b/azalea-client/src/account.rs
@@ -0,0 +1,20 @@
+//! Connect to Minecraft servers.
+
+use crate::Client;
+use azalea_protocol::ServerAddress;
+
+/// Something that can join Minecraft servers.
+pub struct Account {
+ pub username: String,
+}
+impl Account {
+ pub fn offline(username: &str) -> Self {
+ Self {
+ username: username.to_string(),
+ }
+ }
+
+ pub async fn join(&self, address: &ServerAddress) -> Result<Client, String> {
+ Client::join(self, address).await
+ }
+}
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
new file mode 100644
index 00000000..dfefe4ad
--- /dev/null
+++ b/azalea-client/src/client.rs
@@ -0,0 +1,673 @@
+use crate::{Account, Player};
+use azalea_auth::game_profile::GameProfile;
+use azalea_core::{ChunkPos, EntityPos, PositionDelta, PositionDeltaTrait, ResourceLocation};
+use azalea_entity::Entity;
+use azalea_protocol::{
+ connect::{GameConnection, HandshakeConnection},
+ packets::{
+ game::{
+ clientbound_player_chat_packet::ClientboundPlayerChatPacket,
+ clientbound_system_chat_packet::ClientboundSystemChatPacket,
+ serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
+ serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
+ serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
+ serverbound_move_player_packet_pos_rot::ServerboundMovePlayerPacketPosRot, GamePacket,
+ },
+ handshake::client_intention_packet::ClientIntentionPacket,
+ login::{
+ serverbound_hello_packet::ServerboundHelloPacket,
+ serverbound_key_packet::{NonceOrSaltSignature, ServerboundKeyPacket},
+ LoginPacket,
+ },
+ ConnectionProtocol, PROTOCOL_VERSION,
+ },
+ resolver, ServerAddress,
+};
+use azalea_world::World;
+use owning_ref::OwningRef;
+use std::{
+ fmt::Debug,
+ sync::{Arc, Mutex},
+};
+use tokio::{
+ sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
+ time::{self, MissedTickBehavior},
+};
+
+#[derive(Default)]
+pub struct ClientState {
+ pub player: Player,
+ pub world: Option<World>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Event {
+ Login,
+ Chat(ChatPacket),
+ /// A game tick, happens 20 times per second.
+ GameTick,
+}
+
+#[derive(Debug, Clone)]
+pub enum ChatPacket {
+ System(ClientboundSystemChatPacket),
+ Player(Box<ClientboundPlayerChatPacket>),
+}
+
+// impl ChatPacket {
+// pub fn message(&self) -> &str {
+// match self {
+// ChatPacket::System(p) => &p.content,
+// ChatPacket::Player(p) => &p.message,
+// }
+// }
+// }
+
+/// A player that you can control that is currently in a Minecraft server.
+pub struct Client {
+ event_receiver: UnboundedReceiver<Event>,
+ game_profile: GameProfile,
+ pub conn: Arc<tokio::sync::Mutex<GameConnection>>,
+ pub state: Arc<Mutex<ClientState>>,
+ // game_loop
+}
+
+/// Whether we should ignore errors when decoding packets.
+const IGNORE_ERRORS: bool = !cfg!(debug_assertions);
+
+#[derive(Debug)]
+struct HandleError(String);
+
+impl Client {
+ /// Connect to a Minecraft server with an account.
+ pub async fn join(account: &Account, address: &ServerAddress) -> Result<Self, String> {
+ let resolved_address = resolver::resolve_address(address).await?;
+
+ let mut conn = HandshakeConnection::new(&resolved_address).await?;
+
+ // handshake
+ conn.write(
+ ClientIntentionPacket {
+ protocol_version: PROTOCOL_VERSION,
+ hostname: address.host.clone(),
+ port: address.port,
+ intention: ConnectionProtocol::Login,
+ }
+ .get(),
+ )
+ .await;
+ let mut conn = conn.login();
+
+ // login
+ conn.write(
+ ServerboundHelloPacket {
+ username: account.username.clone(),
+ public_key: None,
+ }
+ .get(),
+ )
+ .await;
+
+ let (conn, game_profile) = loop {
+ let packet_result = conn.read().await;
+ match packet_result {
+ Ok(packet) => match packet {
+ LoginPacket::ClientboundHelloPacket(p) => {
+ println!("Got encryption request");
+ let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap();
+
+ // TODO: authenticate with the server here (authenticateServer)
+
+ conn.write(
+ ServerboundKeyPacket {
+ nonce_or_salt_signature: NonceOrSaltSignature::Nonce(
+ e.encrypted_nonce,
+ ),
+ key_bytes: e.encrypted_public_key,
+ }
+ .get(),
+ )
+ .await;
+ conn.set_encryption_key(e.secret_key);
+ }
+ LoginPacket::ClientboundLoginCompressionPacket(p) => {
+ println!("Got compression request {:?}", p.compression_threshold);
+ conn.set_compression_threshold(p.compression_threshold);
+ }
+ LoginPacket::ClientboundGameProfilePacket(p) => {
+ println!("Got profile {:?}", p.game_profile);
+ break (conn.game(), p.game_profile);
+ }
+ LoginPacket::ClientboundLoginDisconnectPacket(p) => {
+ println!("Got disconnect {:?}", p);
+ }
+ LoginPacket::ClientboundCustomQueryPacket(p) => {
+ println!("Got custom query {:?}", p);
+ }
+ _ => panic!("Unexpected packet {:?}", packet),
+ },
+ Err(e) => {
+ panic!("Error: {:?}", e);
+ }
+ }
+ };
+
+ let conn = Arc::new(tokio::sync::Mutex::new(conn));
+
+ let (tx, rx) = mpsc::unbounded_channel();
+
+ // we got the GameConnection, so the server is now connected :)
+ let client = Client {
+ game_profile: game_profile.clone(),
+ event_receiver: rx,
+ conn: conn.clone(),
+ state: Arc::new(Mutex::new(ClientState::default())),
+ };
+
+ // just start up the game loop and we're ready!
+
+ let game_loop_state = client.state.clone();
+
+ // if you get an error right here that means you're doing something with locks wrong
+ // read the error to see where the issue is
+ // you might be able to just drop the lock or put it in its own scope to fix
+ tokio::spawn(Self::protocol_loop(
+ conn.clone(),
+ tx.clone(),
+ game_loop_state.clone(),
+ game_profile.clone(),
+ ));
+ tokio::spawn(Self::game_tick_loop(conn, tx, game_loop_state));
+
+ Ok(client)
+ }
+
+ async fn protocol_loop(
+ conn: Arc<tokio::sync::Mutex<GameConnection>>,
+ tx: UnboundedSender<Event>,
+ state: Arc<Mutex<ClientState>>,
+ game_profile: GameProfile,
+ ) {
+ loop {
+ let r = conn.lock().await.read().await;
+ match r {
+ Ok(packet) => {
+ match Self::handle(&packet, &tx, &state, &conn, &game_profile).await {
+ Ok(_) => {}
+ Err(e) => {
+ println!("Error handling packet: {:?}", e);
+ if IGNORE_ERRORS {
+ continue;
+ } else {
+ panic!("Error handling packet: {:?}", e);
+ }
+ }
+ }
+ }
+ Err(e) => {
+ if IGNORE_ERRORS {
+ println!("Error: {:?}", e);
+ if e == "length wider than 21-bit" {
+ panic!();
+ }
+ } else {
+ panic!("Error: {:?}", e);
+ }
+ }
+ };
+ }
+ }
+
+ async fn handle(
+ packet: &GamePacket,
+ tx: &UnboundedSender<Event>,
+ state: &Arc<Mutex<ClientState>>,
+ conn: &Arc<tokio::sync::Mutex<GameConnection>>,
+ game_profile: &GameProfile,
+ ) -> Result<(), HandleError> {
+ match packet {
+ GamePacket::ClientboundLoginPacket(p) => {
+ println!("Got login packet {:?}", p);
+
+ {
+ let mut state_lock = state.lock()?;
+
+ // // write p into login.txt
+ // std::io::Write::write_all(
+ // &mut std::fs::File::create("login.txt").unwrap(),
+ // format!("{:#?}", p).as_bytes(),
+ // )
+ // .unwrap();
+
+ // TODO: have registry_holder be a struct because this sucks rn
+ // best way would be to add serde support to azalea-nbt
+
+ let registry_holder = p
+ .registry_holder
+ .as_compound()
+ .expect("Registry holder is not a compound")
+ .get("")
+ .expect("No \"\" tag")
+ .as_compound()
+ .expect("\"\" tag is not a compound");
+ let dimension_types = registry_holder
+ .get("minecraft:dimension_type")
+ .expect("No dimension_type tag")
+ .as_compound()
+ .expect("dimension_type is not a compound")
+ .get("value")
+ .expect("No dimension_type value")
+ .as_list()
+ .expect("dimension_type value is not a list");
+ let dimension_type = dimension_types
+ .iter()
+ .find(|t| {
+ t.as_compound()
+ .expect("dimension_type value is not a compound")
+ .get("name")
+ .expect("No name tag")
+ .as_string()
+ .expect("name is not a string")
+ == p.dimension_type.to_string()
+ })
+ .unwrap_or_else(|| {
+ panic!("No dimension_type with name {}", p.dimension_type)
+ })
+ .as_compound()
+ .unwrap()
+ .get("element")
+ .expect("No element tag")
+ .as_compound()
+ .expect("element is not a compound");
+ let height = (*dimension_type
+ .get("height")
+ .expect("No height tag")
+ .as_int()
+ .expect("height tag is not an int"))
+ .try_into()
+ .expect("height is not a u32");
+ let min_y = *dimension_type
+ .get("min_y")
+ .expect("No min_y tag")
+ .as_int()
+ .expect("min_y tag is not an int");
+
+ // the 16 here is our render distance
+ // i'll make this an actual setting later
+ state_lock.world = Some(World::new(16, height, min_y));
+
+ let entity = Entity::new(p.player_id, game_profile.uuid, EntityPos::default());
+ state_lock
+ .world
+ .as_mut()
+ .expect("World doesn't exist! We should've gotten a login packet by now.")
+ .add_entity(entity);
+
+ state_lock.player.set_entity_id(p.player_id);
+ }
+
+ conn.lock()
+ .await
+ .write(
+ ServerboundCustomPayloadPacket {
+ identifier: ResourceLocation::new("brand").unwrap(),
+ // they don't have to know :)
+ data: "vanilla".into(),
+ }
+ .get(),
+ )
+ .await;
+
+ tx.send(Event::Login).unwrap();
+ }
+ GamePacket::ClientboundUpdateViewDistancePacket(p) => {
+ println!("Got view distance packet {:?}", p);
+ }
+ GamePacket::ClientboundCustomPayloadPacket(p) => {
+ println!("Got custom payload packet {:?}", p);
+ }
+ GamePacket::ClientboundChangeDifficultyPacket(p) => {
+ println!("Got difficulty packet {:?}", p);
+ }
+ GamePacket::ClientboundDeclareCommandsPacket(_p) => {
+ println!("Got declare commands packet");
+ }
+ GamePacket::ClientboundPlayerAbilitiesPacket(p) => {
+ println!("Got player abilities packet {:?}", p);
+ }
+ GamePacket::ClientboundSetCarriedItemPacket(p) => {
+ println!("Got set carried item packet {:?}", p);
+ }
+ GamePacket::ClientboundUpdateTagsPacket(_p) => {
+ println!("Got update tags packet");
+ }
+ GamePacket::ClientboundDisconnectPacket(p) => {
+ println!("Got disconnect packet {:?}", p);
+ }
+ GamePacket::ClientboundUpdateRecipesPacket(_p) => {
+ println!("Got update recipes packet");
+ }
+ GamePacket::ClientboundEntityEventPacket(_p) => {
+ // println!("Got entity event packet {:?}", p);
+ }
+ GamePacket::ClientboundRecipePacket(_p) => {
+ println!("Got recipe packet");
+ }
+ GamePacket::ClientboundPlayerPositionPacket(p) => {
+ // TODO: reply with teleport confirm
+ println!("Got player position packet {:?}", p);
+
+ let (new_pos, y_rot, x_rot) = {
+ let mut state_lock = state.lock()?;
+ let player_entity_id = state_lock.player.entity_id;
+ let world = state_lock.world.as_mut().unwrap();
+ let player_entity = world
+ .mut_entity_by_id(player_entity_id)
+ .expect("Player entity doesn't exist");
+ let delta_movement = &player_entity.delta;
+
+ let is_x_relative = p.relative_arguments.x;
+ let is_y_relative = p.relative_arguments.y;
+ let is_z_relative = p.relative_arguments.z;
+
+ let (delta_x, new_pos_x) = if is_x_relative {
+ player_entity.old_pos.x += p.x;
+ (delta_movement.x(), player_entity.pos().x + p.x)
+ } else {
+ player_entity.old_pos.x = p.x;
+ (0.0, p.x)
+ };
+ let (delta_y, new_pos_y) = if is_y_relative {
+ player_entity.old_pos.y += p.y;
+ (delta_movement.y(), player_entity.pos().y + p.y)
+ } else {
+ player_entity.old_pos.y = p.y;
+ (0.0, p.y)
+ };
+ let (delta_z, new_pos_z) = if is_z_relative {
+ player_entity.old_pos.z += p.z;
+ (delta_movement.z(), player_entity.pos().z + p.z)
+ } else {
+ player_entity.old_pos.z = p.z;
+ (0.0, p.z)
+ };
+
+ let mut y_rot = p.y_rot;
+ let mut x_rot = p.x_rot;
+ if p.relative_arguments.x_rot {
+ y_rot += player_entity.x_rot;
+ }
+ if p.relative_arguments.y_rot {
+ x_rot += player_entity.y_rot;
+ }
+
+ player_entity.delta = PositionDelta {
+ xa: delta_x,
+ ya: delta_y,
+ za: delta_z,
+ };
+ player_entity.set_rotation(y_rot, x_rot);
+ // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
+ // so investigate that ig
+ let new_pos = EntityPos {
+ x: new_pos_x,
+ y: new_pos_y,
+ z: new_pos_z,
+ };
+ world
+ .move_entity(player_entity_id, new_pos)
+ .expect("The player entity should always exist");
+
+ (new_pos, y_rot, x_rot)
+ };
+
+ let mut conn_lock = conn.lock().await;
+ conn_lock
+ .write(ServerboundAcceptTeleportationPacket { id: p.id }.get())
+ .await;
+ conn_lock
+ .write(
+ ServerboundMovePlayerPacketPosRot {
+ x: new_pos.x,
+ y: new_pos.y,
+ z: new_pos.z,
+ y_rot,
+ x_rot,
+ // this is always false
+ on_ground: false,
+ }
+ .get(),
+ )
+ .await;
+ }
+ GamePacket::ClientboundPlayerInfoPacket(p) => {
+ println!("Got player info packet {:?}", p);
+ }
+ GamePacket::ClientboundSetChunkCacheCenterPacket(p) => {
+ println!("Got chunk cache center packet {:?}", p);
+ state
+ .lock()?
+ .world
+ .as_mut()
+ .unwrap()
+ .update_view_center(&ChunkPos::new(p.x, p.z));
+ }
+ GamePacket::ClientboundLevelChunkWithLightPacket(p) => {
+ println!("Got chunk with light packet {} {}", p.x, p.z);
+ let pos = ChunkPos::new(p.x, p.z);
+ // let chunk = Chunk::read_with_world_height(&mut p.chunk_data);
+ // println("chunk {:?}")
+ state
+ .lock()?
+ .world
+ .as_mut()
+ .expect("World doesn't exist! We should've gotten a login packet by now.")
+ .replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
+ .unwrap();
+ }
+ GamePacket::ClientboundLightUpdatePacket(p) => {
+ println!("Got light update packet {:?}", p);
+ }
+ GamePacket::ClientboundAddEntityPacket(p) => {
+ println!("Got add entity packet {:?}", p);
+ let entity = Entity::from(p);
+ state
+ .lock()?
+ .world
+ .as_mut()
+ .expect("World doesn't exist! We should've gotten a login packet by now.")
+ .add_entity(entity);
+ }
+ GamePacket::ClientboundSetEntityDataPacket(_p) => {
+ // println!("Got set entity data packet {:?}", p);
+ }
+ GamePacket::ClientboundUpdateAttributesPacket(_p) => {
+ // println!("Got update attributes packet {:?}", p);
+ }
+ GamePacket::ClientboundEntityVelocityPacket(_p) => {
+ // println!("Got entity velocity packet {:?}", p);
+ }
+ GamePacket::ClientboundSetEntityLinkPacket(p) => {
+ println!("Got set entity link packet {:?}", p);
+ }
+ GamePacket::ClientboundAddPlayerPacket(p) => {
+ println!("Got add player packet {:?}", p);
+ let entity = Entity::from(p);
+ state
+ .lock()?
+ .world
+ .as_mut()
+ .expect("World doesn't exist! We should've gotten a login packet by now.")
+ .add_entity(entity);
+ }
+ GamePacket::ClientboundInitializeBorderPacket(p) => {
+ println!("Got initialize border packet {:?}", p);
+ }
+ GamePacket::ClientboundSetTimePacket(p) => {
+ println!("Got set time packet {:?}", p);
+ }
+ GamePacket::ClientboundSetDefaultSpawnPositionPacket(p) => {
+ println!("Got set default spawn position packet {:?}", p);
+ }
+ GamePacket::ClientboundContainerSetContentPacket(p) => {
+ println!("Got container set content packet {:?}", p);
+ }
+ GamePacket::ClientboundSetHealthPacket(p) => {
+ println!("Got set health packet {:?}", p);
+ }
+ GamePacket::ClientboundSetExperiencePacket(p) => {
+ println!("Got set experience packet {:?}", p);
+ }
+ GamePacket::ClientboundTeleportEntityPacket(p) => {
+ let mut state_lock = state.lock()?;
+ let world = state_lock.world.as_mut().unwrap();
+
+ world.move_entity(
+ p.id,
+ EntityPos {
+ x: p.x,
+ y: p.y,
+ z: p.z,
+ },
+ )?;
+ }
+ GamePacket::ClientboundUpdateAdvancementsPacket(p) => {
+ println!("Got update advancements packet {:?}", p);
+ }
+ GamePacket::ClientboundRotateHeadPacket(_p) => {
+ // println!("Got rotate head packet {:?}", p);
+ }
+ GamePacket::ClientboundMoveEntityPosPacket(p) => {
+ let mut state_lock = state.lock()?;
+ let world = state_lock.world.as_mut().unwrap();
+
+ world.move_entity_with_delta(p.entity_id, &p.delta)?;
+ }
+ GamePacket::ClientboundMoveEntityPosRotPacket(p) => {
+ let mut state_lock = state.lock()?;
+ let world = state_lock.world.as_mut().unwrap();
+
+ world.move_entity_with_delta(p.entity_id, &p.delta)?;
+ }
+ GamePacket::ClientboundMoveEntityRotPacket(p) => {
+ println!("Got move entity rot packet {:?}", p);
+ }
+ GamePacket::ClientboundKeepAlivePacket(p) => {
+ println!("Got keep alive packet {:?}", p);
+ conn.lock()
+ .await
+ .write(ServerboundKeepAlivePacket { id: p.id }.get())
+ .await;
+ }
+ GamePacket::ClientboundRemoveEntitiesPacket(p) => {
+ println!("Got remove entities packet {:?}", p);
+ }
+ GamePacket::ClientboundPlayerChatPacket(p) => {
+ println!("Got player chat packet {:?}", p);
+ tx.send(Event::Chat(ChatPacket::Player(Box::new(p.clone()))))
+ .unwrap();
+ }
+ GamePacket::ClientboundSystemChatPacket(p) => {
+ println!("Got system chat packet {:?}", p);
+ tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap();
+ }
+ GamePacket::ClientboundSoundPacket(p) => {
+ println!("Got sound packet {:?}", p);
+ }
+ GamePacket::ClientboundLevelEventPacket(p) => {
+ println!("Got level event packet {:?}", p);
+ }
+ GamePacket::ClientboundBlockUpdatePacket(p) => {
+ println!("Got block update packet {:?}", p);
+ // TODO: update world
+ }
+ GamePacket::ClientboundAnimatePacket(p) => {
+ println!("Got animate packet {:?}", p);
+ }
+ GamePacket::ClientboundSectionBlocksUpdatePacket(p) => {
+ println!("Got section blocks update packet {:?}", p);
+ // TODO: update world
+ }
+ GamePacket::ClientboundGameEventPacket(p) => {
+ println!("Got game event packet {:?}", p);
+ }
+ GamePacket::ClientboundLevelParticlesPacket(p) => {
+ println!("Got level particles packet {:?}", p);
+ }
+ GamePacket::ClientboundServerDataPacket(p) => {
+ println!("Got server data packet {:?}", p);
+ }
+ GamePacket::ClientboundSetEquipmentPacket(p) => {
+ println!("Got set equipment packet {:?}", p);
+ }
+ GamePacket::ClientboundUpdateMobEffectPacket(p) => {
+ println!("Got update mob effect packet {:?}", p);
+ }
+ _ => panic!("Unexpected packet {:?}", packet),
+ }
+
+ Ok(())
+ }
+
+ pub async fn next(&mut self) -> Option<Event> {
+ self.event_receiver.recv().await
+ }
+
+ /// Runs game_tick every 50 milliseconds.
+ async fn game_tick_loop(
+ conn: Arc<tokio::sync::Mutex<GameConnection>>,
+ tx: UnboundedSender<Event>,
+ state: Arc<Mutex<ClientState>>,
+ ) {
+ let mut game_tick_interval = time::interval(time::Duration::from_millis(50));
+ // TODO: Minecraft bursts up to 10 ticks and then skips, we should too
+ game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
+ loop {
+ game_tick_interval.tick().await;
+ Self::game_tick(&conn, &tx, &state).await;
+ }
+ }
+
+ /// Runs every 50 milliseconds.
+ async fn game_tick(
+ conn: &Arc<tokio::sync::Mutex<GameConnection>>,
+ tx: &UnboundedSender<Event>,
+ state: &Arc<Mutex<ClientState>>,
+ ) {
+ if state.lock().unwrap().world.is_none() {
+ return;
+ }
+ tx.send(Event::GameTick).unwrap();
+ }
+
+ /// Gets the `World` the client is in.
+ ///
+ /// This is basically a shortcut for `client.state.lock().unwrap().world.as_ref().unwrap()`.
+ /// If the client hasn't received a login packet yet, this will panic.
+ pub fn world(&self) -> OwningRef<std::sync::MutexGuard<ClientState>, World> {
+ let state_lock: std::sync::MutexGuard<ClientState> = self.state.lock().unwrap();
+ let state_lock_ref = OwningRef::new(state_lock);
+ state_lock_ref.map(|state| state.world.as_ref().expect("World doesn't exist!"))
+ }
+
+ /// Gets the `Player` struct for our player.
+ ///
+ /// This is basically a shortcut for `client.state.lock().unwrap().player`.
+ pub fn player(&self) -> OwningRef<std::sync::MutexGuard<ClientState>, Player> {
+ let state_lock: std::sync::MutexGuard<ClientState> = self.state.lock().unwrap();
+ let state_lock_ref = OwningRef::new(state_lock);
+ state_lock_ref.map(|state| &state.player)
+ }
+}
+
+impl<T> From<std::sync::PoisonError<T>> for HandleError {
+ fn from(e: std::sync::PoisonError<T>) -> Self {
+ HandleError(e.to_string())
+ }
+}
+
+impl From<String> for HandleError {
+ fn from(e: String) -> Self {
+ HandleError(e)
+ }
+}
diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs
deleted file mode 100755
index 1551ca69..00000000
--- a/azalea-client/src/connect.rs
+++ /dev/null
@@ -1,477 +0,0 @@
-use crate::Player;
-use azalea_core::{resource_location::ResourceLocation, ChunkPos, EntityPos};
-use azalea_protocol::{
- connect::{GameConnection, HandshakeConnection},
- packets::{
- game::{
- clientbound_player_chat_packet::ClientboundPlayerChatPacket,
- clientbound_system_chat_packet::ClientboundSystemChatPacket,
- serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
- serverbound_keep_alive_packet::ServerboundKeepAlivePacket, GamePacket,
- },
- handshake::client_intention_packet::ClientIntentionPacket,
- login::{
- serverbound_hello_packet::ServerboundHelloPacket,
- serverbound_key_packet::{NonceOrSaltSignature, ServerboundKeyPacket},
- LoginPacket,
- },
- ConnectionProtocol, PROTOCOL_VERSION,
- },
- resolver, ServerAddress,
-};
-use azalea_world::{ChunkStorage, World};
-use std::{fmt::Debug, sync::Arc};
-use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
-use tokio::sync::Mutex;
-
-///! Connect to Minecraft servers.
-
-/// Something that can join Minecraft servers.
-pub struct Account {
- username: String,
-}
-
-#[derive(Default)]
-pub struct ClientState {
- pub player: Player,
- pub world: Option<World>,
-}
-
-/// A player that you can control that is currently in a Minecraft server.
-pub struct Client {
- event_receiver: UnboundedReceiver<Event>,
- pub conn: Arc<Mutex<GameConnection>>,
- pub state: Arc<Mutex<ClientState>>,
- // game_loop
-}
-
-#[derive(Debug, Clone)]
-pub enum ChatPacket {
- System(ClientboundSystemChatPacket),
- Player(ClientboundPlayerChatPacket),
-}
-
-// impl ChatPacket {
-// pub fn message(&self) -> &str {
-// match self {
-// ChatPacket::System(p) => &p.content,
-// ChatPacket::Player(p) => &p.message,
-// }
-// }
-// }
-
-#[derive(Debug, Clone)]
-pub enum Event {
- Login,
- Chat(ChatPacket),
-}
-
-/// Whether we should ignore errors when decoding packets.
-const IGNORE_ERRORS: bool = false;
-
-impl Client {
- async fn join(account: &Account, address: &ServerAddress) -> Result<Self, String> {
- let resolved_address = resolver::resolve_address(address).await?;
-
- let mut conn = HandshakeConnection::new(&resolved_address).await?;
-
- // handshake
- conn.write(
- ClientIntentionPacket {
- protocol_version: PROTOCOL_VERSION,
- hostname: address.host.clone(),
- port: address.port,
- intention: ConnectionProtocol::Login,
- }
- .get(),
- )
- .await;
- let mut conn = conn.login();
-
- // login
- conn.write(
- ServerboundHelloPacket {
- username: account.username.clone(),
- public_key: None,
- }
- .get(),
- )
- .await;
-
- let conn = loop {
- let packet_result = conn.read().await;
- match packet_result {
- Ok(packet) => match packet {
- LoginPacket::ClientboundHelloPacket(p) => {
- println!("Got encryption request");
- let e = azalea_crypto::encrypt(&p.public_key, &p.nonce).unwrap();
-
- // TODO: authenticate with the server here (authenticateServer)
-
- conn.write(
- ServerboundKeyPacket {
- nonce_or_salt_signature: NonceOrSaltSignature::Nonce(
- e.encrypted_nonce,
- ),
- key_bytes: e.encrypted_public_key,
- }
- .get(),
- )
- .await;
- conn.set_encryption_key(e.secret_key);
- }
- LoginPacket::ClientboundLoginCompressionPacket(p) => {
- println!("Got compression request {:?}", p.compression_threshold);
- conn.set_compression_threshold(p.compression_threshold);
- }
- LoginPacket::ClientboundGameProfilePacket(p) => {
- println!("Got profile {:?}", p.game_profile);
- break conn.game();
- }
- LoginPacket::ClientboundLoginDisconnectPacket(p) => {
- println!("Got disconnect {:?}", p);
- }
- LoginPacket::ClientboundCustomQueryPacket(p) => {
- println!("Got custom query {:?}", p);
- }
- _ => panic!("Unexpected packet {:?}", packet),
- },
- Err(e) => {
- panic!("Error: {:?}", e);
- }
- }
- };
-
- let conn = Arc::new(Mutex::new(conn));
-
- let (tx, rx) = mpsc::unbounded_channel();
-
- // we got the GameConnection, so the server is now connected :)
- let client = Client {
- event_receiver: rx,
- conn: conn.clone(),
- state: Arc::new(Mutex::new(ClientState::default())),
- };
- // let client = Arc::new(Mutex::new(client));
- // let weak_client = Arc::<_>::downgrade(&client);
-
- // just start up the game loop and we're ready!
- // tokio::spawn(Self::game_loop(conn, tx, handler, state))
-
- let game_loop_state = client.state.clone();
-
- tokio::spawn(Self::game_loop(conn, tx, game_loop_state));
-
- Ok(client)
- }
-
- async fn game_loop(
- conn: Arc<Mutex<GameConnection>>,
- tx: UnboundedSender<Event>,
- state: Arc<Mutex<ClientState>>,
- ) {
- loop {
- let r = conn.lock().await.read().await;
- match r {
- Ok(packet) => Self::handle(&packet, &tx, &state, &conn).await,
- Err(e) => {
- if IGNORE_ERRORS {
- println!("Error: {:?}", e);
- if e == "length wider than 21-bit" {
- panic!();
- }
- } else {
- panic!("Error: {:?}", e);
- }
- }
- };
- }
- }
-
- async fn handle(
- packet: &GamePacket,
- tx: &UnboundedSender<Event>,
- state: &Arc<Mutex<ClientState>>,
- conn: &Arc<Mutex<GameConnection>>,
- ) {
- match packet {
- GamePacket::ClientboundLoginPacket(p) => {
- println!("Got login packet {:?}", p);
-
- let mut state = state.lock().await;
-
- // // write p into login.txt
- // std::io::Write::write_all(
- // &mut std::fs::File::create("login.txt").unwrap(),
- // format!("{:#?}", p).as_bytes(),
- // )
- // .unwrap();
-
- state.player.entity.id = p.player_id;
-
- // TODO: have registry_holder be a struct because this sucks rn
- // best way would be to add serde support to azalea-nbt
-
- let registry_holder = p
- .registry_holder
- .as_compound()
- .expect("Registry holder is not a compound")
- .get("")
- .expect("No \"\" tag")
- .as_compound()
- .expect("\"\" tag is not a compound");
- let dimension_types = registry_holder
- .get("minecraft:dimension_type")
- .expect("No dimension_type tag")
- .as_compound()
- .expect("dimension_type is not a compound")
- .get("value")
- .expect("No dimension_type value")
- .as_list()
- .expect("dimension_type value is not a list");
- let dimension_type = dimension_types
- .iter()
- .find(|t| {
- t.as_compound()
- .expect("dimension_type value is not a compound")
- .get("name")
- .expect("No name tag")
- .as_string()
- .expect("name is not a string")
- == p.dimension_type.to_string()
- })
- .expect(&format!("No dimension_type with name {}", p.dimension_type))
- .as_compound()
- .unwrap()
- .get("element")
- .expect("No element tag")
- .as_compound()
- .expect("element is not a compound");
- let height = (*dimension_type
- .get("height")
- .expect("No height tag")
- .as_int()
- .expect("height tag is not an int"))
- .try_into()
- .expect("height is not a u32");
- let min_y = (*dimension_type
- .get("min_y")
- .expect("No min_y tag")
- .as_int()
- .expect("min_y tag is not an int"))
- .try_into()
- .expect("min_y is not an i32");
-
- state.world = Some(World {
- height,
- min_y,
- storage: ChunkStorage::new(16),
- });
-
- conn.lock()
- .await
- .write(
- ServerboundCustomPayloadPacket {
- identifier: ResourceLocation::new("brand").unwrap(),
- // they don't have to know :)
- data: "vanilla".into(),
- }
- .get(),
- )
- .await;
-
- tx.send(Event::Login).unwrap();
- }
- GamePacket::ClientboundUpdateViewDistancePacket(p) => {
- println!("Got view distance packet {:?}", p);
- }
- GamePacket::ClientboundCustomPayloadPacket(p) => {
- println!("Got custom payload packet {:?}", p);
- }
- GamePacket::ClientboundChangeDifficultyPacket(p) => {
- println!("Got difficulty packet {:?}", p);
- }
- GamePacket::ClientboundDeclareCommandsPacket(_p) => {
- println!("Got declare commands packet");
- }
- GamePacket::ClientboundPlayerAbilitiesPacket(p) => {
- println!("Got player abilities packet {:?}", p);
- }
- GamePacket::ClientboundSetCarriedItemPacket(p) => {
- println!("Got set carried item packet {:?}", p);
- }
- GamePacket::ClientboundUpdateTagsPacket(_p) => {
- println!("Got update tags packet");
- }
- GamePacket::ClientboundDisconnectPacket(p) => {
- println!("Got disconnect packet {:?}", p);
- }
- GamePacket::ClientboundUpdateRecipesPacket(_p) => {
- println!("Got update recipes packet");
- }
- GamePacket::ClientboundEntityEventPacket(p) => {
- // println!("Got entity event packet {:?}", p);
- }
- GamePacket::ClientboundRecipePacket(_p) => {
- println!("Got recipe packet");
- }
- GamePacket::ClientboundPlayerPositionPacket(p) => {
- // TODO: reply with teleport confirm
- println!("Got player position packet {:?}", p);
- }
- GamePacket::ClientboundPlayerInfoPacket(p) => {
- println!("Got player info packet {:?}", p);
- }
- GamePacket::ClientboundSetChunkCacheCenterPacket(p) => {
- println!("Got chunk cache center packet {:?}", p);
- state
- .lock()
- .await
- .world
- .as_mut()
- .unwrap()
- .update_view_center(&ChunkPos::new(p.x, p.z));
- }
- GamePacket::ClientboundLevelChunkWithLightPacket(p) => {
- println!("Got chunk with light packet {} {}", p.x, p.z);
- let pos = ChunkPos::new(p.x, p.z);
- // let chunk = Chunk::read_with_world_height(&mut p.chunk_data);
- // println("chunk {:?}")
- state
- .lock()
- .await
- .world
- .as_mut()
- .expect("World doesn't exist! We should've gotten a login packet by now.")
- .replace_with_packet_data(&pos, &mut p.chunk_data.data.as_slice())
- .unwrap();
- }
- GamePacket::ClientboundLightUpdatePacket(p) => {
- println!("Got light update packet {:?}", p);
- }
- GamePacket::ClientboundAddEntityPacket(p) => {
- println!("Got add entity packet {:?}", p);
- let pos = EntityPos {
- x: p.x,
- y: p.y,
- z: p.z,
- };
- }
- GamePacket::ClientboundSetEntityDataPacket(p) => {
- // println!("Got set entity data packet {:?}", p);
- }
- GamePacket::ClientboundUpdateAttributesPacket(p) => {
- // println!("Got update attributes packet {:?}", p);
- }
- GamePacket::ClientboundEntityVelocityPacket(p) => {
- // println!("Got entity velocity packet {:?}", p);
- }
- GamePacket::ClientboundSetEntityLinkPacket(p) => {
- println!("Got set entity link packet {:?}", p);
- }
- GamePacket::ClientboundAddPlayerPacket(p) => {
- println!("Got add player packet {:?}", p);
- }
- GamePacket::ClientboundInitializeBorderPacket(p) => {
- println!("Got initialize border packet {:?}", p);
- }
- GamePacket::ClientboundSetTimePacket(p) => {
- println!("Got set time packet {:?}", p);
- }
- GamePacket::ClientboundSetDefaultSpawnPositionPacket(p) => {
- println!("Got set default spawn position packet {:?}", p);
- }
- GamePacket::ClientboundContainerSetContentPacket(p) => {
- println!("Got container set content packet {:?}", p);
- }
- GamePacket::ClientboundSetHealthPacket(p) => {
- println!("Got set health packet {:?}", p);
- }
- GamePacket::ClientboundSetExperiencePacket(p) => {
- println!("Got set experience packet {:?}", p);
- }
- GamePacket::ClientboundTeleportEntityPacket(p) => {
- // println!("Got teleport entity packet {:?}", p);
- }
- GamePacket::ClientboundUpdateAdvancementsPacket(p) => {
- println!("Got update advancements packet {:?}", p);
- }
- GamePacket::ClientboundRotateHeadPacket(p) => {
- // println!("Got rotate head packet {:?}", p);
- }
- GamePacket::ClientboundMoveEntityPosPacket(p) => {
- // println!("Got move entity pos packet {:?}", p);
- }
- GamePacket::ClientboundMoveEntityPosRotPacket(p) => {
- // println!("Got move entity pos rot packet {:?}", p);
- }
- GamePacket::ClientboundMoveEntityRotPacket(p) => {
- println!("Got move entity rot packet {:?}", p);
- }
- GamePacket::ClientboundKeepAlivePacket(p) => {
- println!("Got keep alive packet {:?}", p);
- conn.lock()
- .await
- .write(ServerboundKeepAlivePacket { id: p.id }.get())
- .await;
- }
- GamePacket::ClientboundRemoveEntitiesPacket(p) => {
- println!("Got remove entities packet {:?}", p);
- }
- GamePacket::ClientboundPlayerChatPacket(p) => {
- println!("Got player chat packet {:?}", p);
- tx.send(Event::Chat(ChatPacket::Player(p.clone()))).unwrap();
- }
- GamePacket::ClientboundSystemChatPacket(p) => {
- println!("Got system chat packet {:?}", p);
- tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap();
- }
- GamePacket::ClientboundSoundPacket(p) => {
- println!("Got sound packet {:?}", p);
- }
- GamePacket::ClientboundLevelEventPacket(p) => {
- println!("Got level event packet {:?}", p);
- }
- GamePacket::ClientboundBlockUpdatePacket(p) => {
- println!("Got block update packet {:?}", p);
- // TODO: update world
- }
- GamePacket::ClientboundAnimatePacket(p) => {
- println!("Got animate packet {:?}", p);
- }
- GamePacket::ClientboundSectionBlocksUpdatePacket(p) => {
- println!("Got section blocks update packet {:?}", p);
- // TODO: update world
- }
- GamePacket::ClientboundGameEventPacket(p) => {
- println!("Got game event packet {:?}", p);
- }
- GamePacket::ClientboundLevelParticlesPacket(p) => {
- println!("Got level particles packet {:?}", p);
- }
- GamePacket::ClientboundServerDataPacket(p) => {
- println!("Got server data packet {:?}", p);
- }
- GamePacket::ClientboundSetEquipmentPacket(p) => {
- println!("Got set equipment packet {:?}", p);
- }
- _ => panic!("Unexpected packet {:?}", packet),
- }
- }
-
- pub async fn next(&mut self) -> Option<Event> {
- self.event_receiver.recv().await
- }
-}
-
-impl Account {
- pub fn offline(username: &str) -> Self {
- Self {
- username: username.to_string(),
- }
- }
-
- pub async fn join(&self, address: &ServerAddress) -> Result<Client, String> {
- Client::join(self, address).await
- }
-}
diff --git a/azalea-client/src/entity.rs b/azalea-client/src/entity.rs
deleted file mode 100644
index d91f556f..00000000
--- a/azalea-client/src/entity.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use azalea_core::EntityPos;
-
-#[derive(Default, Debug)]
-pub struct Entity {
- /// The incremental numerical id of the entity.
- pub id: u32,
- pub pos: EntityPos,
-}
diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs
index 5814687a..c3c37460 100755
--- a/azalea-client/src/lib.rs
+++ b/azalea-client/src/lib.rs
@@ -1,12 +1,13 @@
//! Significantly abstract azalea-protocol so it's actually useable for bots.
-mod connect;
-mod entity;
+mod account;
+mod client;
+mod movement;
pub mod ping;
mod player;
-pub use connect::{Account, Client, Event};
-pub use entity::Entity;
+pub use account::Account;
+pub use client::{Client, Event};
pub use player::Player;
#[cfg(test)]
diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs
new file mode 100644
index 00000000..f74d48df
--- /dev/null
+++ b/azalea-client/src/movement.rs
@@ -0,0 +1,45 @@
+use crate::Client;
+use azalea_core::EntityPos;
+use azalea_protocol::packets::game::serverbound_move_player_packet_pos_rot::ServerboundMovePlayerPacketPosRot;
+
+impl Client {
+ /// Set the client's position to the given coordinates.
+ pub async fn move_to(&mut self, new_pos: EntityPos) -> Result<(), String> {
+ println!("obtaining lock on state");
+ let mut state_lock = self.state.lock().unwrap();
+ println!("obtained lock on state");
+
+ let world = state_lock.world.as_ref().unwrap();
+
+ let player = &state_lock.player;
+ let player_id = if let Some(player) = player.entity(world) {
+ player.id
+ } else {
+ return Err("Player entity not found".to_string());
+ };
+
+ let world = state_lock.world.as_mut().unwrap();
+ world.move_entity(player_id, new_pos)?;
+ drop(state_lock);
+
+ println!("obtaining lock on conn");
+ self.conn
+ .lock()
+ .await
+ .write(
+ ServerboundMovePlayerPacketPosRot {
+ x: new_pos.x,
+ y: new_pos.y,
+ z: new_pos.z,
+ x_rot: 0.0,
+ y_rot: 0.0,
+ on_ground: false,
+ }
+ .get(),
+ )
+ .await;
+ println!("obtained lock on conn");
+
+ Ok(())
+ }
+}
diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs
index 04d34f6d..ee0b9718 100644
--- a/azalea-client/src/player.rs
+++ b/azalea-client/src/player.rs
@@ -1,7 +1,27 @@
-use crate::Entity;
+use azalea_entity::Entity;
+use azalea_world::World;
+use uuid::Uuid;
#[derive(Default, Debug)]
pub struct Player {
- /// The entity attached to the player. There's some useful fields here.
- pub entity: Entity,
+ /// The player's uuid.
+ pub uuid: Uuid,
+ /// The player's entity id.
+ pub entity_id: u32,
+}
+
+impl Player {
+ /// Get the entity of the player in the world.
+ pub fn entity<'a>(&self, world: &'a World) -> Option<&'a Entity> {
+ // world.entity_by_uuid(&self.uuid)
+ world.entity_by_id(self.entity_id)
+ }
+
+ pub fn set_uuid(&mut self, uuid: Uuid) {
+ self.uuid = uuid;
+ }
+
+ pub fn set_entity_id(&mut self, entity_id: u32) {
+ self.entity_id = entity_id;
+ }
}
diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml
index 27112b21..470bc998 100755
--- a/azalea-core/Cargo.toml
+++ b/azalea-core/Cargo.toml
@@ -6,6 +6,7 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+azalea-buf = {path = "../azalea-buf"}
azalea-chat = {path = "../azalea-chat"}
azalea-nbt = {path = "../azalea-nbt"}
uuid = "^1.1.2"
diff --git a/azalea-core/src/delta.rs b/azalea-core/src/delta.rs
new file mode 100644
index 00000000..c0056411
--- /dev/null
+++ b/azalea-core/src/delta.rs
@@ -0,0 +1,68 @@
+use crate::EntityPos;
+pub use azalea_buf::McBuf;
+
+pub trait PositionDeltaTrait {
+ fn x(&self) -> f64;
+ fn y(&self) -> f64;
+ fn z(&self) -> f64;
+}
+
+#[derive(Clone, Debug, McBuf, Default)]
+pub struct PositionDelta {
+ pub xa: f64,
+ pub ya: f64,
+ pub za: f64,
+}
+
+/// Only works for up to 8 blocks
+#[derive(Clone, Debug, McBuf, Default)]
+pub struct PositionDelta8 {
+ pub xa: i16,
+ pub ya: i16,
+ pub za: i16,
+}
+
+impl PositionDeltaTrait for PositionDelta {
+ fn x(&self) -> f64 {
+ self.xa
+ }
+ fn y(&self) -> f64 {
+ self.ya
+ }
+ fn z(&self) -> f64 {
+ self.za
+ }
+}
+
+impl PositionDelta8 {
+ #[deprecated]
+ pub fn float(&self) -> (f64, f64, f64) {
+ (
+ (self.xa as f64) / 4096.0,
+ (self.ya as f64) / 4096.0,
+ (self.za as f64) / 4096.0,
+ )
+ }
+}
+
+impl PositionDeltaTrait for PositionDelta8 {
+ fn x(&self) -> f64 {
+ (self.xa as f64) / 4096.0
+ }
+ fn y(&self) -> f64 {
+ (self.ya as f64) / 4096.0
+ }
+ fn z(&self) -> f64 {
+ (self.za as f64) / 4096.0
+ }
+}
+
+impl EntityPos {
+ pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> EntityPos {
+ EntityPos {
+ x: self.x + delta.x(),
+ y: self.y + delta.y(),
+ z: self.z + delta.z(),
+ }
+ }
+}
diff --git a/azalea-core/src/difficulty.rs b/azalea-core/src/difficulty.rs
index 5d869325..ebbb7708 100755
--- a/azalea-core/src/difficulty.rs
+++ b/azalea-core/src/difficulty.rs
@@ -1,11 +1,16 @@
-use std::fmt::{Debug, Error, Formatter};
+use std::{
+ fmt::{Debug, Error, Formatter},
+ io::{Read, Write},
+};
+
+use azalea_buf::{McBufReadable, McBufWritable};
#[derive(Hash, Clone, Debug, PartialEq)]
pub enum Difficulty {
- PEACEFUL,
- EASY,
- NORMAL,
- HARD,
+ PEACEFUL = 0,
+ EASY = 1,
+ NORMAL = 2,
+ HARD = 3,
}
pub enum Err {
@@ -61,6 +66,18 @@ impl Difficulty {
}
}
+impl McBufReadable for Difficulty {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ Ok(Difficulty::by_id(u8::read_into(buf)?))
+ }
+}
+
+impl McBufWritable for Difficulty {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ u8::write_into(&self.id(), buf)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs
index 721f21a0..d3083922 100644
--- a/azalea-core/src/direction.rs
+++ b/azalea-core/src/direction.rs
@@ -1,4 +1,6 @@
-#[derive(Clone, Copy, Debug)]
+use azalea_buf::McBuf;
+
+#[derive(Clone, Copy, Debug, McBuf)]
pub enum Direction {
Down = 0,
Up = 1,
diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs
index f5b9fb38..67c392b2 100755
--- a/azalea-core/src/game_type.rs
+++ b/azalea-core/src/game_type.rs
@@ -1,4 +1,7 @@
-#[derive(Hash, Clone, Debug)]
+use azalea_buf::{McBufReadable, McBufWritable};
+use std::io::{Read, Write};
+
+#[derive(Hash, Copy, Clone, Debug)]
pub enum GameType {
SURVIVAL,
CREATIVE,
@@ -17,8 +20,8 @@ impl GameType {
}
/// Get the id of the game type, but return -1 if the game type is invalid.
- pub fn to_optional_id(game_type: &Option<GameType>) -> i8 {
- match game_type {
+ pub fn to_optional_id<T: Into<Option<GameType>>>(game_type: T) -> i8 {
+ match game_type.into() {
Some(game_type) => game_type.to_id() as i8,
None => -1,
}
@@ -34,11 +37,12 @@ impl GameType {
})
}
- pub fn from_optional_id(id: i8) -> Result<Option<GameType>, String> {
+ pub fn from_optional_id(id: i8) -> Result<OptionalGameType, String> {
Ok(match id {
-1 => None,
id => Some(GameType::from_id(id as u8)?),
- })
+ }
+ .into())
}
pub fn short_name(&self) -> &'static str {
@@ -71,3 +75,43 @@ impl GameType {
}
}
}
+
+impl McBufReadable for GameType {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ GameType::from_id(u8::read_into(buf)?)
+ }
+}
+
+impl McBufWritable for GameType {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ u8::write_into(&self.to_id(), buf)
+ }
+}
+
+/// Rust doesn't let us `impl McBufReadable for Option<GameType>` so we have to make a new type :(
+#[derive(Hash, Copy, Clone, Debug)]
+pub struct OptionalGameType(Option<GameType>);
+
+impl From<Option<GameType>> for OptionalGameType {
+ fn from(game_type: Option<GameType>) -> Self {
+ OptionalGameType(game_type)
+ }
+}
+
+impl From<OptionalGameType> for Option<GameType> {
+ fn from(optional_game_type: OptionalGameType) -> Self {
+ optional_game_type.0
+ }
+}
+
+impl McBufReadable for OptionalGameType {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ GameType::from_optional_id(i8::read_into(buf)?)
+ }
+}
+
+impl McBufWritable for OptionalGameType {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ GameType::to_optional_id(*self).write_into(buf)
+ }
+}
diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs
index a2632871..a1fa1fca 100755
--- a/azalea-core/src/lib.rs
+++ b/azalea-core/src/lib.rs
@@ -2,10 +2,14 @@
#![feature(int_roundings)]
-pub mod difficulty;
-pub mod game_type;
-pub mod resource_location;
-pub mod serializable_uuid;
+mod difficulty;
+pub use difficulty::*;
+
+mod resource_location;
+pub use resource_location::*;
+
+mod game_type;
+pub use game_type::*;
mod slot;
pub use slot::{Slot, SlotData};
@@ -15,3 +19,9 @@ pub use position::*;
mod direction;
pub use direction::Direction;
+
+mod delta;
+pub use delta::*;
+
+mod particle;
+pub use particle::*;
diff --git a/azalea-protocol/src/mc_buf/definitions.rs b/azalea-core/src/particle/mod.rs
index 3867b3fc..6eb53955 100644
--- a/azalea-protocol/src/mc_buf/definitions.rs
+++ b/azalea-core/src/particle/mod.rs
@@ -1,186 +1,6 @@
-use crate::mc_buf::read::{McBufReadable, Readable};
-use crate::mc_buf::write::{McBufWritable, Writable};
-use crate::mc_buf::McBufVarReadable;
-use azalea_chat::component::Component;
-use azalea_core::{BlockPos, Direction, Slot};
-use packet_macros::McBuf;
+use crate::{BlockPos, Slot};
+use azalea_buf::{McBuf, McBufReadable, McBufVarReadable, McBufWritable};
use std::io::{Read, Write};
-use std::ops::Deref;
-use uuid::Uuid;
-
-/// A Vec<u8> that isn't prefixed by a VarInt with the size.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct UnsizedByteArray(Vec<u8>);
-
-impl Deref for UnsizedByteArray {
- type Target = Vec<u8>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl From<Vec<u8>> for UnsizedByteArray {
- fn from(vec: Vec<u8>) -> Self {
- Self(vec)
- }
-}
-
-impl From<&str> for UnsizedByteArray {
- fn from(s: &str) -> Self {
- Self(s.as_bytes().to_vec())
- }
-}
-
-/// Represents Java's BitSet, a list of bits.
-#[derive(Debug, Clone, PartialEq, Eq, Hash, McBuf)]
-pub struct BitSet {
- data: Vec<u64>,
-}
-
-// the Index trait requires us to return a reference, but we can't do that
-impl BitSet {
- pub fn index(&self, index: usize) -> bool {
- (self.data[index / 64] & (1u64 << (index % 64))) != 0
- }
-}
-
-pub type EntityMetadata = Vec<EntityDataItem>;
-
-#[derive(Clone, Debug)]
-pub struct EntityDataItem {
- // we can't identify what the index is for here because we don't know the
- // entity type
- pub index: u8,
- pub value: EntityDataValue,
-}
-
-impl McBufReadable for Vec<EntityDataItem> {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let mut metadata = Vec::new();
- loop {
- let index = buf.read_byte()?;
- if index == 0xff {
- break;
- }
- let value = EntityDataValue::read_into(buf)?;
- metadata.push(EntityDataItem { index, value });
- }
- Ok(metadata)
- }
-}
-
-impl McBufWritable for Vec<EntityDataItem> {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- for item in self {
- buf.write_byte(item.index)?;
- item.value.write_into(buf)?;
- }
- buf.write_byte(0xff)?;
- Ok(())
- }
-}
-
-#[derive(Clone, Debug)]
-pub enum EntityDataValue {
- Byte(u8),
- // varint
- Int(i32),
- Float(f32),
- String(String),
- Component(Component),
- OptionalComponent(Option<Component>),
- ItemStack(Slot),
- Boolean(bool),
- Rotations { x: f32, y: f32, z: f32 },
- BlockPos(BlockPos),
- OptionalBlockPos(Option<BlockPos>),
- Direction(Direction),
- OptionalUuid(Option<Uuid>),
- // 0 for absent (implies air); otherwise, a block state ID as per the global palette
- // this is a varint
- OptionalBlockState(Option<i32>),
- CompoundTag(azalea_nbt::Tag),
- Particle(Particle),
- VillagerData(VillagerData),
- // 0 for absent; 1 + actual value otherwise. Used for entity IDs.
- OptionalUnsignedInt(Option<u32>),
- Pose(Pose),
-}
-
-impl McBufReadable for EntityDataValue {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let type_ = buf.read_varint()?;
- Ok(match type_ {
- 0 => EntityDataValue::Byte(buf.read_byte()?),
- 1 => EntityDataValue::Int(buf.read_varint()?),
- 2 => EntityDataValue::Float(buf.read_float()?),
- 3 => EntityDataValue::String(buf.read_utf()?),
- 4 => EntityDataValue::Component(Component::read_into(buf)?),
- 5 => EntityDataValue::OptionalComponent(Option::<Component>::read_into(buf)?),
- 6 => EntityDataValue::ItemStack(Slot::read_into(buf)?),
- 7 => EntityDataValue::Boolean(buf.read_boolean()?),
- 8 => EntityDataValue::Rotations {
- x: buf.read_float()?,
- y: buf.read_float()?,
- z: buf.read_float()?,
- },
- 9 => EntityDataValue::BlockPos(BlockPos::read_into(buf)?),
- 10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_into(buf)?),
- 11 => EntityDataValue::Direction(Direction::read_into(buf)?),
- 12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_into(buf)?),
- 13 => EntityDataValue::OptionalBlockState({
- let val = i32::read_into(buf)?;
- if val == 0 {
- None
- } else {
- Some(val)
- }
- }),
- 14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_into(buf)?),
- 15 => EntityDataValue::Particle(Particle::read_into(buf)?),
- 16 => EntityDataValue::VillagerData(VillagerData::read_into(buf)?),
- 17 => EntityDataValue::OptionalUnsignedInt({
- let val = buf.read_varint()?;
- if val == 0 {
- None
- } else {
- Some((val - 1) as u32)
- }
- }),
- 18 => EntityDataValue::Pose(Pose::read_into(buf)?),
- _ => return Err(format!("Unknown entity data type: {}", type_)),
- })
- }
-}
-
-impl McBufWritable for EntityDataValue {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- todo!();
- }
-}
-
-#[derive(Clone, Debug, Copy, McBuf)]
-pub enum Pose {
- Standing = 0,
- FallFlying = 1,
- Sleeping = 2,
- Swimming = 3,
- SpinAttack = 4,
- Sneaking = 5,
- LongJumping = 6,
- Dying = 7,
-}
-
-#[derive(Debug, Clone, McBuf)]
-pub struct VillagerData {
- #[var]
- type_: u32,
- #[var]
- profession: u32,
- #[var]
- level: u32,
-}
#[derive(Debug, Clone, McBuf)]
pub struct Particle {
diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs
index 24be5f6a..de8e2516 100644
--- a/azalea-core/src/position.rs
+++ b/azalea-core/src/position.rs
@@ -1,8 +1,17 @@
-use std::ops::Rem;
+use crate::ResourceLocation;
+use azalea_buf::{McBufReadable, McBufWritable};
+use std::{
+ io::{Read, Write},
+ ops::Rem,
+};
-use crate::resource_location::ResourceLocation;
+pub trait PositionXYZ<T> {
+ fn add_x(&self, n: T) -> Self;
+ fn add_y(&self, n: T) -> Self;
+ fn add_z(&self, n: T) -> Self;
+}
-#[derive(Clone, Copy, Debug, Default)]
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct BlockPos {
pub x: i32,
pub y: i32,
@@ -27,7 +36,31 @@ impl Rem<i32> for BlockPos {
}
}
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
+impl PositionXYZ<i32> for BlockPos {
+ fn add_x(&self, n: i32) -> Self {
+ BlockPos {
+ x: self.x + n,
+ y: self.y,
+ z: self.z,
+ }
+ }
+ fn add_y(&self, n: i32) -> Self {
+ BlockPos {
+ x: self.x,
+ y: self.y + n,
+ z: self.z,
+ }
+ }
+ fn add_z(&self, n: i32) -> Self {
+ BlockPos {
+ x: self.x,
+ y: self.y,
+ z: self.z + n,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct ChunkPos {
pub x: i32,
pub z: i32,
@@ -39,15 +72,6 @@ impl ChunkPos {
}
}
-impl From<&BlockPos> for ChunkPos {
- fn from(pos: &BlockPos) -> Self {
- ChunkPos {
- x: pos.x.div_floor(16),
- z: pos.z.div_floor(16),
- }
- }
-}
-
/// The coordinates of a chunk section in the world.
#[derive(Clone, Copy, Debug, Default)]
pub struct ChunkSectionPos {
@@ -61,23 +85,6 @@ impl ChunkSectionPos {
ChunkSectionPos { x, y, z }
}
}
-
-impl From<BlockPos> for ChunkSectionPos {
- fn from(pos: BlockPos) -> Self {
- ChunkSectionPos {
- x: pos.x.div_floor(16),
- y: pos.y.div_floor(16),
- z: pos.z.div_floor(16),
- }
- }
-}
-
-impl From<ChunkSectionPos> for ChunkPos {
- fn from(pos: ChunkSectionPos) -> Self {
- ChunkPos { x: pos.x, z: pos.z }
- }
-}
-
/// The coordinates of a block inside a chunk.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ChunkBlockPos {
@@ -91,17 +98,6 @@ impl ChunkBlockPos {
ChunkBlockPos { x, y, z }
}
}
-
-impl From<&BlockPos> for ChunkBlockPos {
- fn from(pos: &BlockPos) -> Self {
- ChunkBlockPos {
- x: pos.x.rem_euclid(16).abs() as u8,
- y: pos.y,
- z: pos.z.rem_euclid(16).abs() as u8,
- }
- }
-}
-
/// The coordinates of a block inside a chunk section.
#[derive(Clone, Copy, Debug, Default)]
pub struct ChunkSectionBlockPos {
@@ -119,6 +115,80 @@ impl ChunkSectionBlockPos {
}
}
+/// A block pos with an attached dimension
+#[derive(Debug, Clone)]
+pub struct GlobalPos {
+ pub pos: BlockPos,
+ // this is actually a ResourceKey in Minecraft, but i don't think it matters?
+ pub dimension: ResourceLocation,
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct EntityPos {
+ pub x: f64,
+ pub y: f64,
+ pub z: f64,
+}
+
+impl PositionXYZ<f64> for EntityPos {
+ fn add_x(&self, n: f64) -> Self {
+ EntityPos {
+ x: self.x + n,
+ y: self.y,
+ z: self.z,
+ }
+ }
+ fn add_y(&self, n: f64) -> Self {
+ EntityPos {
+ x: self.x,
+ y: self.y + n,
+ z: self.z,
+ }
+ }
+ fn add_z(&self, n: f64) -> Self {
+ EntityPos {
+ x: self.x,
+ y: self.y,
+ z: self.z + n,
+ }
+ }
+}
+
+impl From<&BlockPos> for ChunkPos {
+ fn from(pos: &BlockPos) -> Self {
+ ChunkPos {
+ x: pos.x.div_floor(16),
+ z: pos.z.div_floor(16),
+ }
+ }
+}
+
+impl From<BlockPos> for ChunkSectionPos {
+ fn from(pos: BlockPos) -> Self {
+ ChunkSectionPos {
+ x: pos.x.div_floor(16),
+ y: pos.y.div_floor(16),
+ z: pos.z.div_floor(16),
+ }
+ }
+}
+
+impl From<ChunkSectionPos> for ChunkPos {
+ fn from(pos: ChunkSectionPos) -> Self {
+ ChunkPos { x: pos.x, z: pos.z }
+ }
+}
+
+impl From<&BlockPos> for ChunkBlockPos {
+ fn from(pos: &BlockPos) -> Self {
+ ChunkBlockPos {
+ x: pos.x.rem_euclid(16).abs() as u8,
+ y: pos.y,
+ z: pos.z.rem_euclid(16).abs() as u8,
+ }
+ }
+}
+
impl From<&BlockPos> for ChunkSectionBlockPos {
fn from(pos: &BlockPos) -> Self {
ChunkSectionBlockPos {
@@ -138,29 +208,77 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos {
}
}
}
+impl From<&EntityPos> for BlockPos {
+ fn from(pos: &EntityPos) -> Self {
+ BlockPos {
+ x: pos.x.floor() as i32,
+ y: pos.y.floor() as i32,
+ z: pos.z.floor() as i32,
+ }
+ }
+}
-/// A block pos with an attached dimension
-#[derive(Debug, Clone)]
-pub struct GlobalPos {
- pub pos: BlockPos,
- // this is actually a ResourceKey in Minecraft, but i don't think it matters?
- pub dimension: ResourceLocation,
+impl From<&EntityPos> for ChunkPos {
+ fn from(pos: &EntityPos) -> Self {
+ ChunkPos::from(&BlockPos::from(pos))
+ }
}
-#[derive(Debug, Clone, Default)]
-pub struct EntityPos {
- pub x: f64,
- pub y: f64,
- pub z: f64,
+impl McBufReadable for BlockPos {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let val = u64::read_into(buf)?;
+ let x = (val >> 38) as i32;
+ let y = (val & 0xFFF) as i32;
+ let z = ((val >> 12) & 0x3FFFFFF) as i32;
+ Ok(BlockPos { x, y, z })
+ }
}
-impl From<&EntityPos> for BlockPos {
- fn from(pos: &EntityPos) -> Self {
- BlockPos {
- x: pos.x as i32,
- y: pos.y as i32,
- z: pos.z as i32,
- }
+impl McBufReadable for GlobalPos {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ Ok(GlobalPos {
+ dimension: ResourceLocation::read_into(buf)?,
+ pos: BlockPos::read_into(buf)?,
+ })
+ }
+}
+
+impl McBufReadable for ChunkSectionPos {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let long = i64::read_into(buf)?;
+ Ok(ChunkSectionPos {
+ x: (long >> 42) as i32,
+ y: (long << 44 >> 44) as i32,
+ z: (long << 22 >> 42) as i32,
+ })
+ }
+}
+
+impl McBufWritable for BlockPos {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ let data = (((self.x & 0x3FFFFFF) as i64) << 38)
+ | (((self.z & 0x3FFFFFF) as i64) << 12)
+ | ((self.y & 0xFFF) as i64);
+ data.write_into(buf)
+ }
+}
+
+impl McBufWritable for GlobalPos {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ ResourceLocation::write_into(&self.dimension, buf)?;
+ BlockPos::write_into(&self.pos, buf)?;
+
+ Ok(())
+ }
+}
+
+impl McBufWritable for ChunkSectionPos {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ let long = (((self.x & 0x3FFFFF) as i64) << 42)
+ | (self.y & 0xFFFFF) as i64
+ | (((self.z & 0x3FFFFF) as i64) << 20);
+ long.write_into(buf)?;
+ Ok(())
}
}
@@ -181,4 +299,26 @@ mod tests {
let chunk_block_pos = ChunkBlockPos::from(&block_pos);
assert_eq!(chunk_block_pos, ChunkBlockPos::new(5, 78, 14));
}
+
+ #[test]
+ fn test_from_entity_pos_to_block_pos() {
+ let entity_pos = EntityPos {
+ x: 31.5,
+ y: 80.0,
+ z: -16.1,
+ };
+ let block_pos = BlockPos::from(&entity_pos);
+ assert_eq!(block_pos, BlockPos::new(31, 80, -17));
+ }
+
+ #[test]
+ fn test_from_entity_pos_to_chunk_pos() {
+ let entity_pos = EntityPos {
+ x: 31.5,
+ y: 80.0,
+ z: -16.1,
+ };
+ let chunk_pos = ChunkPos::from(&entity_pos);
+ assert_eq!(chunk_pos, ChunkPos::new(1, -2));
+ }
}
diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs
index cdf8f381..acca0c58 100755
--- a/azalea-core/src/resource_location.rs
+++ b/azalea-core/src/resource_location.rs
@@ -1,5 +1,8 @@
//! A resource, like minecraft:stone
+use azalea_buf::{McBufReadable, McBufWritable};
+use std::io::{Read, Write};
+
#[derive(Hash, Clone, PartialEq, Eq)]
pub struct ResourceLocation {
pub namespace: String,
@@ -42,8 +45,22 @@ impl std::fmt::Debug for ResourceLocation {
}
}
+impl McBufReadable for ResourceLocation {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let location_string = String::read_into(buf)?;
+ ResourceLocation::new(&location_string)
+ }
+}
+impl McBufWritable for ResourceLocation {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.to_string().write_into(buf)
+ }
+}
+
#[cfg(test)]
mod tests {
+ use std::io::Cursor;
+
use super::*;
#[test]
@@ -70,4 +87,20 @@ mod tests {
assert_eq!(r.namespace, "azalea");
assert_eq!(r.path, "");
}
+
+ #[test]
+ fn mcbuf_resource_location() {
+ let mut buf = Vec::new();
+ ResourceLocation::new("minecraft:dirt")
+ .unwrap()
+ .write_into(&mut buf)
+ .unwrap();
+
+ let mut buf = Cursor::new(buf);
+
+ assert_eq!(
+ ResourceLocation::read_into(&mut buf).unwrap(),
+ ResourceLocation::new("minecraft:dirt").unwrap()
+ );
+ }
}
diff --git a/azalea-core/src/slot.rs b/azalea-core/src/slot.rs
index 5e42f558..6e622872 100644
--- a/azalea-core/src/slot.rs
+++ b/azalea-core/src/slot.rs
@@ -1,14 +1,40 @@
// TODO: have an azalea-inventory or azalea-container crate and put this there
+use azalea_buf::{McBuf, McBufReadable, McBufWritable};
+use std::io::{Read, Write};
+
#[derive(Debug, Clone)]
pub enum Slot {
- Present(SlotData),
Empty,
+ Present(SlotData),
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, McBuf)]
pub struct SlotData {
+ #[var]
pub id: i32,
pub count: u8,
pub nbt: azalea_nbt::Tag,
}
+
+impl McBufReadable for Slot {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let present = bool::read_into(buf)?;
+ if !present {
+ return Ok(Slot::Empty);
+ }
+ let slot = SlotData::read_into(buf)?;
+ Ok(Slot::Present(slot))
+ }
+}
+
+impl McBufWritable for Slot {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ match self {
+ Slot::Empty => 0u8.write_into(buf)?,
+ Slot::Present(i) => i.write_into(buf)?,
+ }
+
+ Ok(())
+ }
+}
diff --git a/azalea-crypto/Cargo.toml b/azalea-crypto/Cargo.toml
index 2532bff9..ee652565 100644
--- a/azalea-crypto/Cargo.toml
+++ b/azalea-crypto/Cargo.toml
@@ -7,6 +7,7 @@ version = "0.1.0"
[dependencies]
aes = "0.8.1"
+azalea-buf = {path = "../azalea-buf"}
cfb8 = "0.8.1"
num-bigint = "^0.4.3"
rand = {version = "^0.8.4", features = ["getrandom"]}
diff --git a/azalea-crypto/src/signing.rs b/azalea-crypto/src/signing.rs
index 21cd813a..4fc07d30 100644
--- a/azalea-crypto/src/signing.rs
+++ b/azalea-crypto/src/signing.rs
@@ -1,5 +1,24 @@
+use azalea_buf::{McBufReadable, McBufWritable};
+use std::io::{Read, Write};
+
#[derive(Debug, Clone)]
pub struct SaltSignaturePair {
pub salt: u64,
pub signature: Vec<u8>,
}
+
+impl McBufReadable for SaltSignaturePair {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let salt = u64::read_into(buf)?;
+ let signature = Vec::<u8>::read_into(buf)?;
+ Ok(SaltSignaturePair { salt, signature })
+ }
+}
+
+impl McBufWritable for SaltSignaturePair {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.salt.write_into(buf)?;
+ self.signature.write_into(buf)?;
+ Ok(())
+ }
+}
diff --git a/azalea-entity/Cargo.toml b/azalea-entity/Cargo.toml
new file mode 100644
index 00000000..022a3343
--- /dev/null
+++ b/azalea-entity/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+edition = "2021"
+name = "azalea-entity"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+azalea-buf = {path = "../azalea-buf"}
+azalea-chat = {path = "../azalea-chat"}
+azalea-core = {path = "../azalea-core"}
+azalea-nbt = {path = "../azalea-nbt"}
+uuid = "^1.1.2"
diff --git a/azalea-entity/src/data.rs b/azalea-entity/src/data.rs
new file mode 100644
index 00000000..5d2a66fc
--- /dev/null
+++ b/azalea-entity/src/data.rs
@@ -0,0 +1,144 @@
+use azalea_buf::McBufVarReadable;
+use azalea_buf::{McBuf, McBufReadable, McBufWritable};
+use azalea_chat::component::Component;
+use azalea_core::{BlockPos, Direction, Particle, Slot};
+use std::io::{Read, Write};
+use uuid::Uuid;
+
+#[derive(Clone, Debug)]
+pub struct EntityMetadata(Vec<EntityDataItem>);
+
+#[derive(Clone, Debug)]
+pub struct EntityDataItem {
+ // we can't identify what the index is for here because we don't know the
+ // entity type
+ pub index: u8,
+ pub value: EntityDataValue,
+}
+
+impl McBufReadable for EntityMetadata {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let mut metadata = Vec::new();
+ loop {
+ let index = u8::read_into(buf)?;
+ if index == 0xff {
+ break;
+ }
+ let value = EntityDataValue::read_into(buf)?;
+ metadata.push(EntityDataItem { index, value });
+ }
+ Ok(EntityMetadata(metadata))
+ }
+}
+
+impl McBufWritable for EntityMetadata {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ for item in &self.0 {
+ item.index.write_into(buf)?;
+ item.value.write_into(buf)?;
+ }
+ 0xffu8.write_into(buf)?;
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum EntityDataValue {
+ Byte(u8),
+ // varint
+ Int(i32),
+ Float(f32),
+ String(String),
+ Component(Component),
+ OptionalComponent(Option<Component>),
+ ItemStack(Slot),
+ Boolean(bool),
+ Rotations { x: f32, y: f32, z: f32 },
+ BlockPos(BlockPos),
+ OptionalBlockPos(Option<BlockPos>),
+ Direction(Direction),
+ OptionalUuid(Option<Uuid>),
+ // 0 for absent (implies air); otherwise, a block state ID as per the global palette
+ // this is a varint
+ OptionalBlockState(Option<i32>),
+ CompoundTag(azalea_nbt::Tag),
+ Particle(Particle),
+ VillagerData(VillagerData),
+ // 0 for absent; 1 + actual value otherwise. Used for entity IDs.
+ OptionalUnsignedInt(Option<u32>),
+ Pose(Pose),
+}
+
+impl McBufReadable for EntityDataValue {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let data_type = i32::var_read_into(buf)?;
+ Ok(match data_type {
+ 0 => EntityDataValue::Byte(u8::read_into(buf)?),
+ 1 => EntityDataValue::Int(i32::var_read_into(buf)?),
+ 2 => EntityDataValue::Float(f32::read_into(buf)?),
+ 3 => EntityDataValue::String(String::read_into(buf)?),
+ 4 => EntityDataValue::Component(Component::read_into(buf)?),
+ 5 => EntityDataValue::OptionalComponent(Option::<Component>::read_into(buf)?),
+ 6 => EntityDataValue::ItemStack(Slot::read_into(buf)?),
+ 7 => EntityDataValue::Boolean(bool::read_into(buf)?),
+ 8 => EntityDataValue::Rotations {
+ x: f32::read_into(buf)?,
+ y: f32::read_into(buf)?,
+ z: f32::read_into(buf)?,
+ },
+ 9 => EntityDataValue::BlockPos(BlockPos::read_into(buf)?),
+ 10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_into(buf)?),
+ 11 => EntityDataValue::Direction(Direction::read_into(buf)?),
+ 12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_into(buf)?),
+ 13 => EntityDataValue::OptionalBlockState({
+ let val = i32::read_into(buf)?;
+ if val == 0 {
+ None
+ } else {
+ Some(val)
+ }
+ }),
+ 14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_into(buf)?),
+ 15 => EntityDataValue::Particle(Particle::read_into(buf)?),
+ 16 => EntityDataValue::VillagerData(VillagerData::read_into(buf)?),
+ 17 => EntityDataValue::OptionalUnsignedInt({
+ let val = u32::var_read_into(buf)?;
+ if val == 0 {
+ None
+ } else {
+ Some((val - 1) as u32)
+ }
+ }),
+ 18 => EntityDataValue::Pose(Pose::read_into(buf)?),
+ _ => return Err(format!("Unknown entity data type: {}", data_type)),
+ })
+ }
+}
+
+impl McBufWritable for EntityDataValue {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ todo!();
+ }
+}
+
+#[derive(Clone, Debug, Copy, McBuf)]
+pub enum Pose {
+ Standing = 0,
+ FallFlying = 1,
+ Sleeping = 2,
+ Swimming = 3,
+ SpinAttack = 4,
+ Sneaking = 5,
+ LongJumping = 6,
+ Dying = 7,
+}
+
+#[derive(Debug, Clone, McBuf)]
+pub struct VillagerData {
+ #[var]
+ type_: u32,
+ #[var]
+ profession: u32,
+ #[var]
+ level: u32,
+}
diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs
new file mode 100644
index 00000000..9436d753
--- /dev/null
+++ b/azalea-entity/src/lib.rs
@@ -0,0 +1,59 @@
+mod data;
+
+use azalea_core::{EntityPos, PositionDelta};
+pub use data::*;
+use uuid::Uuid;
+
+#[derive(Default, Debug)]
+pub struct Entity {
+ /// The incrementing numerical id of the entity.
+ pub id: u32,
+ pub uuid: Uuid,
+ /// The position of the entity right now.
+ pos: EntityPos,
+ /// The position of the entity last tick.
+ pub old_pos: EntityPos,
+ pub delta: PositionDelta,
+
+ pub x_rot: f32,
+ pub y_rot: f32,
+}
+
+impl Entity {
+ pub fn new(id: u32, uuid: Uuid, pos: EntityPos) -> Self {
+ Self {
+ id,
+ uuid,
+ pos,
+ old_pos: pos,
+ delta: PositionDelta::default(),
+ x_rot: 0.0,
+ y_rot: 0.0,
+ }
+ }
+
+ pub fn pos(&self) -> &EntityPos {
+ &self.pos
+ }
+
+ /// Sets the position of the entity. This doesn't update the cache in
+ /// azalea-world, and should only be used within azalea-world!
+ pub fn unsafe_move(&mut self, new_pos: EntityPos) {
+ self.pos = new_pos;
+ }
+
+ pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
+ self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0;
+ self.x_rot = x_rot % 360.0;
+ // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so
+ }
+}
+
+// #[cfg(test)]
+// mod tests {
+// #[test]
+// fn it_works() {
+// let result = 2 + 2;
+// assert_eq!(result, 4);
+// }
+// }
diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml
index ad466e1f..992d242a 100755
--- a/azalea-nbt/Cargo.toml
+++ b/azalea-nbt/Cargo.toml
@@ -6,6 +6,7 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+azalea-buf = {path = "../azalea-buf"}
byteorder = "1.4.3"
flate2 = "1.0.23"
num-derive = "^0.3.3"
diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs
index 7f2ca754..1c011839 100755
--- a/azalea-nbt/src/decode.rs
+++ b/azalea-nbt/src/decode.rs
@@ -1,5 +1,6 @@
use crate::Error;
use crate::Tag;
+use azalea_buf::McBufReadable;
use byteorder::{ReadBytesExt, BE};
use flate2::read::{GzDecoder, ZlibDecoder};
use std::collections::HashMap;
@@ -136,3 +137,13 @@ impl Tag {
Tag::read(&mut gz)
}
}
+
+impl McBufReadable for Tag {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ match Tag::read(buf) {
+ Ok(r) => Ok(r),
+ // Err(e) => Err(e.to_string()),
+ Err(e) => Err(e.to_string()).unwrap(),
+ }
+ }
+}
diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs
index fb5585b3..3763ffd1 100755
--- a/azalea-nbt/src/encode.rs
+++ b/azalea-nbt/src/encode.rs
@@ -1,5 +1,6 @@
use crate::Error;
use crate::Tag;
+use azalea_buf::McBufWritable;
use byteorder::{WriteBytesExt, BE};
use flate2::write::{GzEncoder, ZlibEncoder};
use std::collections::HashMap;
@@ -217,3 +218,10 @@ impl Tag {
self.write(&mut encoder)
}
}
+
+impl McBufWritable for Tag {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.write(buf)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
+ }
+}
diff --git a/azalea-nbt/src/lib.rs b/azalea-nbt/src/lib.rs
index d14fd929..cb2eb28e 100755
--- a/azalea-nbt/src/lib.rs
+++ b/azalea-nbt/src/lib.rs
@@ -5,3 +5,37 @@ mod tag;
pub use error::Error;
pub use tag::Tag;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use azalea_buf::{McBufReadable, McBufWritable};
+ use std::{collections::HashMap, io::Cursor};
+
+ #[test]
+ fn mcbuf_nbt() {
+ let mut buf = Vec::new();
+ let tag = Tag::Compound(HashMap::from_iter(vec![(
+ "hello world".to_string(),
+ Tag::Compound(HashMap::from_iter(vec![(
+ "name".to_string(),
+ Tag::String("Bananrama".to_string()),
+ )])),
+ )]));
+ tag.write_into(&mut buf).unwrap();
+
+ let mut buf = Cursor::new(buf);
+
+ let result = Tag::read_into(&mut buf).unwrap();
+ assert_eq!(
+ result,
+ Tag::Compound(HashMap::from_iter(vec![(
+ "hello world".to_string(),
+ Tag::Compound(HashMap::from_iter(vec![(
+ "name".to_string(),
+ Tag::String("Bananrama".to_string()),
+ )])),
+ )]))
+ );
+ }
+}
diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml
index e49088bc..17884a51 100755
--- a/azalea-protocol/Cargo.toml
+++ b/azalea-protocol/Cargo.toml
@@ -6,13 +6,15 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-async-compression = {version = "^0.3.8", features = ["tokio", "zlib"]}
-async-recursion = "^1.0.0"
+async-compression = {version = "^0.3.8", features = ["tokio", "zlib"], optional = true}
+async-recursion = "1.0.0"
azalea-auth = {path = "../azalea-auth"}
azalea-brigadier = {path = "../azalea-brigadier"}
+azalea-buf = {path = "../azalea-buf"}
azalea-chat = {path = "../azalea-chat"}
-azalea-core = {path = "../azalea-core"}
+azalea-core = {path = "../azalea-core", optional = true}
azalea-crypto = {path = "../azalea-crypto"}
+azalea-entity = {path = "../azalea-entity"}
azalea-nbt = {path = "../azalea-nbt"}
byteorder = "^1.4.3"
bytes = "^1.1.0"
@@ -20,6 +22,13 @@ flate2 = "1.0.23"
packet-macros = {path = "./packet-macros"}
serde = {version = "1.0.130", features = ["serde_derive"]}
serde_json = "^1.0.72"
-tokio = {version = "1.19.2", features = ["io-util", "net", "macros"]}
-trust-dns-resolver = {version = "^0.21.2"}
-uuid = "^1.1.2"
+thiserror = "^1.0.30"
+tokio = {version = "^1.19.2", features = ["io-util", "net", "macros"]}
+tokio-util = "^0.6.9"
+trust-dns-resolver = "^0.20.3"
+uuid = "1.1.2"
+
+[features]
+connecting = []
+default = ["packets"]
+packets = ["connecting", "dep:async-compression", "dep:azalea-core"]
diff --git a/azalea-protocol/packet-macros/src/lib.rs b/azalea-protocol/packet-macros/src/lib.rs
index 5ea69a62..58487cdd 100755
--- a/azalea-protocol/packet-macros/src/lib.rs
+++ b/azalea-protocol/packet-macros/src/lib.rs
@@ -6,180 +6,6 @@ use syn::{
parse_macro_input, Data, DeriveInput, FieldsNamed, Ident, LitInt, Token,
};
-fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
- match data {
- syn::Data::Struct(syn::DataStruct { fields, .. }) => {
- let FieldsNamed { named, .. } = match fields {
- syn::Fields::Named(f) => f,
- _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
- };
-
- let read_fields = named
- .iter()
- .map(|f| {
- let field_name = &f.ident;
- let field_type = &f.ty;
- // do a different buf.write_* for each field depending on the type
- // if it's a string, use buf.write_string
- match field_type {
- syn::Type::Path(_) => {
- if f.attrs.iter().any(|a| a.path.is_ident("var")) {
- quote! {
- let #field_name = crate::mc_buf::McBufVarReadable::var_read_into(buf)?;
- }
- } else {
- quote! {
- let #field_name = crate::mc_buf::McBufReadable::read_into(buf)?;
- }
- }
- }
- _ => panic!(
- "Error reading field {}: {}",
- field_name.clone().unwrap(),
- field_type.to_token_stream()
- ),
- }
- })
- .collect::<Vec<_>>();
- let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
-
- quote! {
- impl crate::mc_buf::McBufReadable for #ident {
- fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String> {
- #(#read_fields)*
- Ok(#ident {
- #(#read_field_names: #read_field_names),*
- })
- }
- }
- }
- }
- syn::Data::Enum(syn::DataEnum { variants, .. }) => {
- let mut match_contents = quote!();
- let mut variant_discrim: u32 = 0;
- for variant in variants {
- let variant_name = &variant.ident;
- match &variant.discriminant.as_ref() {
- Some(d) => {
- variant_discrim = match &d.1 {
- syn::Expr::Lit(e) => match &e.lit {
- syn::Lit::Int(i) => i.base10_parse().unwrap(),
- _ => panic!("Error parsing enum discriminant"),
- },
- _ => panic!("Error parsing enum discriminant"),
- }
- }
- None => {
- variant_discrim += 1;
- }
- }
- match_contents.extend(quote! {
- #variant_discrim => Ok(Self::#variant_name),
- });
- }
-
- quote! {
- impl crate::mc_buf::McBufReadable for #ident {
- fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String>
- {
- let id = crate::mc_buf::McBufVarReadable::var_read_into(buf)?;
- match id {
- #match_contents
- _ => Err(format!("Unknown enum variant {}", id)),
- }
- }
- }
- }
- }
- _ => panic!("#[derive(*Packet)] can only be used on structs"),
- }
-}
-
-fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream {
- match data {
- syn::Data::Struct(syn::DataStruct { fields, .. }) => {
- let FieldsNamed { named, .. } = match fields {
- syn::Fields::Named(f) => f,
- _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
- };
-
- let write_fields = named
- .iter()
- .map(|f| {
- let field_name = &f.ident;
- let field_type = &f.ty;
- // do a different buf.write_* for each field depending on the type
- // if it's a string, use buf.write_string
- match field_type {
- syn::Type::Path(_) => {
- if f.attrs.iter().any(|attr| attr.path.is_ident("var")) {
- quote! {
- crate::mc_buf::McBufVarWritable::var_write_into(&self.#field_name, buf)?;
- }
- } else {
- quote! {
- crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?;
- }
- }
- }
- _ => panic!(
- "Error writing field {}: {}",
- field_name.clone().unwrap(),
- field_type.to_token_stream()
- ),
- }
- })
- .collect::<Vec<_>>();
-
- quote! {
- impl crate::mc_buf::McBufWritable for #ident {
- fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
- #(#write_fields)*
- Ok(())
- }
- }
- }
- }
- syn::Data::Enum(syn::DataEnum { .. }) => {
- quote! {
- impl crate::mc_buf::McBufWritable for #ident {
- fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
- crate::mc_buf::Writable::write_varint(buf, *self as i32)
- }
- }
- }
- }
- _ => panic!("#[derive(*Packet)] can only be used on structs"),
- }
-}
-
-#[proc_macro_derive(McBufReadable, attributes(var))]
-pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream {
- let DeriveInput { ident, data, .. } = parse_macro_input!(input);
-
- create_impl_mcbufreadable(&ident, &data).into()
-}
-
-#[proc_macro_derive(McBufWritable, attributes(var))]
-pub fn derive_mcbufwritable(input: TokenStream) -> TokenStream {
- let DeriveInput { ident, data, .. } = parse_macro_input!(input);
-
- create_impl_mcbufwritable(&ident, &data).into()
-}
-
-#[proc_macro_derive(McBuf, attributes(var))]
-pub fn derive_mcbuf(input: TokenStream) -> TokenStream {
- let DeriveInput { ident, data, .. } = parse_macro_input!(input);
-
- let writable = create_impl_mcbufwritable(&ident, &data);
- let readable = create_impl_mcbufreadable(&ident, &data);
- quote! {
- #writable
- #readable
- }
- .into()
-}
-
fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
@@ -192,9 +18,6 @@ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> Toke
_ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
};
- let _mcbufreadable_impl = create_impl_mcbufreadable(&ident, &data);
- let _mcbufwritable_impl = create_impl_mcbufwritable(&ident, &data);
-
let contents = quote! {
impl #ident {
pub fn get(self) -> #state {
@@ -202,13 +25,13 @@ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> Toke
}
pub fn write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
- crate::mc_buf::McBufWritable::write_into(self, buf)
+ azalea_buf::McBufWritable::write_into(self, buf)
}
pub fn read(
buf: &mut impl std::io::Read,
) -> Result<#state, String> {
- use crate::mc_buf::McBufReadable;
+ use azalea_buf::McBufReadable;
Ok(Self::read_into(buf)?.get())
}
}
diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs
index d7f75f00..b1d1a9c4 100755
--- a/azalea-protocol/src/lib.rs
+++ b/azalea-protocol/src/lib.rs
@@ -1,13 +1,11 @@
//! This lib is responsible for parsing Minecraft packets.
-#![feature(min_specialization)]
-#![feature(arbitrary_enum_discriminant)]
-
use std::net::IpAddr;
use std::str::FromStr;
+#[cfg(feature = "connecting")]
pub mod connect;
-pub mod mc_buf;
+#[cfg(feature = "packets")]
pub mod packets;
pub mod read;
pub mod resolver;
@@ -43,6 +41,7 @@ impl<'a> TryFrom<&'a str> for ServerAddress {
}
}
+#[cfg(feature = "connecting")]
pub async fn connect(address: ServerAddress) -> Result<(), Box<dyn std::error::Error>> {
let resolved_address = resolver::resolve_address(&address).await;
println!("Resolved address: {:?}", resolved_address);
diff --git a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
index 9ef7e05c..0fc7b817 100644
--- a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs
@@ -1,4 +1,7 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::EntityPos;
+use azalea_entity::Entity;
+use packet_macros::GamePacket;
use uuid::Uuid;
#[derive(Clone, Debug, McBuf, GamePacket)]
@@ -22,3 +25,17 @@ pub struct ClientboundAddEntityPacket {
pub y_vel: i16,
pub z_vel: i16,
}
+
+impl From<&ClientboundAddEntityPacket> for Entity {
+ fn from(p: &ClientboundAddEntityPacket) -> Self {
+ Self::new(
+ p.id,
+ p.uuid,
+ EntityPos {
+ x: p.x,
+ y: p.y,
+ z: p.z,
+ },
+ )
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
index f1947d09..ddadc73f 100644
--- a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs
@@ -1,10 +1,14 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::EntityPos;
+use azalea_entity::Entity;
+use packet_macros::GamePacket;
use uuid::Uuid;
+/// This packet is sent by the server when a player comes into visible range, not when a player joins.
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundAddPlayerPacket {
#[var]
- pub id: i32,
+ pub id: u32,
pub uuid: Uuid,
pub x: f64,
pub y: f64,
@@ -12,3 +16,17 @@ pub struct ClientboundAddPlayerPacket {
pub x_rot: i8,
pub y_rot: i8,
}
+
+impl From<&ClientboundAddPlayerPacket> for Entity {
+ fn from(p: &ClientboundAddPlayerPacket) -> Self {
+ Self::new(
+ p.id,
+ p.uuid,
+ EntityPos {
+ x: p.x,
+ y: p.y,
+ z: p.z,
+ },
+ )
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_animate_packet.rs b/azalea-protocol/src/packets/game/clientbound_animate_packet.rs
index 0bba1a25..e554da0a 100644
--- a/azalea-protocol/src/packets/game/clientbound_animate_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_animate_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundAnimatePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_block_changed_ack_packet.rs b/azalea-protocol/src/packets/game/clientbound_block_changed_ack_packet.rs
index a580440c..2901cb82 100644
--- a/azalea-protocol/src/packets/game/clientbound_block_changed_ack_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_block_changed_ack_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundBlockChangedAckPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_block_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_block_update_packet.rs
index f068cc7d..769270ee 100644
--- a/azalea-protocol/src/packets/game/clientbound_block_update_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_block_update_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_core::BlockPos;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundBlockUpdatePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs
index edda52d9..57cb1f69 100755
--- a/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs
@@ -1,5 +1,6 @@
-use azalea_core::difficulty::Difficulty;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::Difficulty;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundChangeDifficultyPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs b/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs
index 58dd0722..75bf0cf7 100644
--- a/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundChatPreviewPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs b/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs
index d38bbfda..721937a7 100644
--- a/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_core::Slot;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundContainerSetContentPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs
index b9ccbba4..d01e7459 100755
--- a/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::UnsizedByteArray;
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::UnsizedByteArray;
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundCustomPayloadPacket {
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 648ca9e0..bf29a050 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::McBufVarReadable;
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::McBufVarReadable;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
use std::{
hash::Hash,
io::{Read, Write},
@@ -182,10 +183,10 @@ impl McBufReadable for BrigadierParser {
41 => Ok(BrigadierParser::Dimension),
42 => Ok(BrigadierParser::Time),
43 => Ok(BrigadierParser::ResourceOrTag {
- registry_key: buf.read_resource_location()?,
+ registry_key: ResourceLocation::read_into(buf)?,
}),
44 => Ok(BrigadierParser::Resource {
- registry_key: buf.read_resource_location()?,
+ registry_key: ResourceLocation::read_into(buf)?,
}),
45 => Ok(BrigadierParser::TemplateMirror),
46 => Ok(BrigadierParser::TemplateRotation),
@@ -218,7 +219,7 @@ impl McBufReadable for BrigadierNodeStub {
let _name = buf.read_utf()?;
let _parser = BrigadierParser::read_into(buf)?;
let _suggestions_type = if has_suggestions_type {
- Some(buf.read_resource_location()?)
+ Some(ResourceLocation::read_into(buf)?)
} else {
None
};
diff --git a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs
index c030d512..e5f35dc9 100644
--- a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundDisconnectPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs
index d0cc7222..a6f6e38d 100644
--- a/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_entity_event_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
// we can't identify the status in azalea-protocol since they vary depending on the entity
#[derive(Clone, Debug, McBuf, GamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_entity_velocity_packet.rs b/azalea-protocol/src/packets/game/clientbound_entity_velocity_packet.rs
index 07218c4e..7e2fa606 100644
--- a/azalea-protocol/src/packets/game/clientbound_entity_velocity_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_entity_velocity_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundEntityVelocityPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs
index dd5f08f6..f5cb13a1 100644
--- a/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundGameEventPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_initialize_border_packet.rs b/azalea-protocol/src/packets/game/clientbound_initialize_border_packet.rs
index a522eba3..f759cfc9 100644
--- a/azalea-protocol/src/packets/game/clientbound_initialize_border_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_initialize_border_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundInitializeBorderPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_keep_alive_packet.rs b/azalea-protocol/src/packets/game/clientbound_keep_alive_packet.rs
index 18628c86..adaca872 100644
--- a/azalea-protocol/src/packets/game/clientbound_keep_alive_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_keep_alive_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundKeepAlivePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs
index 43bda0b6..f105bd6a 100644
--- a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
use super::clientbound_light_update_packet::ClientboundLightUpdatePacketData;
diff --git a/azalea-protocol/src/packets/game/clientbound_level_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_event_packet.rs
index 70926268..42c5f412 100644
--- a/azalea-protocol/src/packets/game/clientbound_level_event_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_level_event_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_core::BlockPos;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundLevelEventPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_level_particles_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_particles_packet.rs
index 9ed08d8a..df1174b7 100644
--- a/azalea-protocol/src/packets/game/clientbound_level_particles_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_level_particles_packet.rs
@@ -1,5 +1,5 @@
-use crate::mc_buf::McBufVarReadable;
-use crate::mc_buf::{McBufReadable, McBufWritable, ParticleData};
+use azalea_buf::{McBufReadable, McBufVarReadable, McBufWritable};
+use azalea_core::ParticleData;
use packet_macros::GamePacket;
use std::io::{Read, Write};
diff --git a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
index f04987ac..adb6e33b 100644
--- a/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_light_update_packet.rs
@@ -1,5 +1,6 @@
-use crate::mc_buf::BitSet;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::BitSet;
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundLightUpdatePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs
index 6ddc6b5a..6b144bac 100755
--- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs
@@ -1,12 +1,13 @@
-use azalea_core::{game_type::GameType, resource_location::ResourceLocation, GlobalPos};
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::{GameType, GlobalPos, OptionalGameType, ResourceLocation};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundLoginPacket {
pub player_id: u32,
pub hardcore: bool,
pub game_type: GameType,
- pub previous_game_type: Option<GameType>,
+ pub previous_game_type: OptionalGameType,
pub levels: Vec<ResourceLocation>,
pub registry_holder: azalea_nbt::Tag,
pub dimension_type: ResourceLocation,
diff --git a/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs b/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs
index 0fc0104a..cd3e3148 100644
--- a/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_move_entity_pos_packet.rs
@@ -1,11 +1,11 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::PositionDelta8;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundMoveEntityPosPacket {
#[var]
- pub entity_id: i32,
- pub xa: i16,
- pub ya: i16,
- pub za: i16,
+ pub entity_id: u32,
+ pub delta: PositionDelta8,
pub on_ground: bool,
}
diff --git a/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs b/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs
index 5fde1b93..e3422ac0 100644
--- a/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_move_entity_posrot_packet.rs
@@ -1,12 +1,13 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::PositionDelta8;
+use packet_macros::GamePacket;
+/// This packet is sent by the server when an entity moves less then 8 blocks.
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundMoveEntityPosRotPacket {
#[var]
- pub entity_id: i32,
- pub xa: i16,
- pub ya: i16,
- pub za: i16,
+ pub entity_id: u32,
+ pub delta: PositionDelta8,
pub y_rot: i8,
pub x_rot: i8,
pub on_ground: bool,
diff --git a/azalea-protocol/src/packets/game/clientbound_move_entity_rot_packet.rs b/azalea-protocol/src/packets/game/clientbound_move_entity_rot_packet.rs
index c8d0170b..85515628 100644
--- a/azalea-protocol/src/packets/game/clientbound_move_entity_rot_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_move_entity_rot_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundMoveEntityRotPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs
index c3387f7f..d351ab04 100755
--- a/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs
@@ -1,5 +1,6 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable};
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable};
+use packet_macros::GamePacket;
use std::io::{Read, Write};
#[derive(Clone, Debug, McBuf, GamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
index e6941f25..4aac93f4 100644
--- a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs
@@ -1,6 +1,7 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
use azalea_crypto::SaltSignaturePair;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundPlayerChatPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
index cb17f1f5..f216fde8 100644
--- a/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_info_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
use azalea_chat::component::Component;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
use std::io::{Read, Write};
use uuid::Uuid;
diff --git a/azalea-protocol/src/packets/game/clientbound_player_position_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_position_packet.rs
index 0457269a..29f7c1a3 100644
--- a/azalea-protocol/src/packets/game/clientbound_player_position_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_player_position_packet.rs
@@ -1,5 +1,6 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable};
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable};
+use packet_macros::GamePacket;
use std::io::{Read, Write};
#[derive(Clone, Debug, McBuf, GamePacket)]
@@ -13,7 +14,7 @@ pub struct ClientboundPlayerPositionPacket {
/// Client should confirm this packet with Teleport Confirm containing the
/// same Teleport ID.
#[var]
- pub id: i32,
+ pub id: u32,
pub dismount_vehicle: bool,
}
diff --git a/azalea-protocol/src/packets/game/clientbound_recipe_packet.rs b/azalea-protocol/src/packets/game/clientbound_recipe_packet.rs
index e76504cc..a79bfcf1 100644
--- a/azalea-protocol/src/packets/game/clientbound_recipe_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_recipe_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
use std::io::{Read, Write};
#[derive(Clone, Debug, McBuf, GamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_remove_entities_packet.rs b/azalea-protocol/src/packets/game/clientbound_remove_entities_packet.rs
index 8f51596d..8c76ec15 100644
--- a/azalea-protocol/src/packets/game/clientbound_remove_entities_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_remove_entities_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundRemoveEntitiesPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_rotate_head_packet.rs b/azalea-protocol/src/packets/game/clientbound_rotate_head_packet.rs
index 71b485ae..dc7ec881 100644
--- a/azalea-protocol/src/packets/game/clientbound_rotate_head_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_rotate_head_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundRotateHeadPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_section_blocks_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_section_blocks_update_packet.rs
index 6c429edb..6ee27ed1 100644
--- a/azalea-protocol/src/packets/game/clientbound_section_blocks_update_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_section_blocks_update_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::{McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
use azalea_core::{ChunkSectionBlockPos, ChunkSectionPos};
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
use std::io::{Read, Write};
#[derive(Clone, Debug, McBuf, GamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs
index 4c2d94e6..1dddfc1e 100644
--- a/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundServerDataPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs
index 003b6ccc..a4ecaaab 100755
--- a/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
/// Sent to change the player's slot selection.
#[derive(Clone, Debug, McBuf, GamePacket)]
diff --git a/azalea-protocol/src/packets/game/clientbound_set_chunk_cache_center_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_chunk_cache_center_packet.rs
index 7557c16b..ee86ec9d 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_chunk_cache_center_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_chunk_cache_center_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetChunkCacheCenterPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs
index 7ac42c5c..9e9a7b87 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_core::BlockPos;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetDefaultSpawnPositionPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_display_chat_preview_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_display_chat_preview_packet.rs
index 46a0d582..8e2cfe70 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_display_chat_preview_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_display_chat_preview_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetDisplayChatPreviewPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
index 8a568689..8cbd6f9b 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
@@ -1,9 +1,10 @@
-use crate::mc_buf::EntityMetadata;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_entity::EntityMetadata;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetEntityDataPacket {
#[var]
- pub id: i32,
+ pub id: u32,
pub metadata: EntityMetadata,
}
diff --git a/azalea-protocol/src/packets/game/clientbound_set_entity_link_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_entity_link_packet.rs
index e6e3af67..ec1ee0ec 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_entity_link_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_entity_link_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetEntityLinkPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_equipment_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_equipment_packet.rs
index 3acbd58f..aa352189 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_equipment_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_equipment_packet.rs
@@ -1,7 +1,8 @@
+use azalea_buf::McBuf;
use azalea_core::Slot;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
-use crate::mc_buf::{McBufReadable, McBufWritable};
+use azalea_buf::{McBufReadable, McBufWritable};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetEquipmentPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_experience_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_experience_packet.rs
index bcb6393d..7387f6dc 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_experience_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_experience_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetExperiencePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_health_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_health_packet.rs
index 6c75cf63..b99fe86a 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_health_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_health_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetHealthPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_set_time_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_time_packet.rs
index 4cad0693..ea4437b7 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_time_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_time_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetTimePacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_sound_packet.rs b/azalea-protocol/src/packets/game/clientbound_sound_packet.rs
index fbc5830b..a8607599 100644
--- a/azalea-protocol/src/packets/game/clientbound_sound_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_sound_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSoundPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs
index dfa75a5b..c531fa1e 100644
--- a/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
-use packet_macros::{GamePacket, McBuf};
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSystemChatPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs
index c10db7b9..433b0727 100644
--- a/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundTeleportEntityPacket {
diff --git a/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs
index daa1ac93..43e4d69f 100644
--- a/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs
@@ -1,7 +1,7 @@
-use crate::packets::{McBufReadable, McBufWritable};
+use azalea_buf::{McBuf, McBufReadable, McBufWritable};
use azalea_chat::component::Component;
-use azalea_core::{resource_location::ResourceLocation, Slot};
-use packet_macros::{GamePacket, McBuf};
+use azalea_core::{ResourceLocation, Slot};
+use packet_macros::GamePacket;
use std::{
collections::HashMap,
io::{Read, Write},
diff --git a/azalea-protocol/src/packets/game/clientbound_update_attributes_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_attributes_packet.rs
index d0e7c9ee..3eca2a84 100644
--- a/azalea-protocol/src/packets/game/clientbound_update_attributes_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_update_attributes_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
use std::io::{Read, Write};
use uuid::Uuid;
diff --git a/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs
new file mode 100644
index 00000000..5a446c2f
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_update_mob_effect_packet.rs
@@ -0,0 +1,16 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ClientboundUpdateMobEffectPacket {
+ #[var]
+ pub entity_id: u32,
+ // TODO: have an enum for this
+ #[var]
+ pub effect: u32,
+ pub effect_amplifier: u8,
+ #[var]
+ pub effect_duration_ticks: u32,
+ pub flags: u8,
+ pub factor_data: Option<azalea_nbt::Tag>,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs
index 27839919..05d57695 100644
--- a/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs
@@ -1,9 +1,10 @@
use std::io::{Read, Write};
-use azalea_core::{resource_location::ResourceLocation, Slot};
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_core::{ResourceLocation, Slot};
+use packet_macros::GamePacket;
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundUpdateRecipesPacket {
@@ -129,8 +130,8 @@ impl McBufWritable for Recipe {
impl McBufReadable for Recipe {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let recipe_type = buf.read_resource_location()?;
- let identifier = buf.read_resource_location()?;
+ let recipe_type = ResourceLocation::read_into(buf)?;
+ let identifier = ResourceLocation::read_into(buf)?;
// rust doesn't let us match ResourceLocation so we have to do a big
// if-else chain :(
diff --git a/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs
index 60794f03..cf3bc069 100755
--- a/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs
@@ -1,6 +1,8 @@
-use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
+use std::ops::Deref;
use std::{
collections::HashMap,
io::{Read, Write},
@@ -8,7 +10,7 @@ use std::{
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundUpdateTagsPacket {
- pub tags: HashMap<ResourceLocation, Vec<Tags>>,
+ pub tags: TagMap,
}
#[derive(Clone, Debug)]
@@ -17,12 +19,15 @@ pub struct Tags {
pub elements: Vec<i32>,
}
-impl McBufReadable for HashMap<ResourceLocation, Vec<Tags>> {
+#[derive(Clone, Debug)]
+pub struct TagMap(HashMap<ResourceLocation, Vec<Tags>>);
+
+impl McBufReadable for TagMap {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let length = buf.read_varint()? as usize;
let mut data = HashMap::with_capacity(length);
for _ in 0..length {
- let tag_type = buf.read_resource_location()?;
+ let tag_type = ResourceLocation::read_into(buf)?;
let tags_count = buf.read_varint()? as usize;
let mut tags_vec = Vec::with_capacity(tags_count);
for _ in 0..tags_count {
@@ -31,14 +36,14 @@ impl McBufReadable for HashMap<ResourceLocation, Vec<Tags>> {
}
data.insert(tag_type, tags_vec);
}
- Ok(data)
+ Ok(TagMap(data))
}
}
-impl McBufWritable for HashMap<ResourceLocation, Vec<Tags>> {
+impl McBufWritable for TagMap {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_varint(self.len() as i32)?;
- for (k, v) in self {
+ for (k, v) in &self.0 {
k.write_into(buf)?;
v.write_into(buf)?;
}
@@ -47,7 +52,7 @@ impl McBufWritable for HashMap<ResourceLocation, Vec<Tags>> {
}
impl McBufReadable for Tags {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let name = buf.read_resource_location()?;
+ let name = ResourceLocation::read_into(buf)?;
let elements = buf.read_int_id_list()?;
Ok(Tags { name, elements })
}
@@ -60,3 +65,11 @@ impl McBufWritable for Tags {
Ok(())
}
}
+
+impl Deref for TagMap {
+ type Target = HashMap<ResourceLocation, Vec<Tags>>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs
index 8288bd73..c0c40f75 100755
--- a/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundUpdateViewDistancePacket {
diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs
index c4435636..f407a697 100755
--- a/azalea-protocol/src/packets/game/mod.rs
+++ b/azalea-protocol/src/packets/game/mod.rs
@@ -46,23 +46,34 @@ pub mod clientbound_system_chat_packet;
pub mod clientbound_teleport_entity_packet;
pub mod clientbound_update_advancements_packet;
pub mod clientbound_update_attributes_packet;
+pub mod clientbound_update_mob_effect_packet;
pub mod clientbound_update_recipes_packet;
pub mod clientbound_update_tags_packet;
pub mod clientbound_update_view_distance_packet;
+pub mod serverbound_accept_teleportation_packet;
pub mod serverbound_chat_command_packet;
pub mod serverbound_chat_preview_packet;
pub mod serverbound_custom_payload_packet;
pub mod serverbound_keep_alive_packet;
+pub mod serverbound_move_player_packet_pos;
+pub mod serverbound_move_player_packet_pos_rot;
+pub mod serverbound_move_player_packet_rot;
+pub mod serverbound_move_player_packet_status_only;
use packet_macros::declare_state_packets;
declare_state_packets!(
GamePacket,
Serverbound => {
+ 0x00: serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
0x03: serverbound_chat_command_packet::ServerboundChatCommandPacket,
0x05: serverbound_chat_preview_packet::ServerboundChatPreviewPacket,
0x0c: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x11: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
+ 0x13: serverbound_move_player_packet_pos::ServerboundMovePlayerPacketPos,
+ 0x14: serverbound_move_player_packet_pos_rot::ServerboundMovePlayerPacketPosRot,
+ 0x15: serverbound_move_player_packet_rot::ServerboundMovePlayerPacketRot,
+ 0x16: serverbound_move_player_packet_status_only::ServerboundMovePlayerPacketStatusOnly,
},
Clientbound => {
0x00: clientbound_add_entity_packet::ClientboundAddEntityPacket,
@@ -97,13 +108,13 @@ declare_state_packets!(
0x3c: clientbound_rotate_head_packet::ClientboundRotateHeadPacket,
0x3d: clientbound_section_blocks_update_packet::ClientboundSectionBlocksUpdatePacket,
0x3f: clientbound_server_data_packet::ClientboundServerDataPacket,
- 0x44: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket,
0x47: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket,
0x48: clientbound_set_chunk_cache_center_packet::ClientboundSetChunkCacheCenterPacket,
0x49: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket,
0x4a: clientbound_set_default_spawn_position_packet::ClientboundSetDefaultSpawnPositionPacket,
0x4b: clientbound_set_display_chat_preview_packet::ClientboundSetDisplayChatPreviewPacket,
0x4d: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket,
+ 0x4e: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket,
0x4f: clientbound_entity_velocity_packet::ClientboundEntityVelocityPacket,
0x50: clientbound_set_equipment_packet::ClientboundSetEquipmentPacket,
0x51: clientbound_set_experience_packet::ClientboundSetExperiencePacket,
@@ -114,6 +125,7 @@ declare_state_packets!(
0x63: clientbound_teleport_entity_packet::ClientboundTeleportEntityPacket,
0x64: clientbound_update_advancements_packet::ClientboundUpdateAdvancementsPacket,
0x65: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket,
+ 0x66: clientbound_update_mob_effect_packet::ClientboundUpdateMobEffectPacket,
0x67: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket,
0x68: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
}
diff --git a/azalea-protocol/src/packets/game/serverbound_accept_teleportation_packet.rs b/azalea-protocol/src/packets/game/serverbound_accept_teleportation_packet.rs
new file mode 100644
index 00000000..98a9f728
--- /dev/null
+++ b/azalea-protocol/src/packets/game/serverbound_accept_teleportation_packet.rs
@@ -0,0 +1,8 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ServerboundAcceptTeleportationPacket {
+ #[var]
+ pub id: u32,
+}
diff --git a/azalea-protocol/src/packets/game/serverbound_chat_command_packet.rs b/azalea-protocol/src/packets/game/serverbound_chat_command_packet.rs
index 9ae0b79f..6371463b 100644
--- a/azalea-protocol/src/packets/game/serverbound_chat_command_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_chat_command_packet.rs
@@ -1,6 +1,7 @@
use std::collections::HashMap;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundChatCommandPacket {
diff --git a/azalea-protocol/src/packets/game/serverbound_chat_preview_packet.rs b/azalea-protocol/src/packets/game/serverbound_chat_preview_packet.rs
index 60535f69..32711d45 100644
--- a/azalea-protocol/src/packets/game/serverbound_chat_preview_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_chat_preview_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundChatPreviewPacket {
diff --git a/azalea-protocol/src/packets/game/serverbound_custom_payload_packet.rs b/azalea-protocol/src/packets/game/serverbound_custom_payload_packet.rs
index bef25b59..eecb920b 100644
--- a/azalea-protocol/src/packets/game/serverbound_custom_payload_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_custom_payload_packet.rs
@@ -1,6 +1,7 @@
-use crate::mc_buf::UnsizedByteArray;
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use azalea_buf::UnsizedByteArray;
+use azalea_core::ResourceLocation;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundCustomPayloadPacket {
diff --git a/azalea-protocol/src/packets/game/serverbound_keep_alive_packet.rs b/azalea-protocol/src/packets/game/serverbound_keep_alive_packet.rs
index c430499e..1edc5a52 100644
--- a/azalea-protocol/src/packets/game/serverbound_keep_alive_packet.rs
+++ b/azalea-protocol/src/packets/game/serverbound_keep_alive_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{GamePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundKeepAlivePacket {
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos.rs b/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos.rs
new file mode 100644
index 00000000..23f4050e
--- /dev/null
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos.rs
@@ -0,0 +1,10 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ServerboundMovePlayerPacketPos {
+ pub x: f64,
+ pub y: f64,
+ pub z: f64,
+ pub on_ground: bool,
+}
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos_rot.rs b/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos_rot.rs
new file mode 100644
index 00000000..9416461c
--- /dev/null
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_packet_pos_rot.rs
@@ -0,0 +1,12 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ServerboundMovePlayerPacketPosRot {
+ pub x: f64,
+ pub y: f64,
+ pub z: f64,
+ pub y_rot: f32,
+ pub x_rot: f32,
+ pub on_ground: bool,
+}
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_packet_rot.rs b/azalea-protocol/src/packets/game/serverbound_move_player_packet_rot.rs
new file mode 100644
index 00000000..0952483a
--- /dev/null
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_packet_rot.rs
@@ -0,0 +1,9 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ServerboundMovePlayerPacketRot {
+ pub y_rot: f32,
+ pub x_rot: f32,
+ pub on_ground: bool,
+}
diff --git a/azalea-protocol/src/packets/game/serverbound_move_player_packet_status_only.rs b/azalea-protocol/src/packets/game/serverbound_move_player_packet_status_only.rs
new file mode 100644
index 00000000..fb765c27
--- /dev/null
+++ b/azalea-protocol/src/packets/game/serverbound_move_player_packet_status_only.rs
@@ -0,0 +1,7 @@
+use azalea_buf::McBuf;
+use packet_macros::GamePacket;
+
+#[derive(Clone, Debug, McBuf, GamePacket)]
+pub struct ServerboundMovePlayerPacketStatusOnly {
+ pub on_ground: bool,
+}
diff --git a/azalea-protocol/src/packets/handshake/client_intention_packet.rs b/azalea-protocol/src/packets/handshake/client_intention_packet.rs
index 410c11ab..5f1987e2 100755
--- a/azalea-protocol/src/packets/handshake/client_intention_packet.rs
+++ b/azalea-protocol/src/packets/handshake/client_intention_packet.rs
@@ -1,5 +1,6 @@
use crate::packets::ConnectionProtocol;
-use packet_macros::{HandshakePacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::HandshakePacket;
use std::hash::Hash;
#[derive(Hash, Clone, Debug, McBuf, HandshakePacket)]
diff --git a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
index 1b1da87a..05310fb0 100755
--- a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs
@@ -1,6 +1,6 @@
-use crate::mc_buf::UnsizedByteArray;
-use azalea_core::resource_location::ResourceLocation;
-use packet_macros::{LoginPacket, McBuf};
+use azalea_buf::{McBuf, UnsizedByteArray};
+use azalea_core::ResourceLocation;
+use packet_macros::LoginPacket;
use std::hash::Hash;
#[derive(Hash, Clone, Debug, McBuf, LoginPacket)]
diff --git a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
index dd11f7f7..bd2edf38 100755
--- a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs
@@ -1,10 +1,8 @@
use std::io::{Read, Write};
use super::LoginPacket;
-use crate::mc_buf::McBufReadable;
-use crate::mc_buf::{Readable, Writable};
use azalea_auth::game_profile::GameProfile;
-use azalea_core::serializable_uuid::SerializableUuid;
+use azalea_buf::{McBufReadable, Readable, SerializableUuid, Writable};
use uuid::Uuid;
#[derive(Clone, Debug)]
diff --git a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
index 58d48ffe..f3724c18 100755
--- a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs
@@ -1,5 +1,5 @@
+use azalea_buf::McBuf;
use packet_macros::LoginPacket;
-use packet_macros::McBuf;
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ClientboundHelloPacket {
diff --git a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
index b1323f50..19b2d58e 100755
--- a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs
@@ -3,7 +3,7 @@ use std::{
io::{Read, Write},
};
-use crate::mc_buf::{Readable, Writable};
+use azalea_buf::{Readable, Writable};
use super::LoginPacket;
diff --git a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs
index 9ab09e3b..acc68c82 100644
--- a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs
+++ b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs
@@ -1,5 +1,6 @@
+use azalea_buf::McBuf;
use azalea_chat::component::Component;
-use packet_macros::{LoginPacket, McBuf};
+use packet_macros::LoginPacket;
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ClientboundLoginDisconnectPacket {
diff --git a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
index bed93f04..57a3e4d8 100755
--- a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
+++ b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{LoginPacket, McBuf};
+use azalea_buf::McBuf;
+use packet_macros::LoginPacket;
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ServerboundHelloPacket {
diff --git a/azalea-protocol/src/packets/login/serverbound_key_packet.rs b/azalea-protocol/src/packets/login/serverbound_key_packet.rs
index d57b122a..12b7c4ec 100644
--- a/azalea-protocol/src/packets/login/serverbound_key_packet.rs
+++ b/azalea-protocol/src/packets/login/serverbound_key_packet.rs
@@ -1,8 +1,9 @@
+use azalea_buf::McBuf;
use azalea_crypto::SaltSignaturePair;
-use packet_macros::{LoginPacket, McBuf};
+use packet_macros::LoginPacket;
use std::io::{Read, Write};
-use crate::mc_buf::{McBufReadable, McBufWritable};
+use azalea_buf::{McBufReadable, McBufWritable};
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ServerboundKeyPacket {
diff --git a/azalea-protocol/src/packets/mod.rs b/azalea-protocol/src/packets/mod.rs
index 73a3a842..b9116228 100755
--- a/azalea-protocol/src/packets/mod.rs
+++ b/azalea-protocol/src/packets/mod.rs
@@ -3,13 +3,12 @@ pub mod handshake;
pub mod login;
pub mod status;
+use crate::connect::PacketFlow;
+use azalea_buf::{McBufReadable, McBufWritable, Readable, Writable};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
use std::io::{Read, Write};
-use crate::{
- connect::PacketFlow,
- mc_buf::{McBufReadable, McBufWritable, Readable, Writable},
-};
-
pub const PROTOCOL_VERSION: u32 = 759;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -53,7 +52,7 @@ where
fn write(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;
}
-impl crate::mc_buf::McBufReadable for ConnectionProtocol {
+impl azalea_buf::McBufReadable for ConnectionProtocol {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
ConnectionProtocol::from_i32(buf.read_varint()?)
.ok_or_else(|| "Invalid intention".to_string())
diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
index 2acdb8c2..e7fb4f2b 100755
--- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
+++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs
@@ -4,7 +4,7 @@ use azalea_chat::component::Component;
use serde::Deserialize;
use serde_json::Value;
-use crate::mc_buf::Readable;
+use azalea_buf::Readable;
use super::StatusPacket;
diff --git a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
index 3369e6a9..c19d7795 100755
--- a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
+++ b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs
@@ -1,4 +1,5 @@
-use packet_macros::{McBuf, StatusPacket};
+use azalea_buf::McBuf;
+use packet_macros::StatusPacket;
#[derive(Clone, Debug, McBuf, StatusPacket)]
pub struct ServerboundStatusRequestPacket {}
diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs
index b4ba17ea..e19b5538 100755
--- a/azalea-protocol/src/read.rs
+++ b/azalea-protocol/src/read.rs
@@ -1,8 +1,5 @@
-use crate::{
- connect::PacketFlow,
- mc_buf::{read_varint_async, Readable},
- packets::ProtocolPacket,
-};
+use crate::{connect::PacketFlow, packets::ProtocolPacket};
+use azalea_buf::{read_varint_async, Readable};
use azalea_crypto::Aes128CfbDec;
use flate2::read::ZlibDecoder;
use std::{
diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs
index 9291681c..ae9a6829 100755
--- a/azalea-protocol/src/write.rs
+++ b/azalea-protocol/src/write.rs
@@ -1,5 +1,6 @@
-use crate::{mc_buf::Writable, packets::ProtocolPacket, read::MAXIMUM_UNCOMPRESSED_LENGTH};
+use crate::{packets::ProtocolPacket, read::MAXIMUM_UNCOMPRESSED_LENGTH};
use async_compression::tokio::bufread::ZlibEncoder;
+use azalea_buf::Writable;
use azalea_crypto::Aes128CfbEnc;
use std::fmt::Debug;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml
index 79e6155d..ca890d82 100644
--- a/azalea-world/Cargo.toml
+++ b/azalea-world/Cargo.toml
@@ -6,7 +6,14 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+azalea-block = {path = "../azalea-block"}
+azalea-buf = {path = "../azalea-buf"}
azalea-core = {path = "../azalea-core"}
+azalea-entity = {path = "../azalea-entity"}
azalea-nbt = {path = "../azalea-nbt"}
-azalea-protocol = {path = "../azalea-protocol"}
-azalea-block = {path = "../azalea-block"}
+log = "0.4.17"
+nohash-hasher = "0.2.0"
+uuid = "1.1.2"
+
+[profile.release]
+lto = true
diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs
index 0dc81f9a..fcb3f8f9 100644
--- a/azalea-world/src/bit_storage.rs
+++ b/azalea-world/src/bit_storage.rs
@@ -107,7 +107,7 @@ impl BitStorage {
// assert!(bits >= 1 && bits <= 32);
if let Some(data) = &data {
- if data.len() == 0 {
+ if data.is_empty() {
// TODO: make 0 bit storage actually work
return Ok(BitStorage::default());
}
@@ -162,7 +162,7 @@ impl BitStorage {
// return (int)(var3 >> var5 & this.mask);
assert!(
- index <= self.size - 1,
+ index < self.size,
"Index {} out of bounds (max is {})",
index,
self.size - 1
@@ -181,7 +181,7 @@ impl BitStorage {
// int var6 = (var1 - var3 * this.valuesPerLong) * this.bits;
// this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6;
- assert!(index <= self.size - 1);
+ assert!(index < self.size);
assert!(value <= self.mask);
let cell_index = self.cell_index(index as u64);
let cell = &mut self.data[cell_index as usize];
diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk.rs
new file mode 100644
index 00000000..27ef1ca7
--- /dev/null
+++ b/azalea-world/src/chunk.rs
@@ -0,0 +1,221 @@
+use crate::palette::PalettedContainer;
+use crate::palette::PalettedContainerType;
+use crate::World;
+use azalea_block::BlockState;
+use azalea_buf::{McBufReadable, McBufWritable};
+use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
+use std::fmt::Debug;
+use std::{
+ io::{Read, Write},
+ ops::{Index, IndexMut},
+ sync::{Arc, Mutex},
+};
+
+const SECTION_HEIGHT: u32 = 16;
+
+pub struct ChunkStorage {
+ pub view_center: ChunkPos,
+ chunk_radius: u32,
+ view_range: u32,
+ pub height: u32,
+ pub min_y: i32,
+ // chunks is a list of size chunk_radius * chunk_radius
+ chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
+}
+
+// java moment
+// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case
+fn floor_mod(x: i32, y: u32) -> u32 {
+ if x < 0 {
+ y - ((-x) as u32 % y)
+ } else {
+ x as u32 % y
+ }
+}
+
+impl ChunkStorage {
+ pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
+ let view_range = chunk_radius * 2 + 1;
+ ChunkStorage {
+ view_center: ChunkPos::new(0, 0),
+ chunk_radius,
+ view_range,
+ height,
+ min_y,
+ chunks: vec![None; (view_range * view_range) as usize],
+ }
+ }
+
+ fn get_index(&self, chunk_pos: &ChunkPos) -> usize {
+ (floor_mod(chunk_pos.x, self.view_range) * self.view_range
+ + floor_mod(chunk_pos.z, self.view_range)) as usize
+ }
+
+ pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
+ (chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius
+ && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
+ }
+
+ pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
+ let chunk_pos = ChunkPos::from(pos);
+ println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
+ let chunk = &self[&chunk_pos];
+ chunk
+ .as_ref()
+ .map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y))
+ }
+
+ pub fn replace_with_packet_data(
+ &mut self,
+ pos: &ChunkPos,
+ data: &mut impl Read,
+ ) -> Result<(), String> {
+ if !self.in_range(pos) {
+ println!(
+ "Ignoring chunk since it's not in the view range: {}, {}",
+ pos.x, pos.z
+ );
+ return Ok(());
+ }
+
+ let chunk = Arc::new(Mutex::new(Chunk::read_with_world_height(
+ data,
+ self.height,
+ )?));
+ println!("Loaded chunk {:?}", pos);
+ self[pos] = Some(chunk);
+
+ Ok(())
+ }
+}
+
+impl Index<&ChunkPos> for ChunkStorage {
+ type Output = Option<Arc<Mutex<Chunk>>>;
+
+ fn index(&self, pos: &ChunkPos) -> &Self::Output {
+ &self.chunks[self.get_index(pos)]
+ }
+}
+impl IndexMut<&ChunkPos> for ChunkStorage {
+ fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
+ let index = self.get_index(pos);
+ &mut self.chunks[index]
+ }
+}
+
+#[derive(Debug)]
+pub struct Chunk {
+ pub sections: Vec<Section>,
+}
+
+impl Chunk {
+ pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result<Self, String> {
+ Self::read_with_world_height(buf, data.height())
+ }
+
+ pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result<Self, String> {
+ let section_count = world_height / SECTION_HEIGHT;
+ let mut sections = Vec::with_capacity(section_count as usize);
+ for _ in 0..section_count {
+ let section = Section::read_into(buf)?;
+ sections.push(section);
+ }
+ Ok(Chunk { sections })
+ }
+
+ pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
+ // TODO: check the build height and stuff, this code will be broken if the min build height is 0
+ // (LevelHeightAccessor.getMinSection in vanilla code)
+ assert!(y >= 0);
+ let min_section_index = min_y.div_floor(16);
+ (y.div_floor(16) - min_section_index) as u32
+ }
+
+ pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> BlockState {
+ let section_index = self.section_index(pos.y, min_y);
+ // TODO: make sure the section exists
+ let section = &self.sections[section_index as usize];
+ let chunk_section_pos = ChunkSectionBlockPos::from(pos);
+ section.get(chunk_section_pos)
+ }
+}
+
+impl McBufWritable for Chunk {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ for section in &self.sections {
+ section.write_into(buf)?;
+ }
+ Ok(())
+ }
+}
+
+impl Debug for ChunkStorage {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ChunkStorage")
+ .field("view_center", &self.view_center)
+ .field("chunk_radius", &self.chunk_radius)
+ .field("view_range", &self.view_range)
+ .field("height", &self.height)
+ .field("min_y", &self.min_y)
+ // .field("chunks", &self.chunks)
+ .field("chunks", &format_args!("{} items", self.chunks.len()))
+ .finish()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Section {
+ pub block_count: u16,
+ pub states: PalettedContainer,
+ pub biomes: PalettedContainer,
+}
+
+impl McBufReadable for Section {
+ fn read_into(buf: &mut impl Read) -> Result<Self, String> {
+ let block_count = u16::read_into(buf)?;
+
+ // this is commented out because the vanilla server is wrong
+ // assert!(
+ // block_count <= 16 * 16 * 16,
+ // "A section has more blocks than what should be possible. This is a bug!"
+ // );
+
+ let states = PalettedContainer::read_with_type(buf, &PalettedContainerType::BlockStates)?;
+
+ for i in 0..states.storage.size() {
+ if !BlockState::is_valid_state(states.storage.get(i) as u32) {
+ return Err(format!(
+ "Invalid block state {} (index {}) found in section.",
+ states.storage.get(i),
+ i
+ ));
+ }
+ }
+
+ let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerType::Biomes)?;
+ Ok(Section {
+ block_count,
+ states,
+ biomes,
+ })
+ }
+}
+
+impl McBufWritable for Section {
+ fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
+ self.block_count.write_into(buf)?;
+ self.states.write_into(buf)?;
+ self.biomes.write_into(buf)?;
+ Ok(())
+ }
+}
+
+impl Section {
+ fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
+ // TODO: use the unsafe method and do the check earlier
+ self.states
+ .get(pos.x as usize, pos.y as usize, pos.z as usize)
+ .try_into()
+ .expect("Invalid block state.")
+ }
+}
diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs
new file mode 100644
index 00000000..5219e410
--- /dev/null
+++ b/azalea-world/src/entity.rs
@@ -0,0 +1,145 @@
+use azalea_core::ChunkPos;
+use azalea_entity::Entity;
+use log::warn;
+use nohash_hasher::{IntMap, IntSet};
+use std::collections::HashMap;
+use uuid::Uuid;
+
+#[derive(Debug)]
+pub struct EntityStorage {
+ by_id: IntMap<u32, Entity>,
+ by_chunk: HashMap<ChunkPos, IntSet<u32>>,
+ by_uuid: HashMap<Uuid, u32>,
+}
+
+impl EntityStorage {
+ pub fn new() -> Self {
+ Self {
+ by_id: IntMap::default(),
+ by_chunk: HashMap::default(),
+ by_uuid: HashMap::default(),
+ }
+ }
+
+ /// Add an entity to the storage.
+ #[inline]
+ pub fn insert(&mut self, entity: Entity) {
+ self.by_chunk
+ .entry(ChunkPos::from(entity.pos()))
+ .or_default()
+ .insert(entity.id);
+ self.by_uuid.insert(entity.uuid, entity.id);
+ self.by_id.insert(entity.id, entity);
+ }
+
+ /// Remove an entity from the storage by its id.
+ #[inline]
+ pub fn remove_by_id(&mut self, id: u32) {
+ if let Some(entity) = self.by_id.remove(&id) {
+ let entity_chunk = ChunkPos::from(entity.pos());
+ let entity_uuid = entity.uuid;
+ if self.by_chunk.remove(&entity_chunk).is_none() {
+ warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found.");
+ }
+ if self.by_uuid.remove(&entity_uuid).is_none() {
+ warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found.");
+ }
+ } else {
+ warn!("Tried to remove entity with id {id} but it was not found.")
+ }
+ }
+
+ /// Get a reference to an entity by its id.
+ #[inline]
+ pub fn get_by_id(&self, id: u32) -> Option<&Entity> {
+ self.by_id.get(&id)
+ }
+
+ /// Get a mutable reference to an entity by its id.
+ #[inline]
+ pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> {
+ self.by_id.get_mut(&id)
+ }
+
+ /// Get a reference to an entity by its uuid.
+ #[inline]
+ pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
+ self.by_uuid.get(uuid).and_then(|id| self.by_id.get(id))
+ }
+
+ /// Get a mutable reference to an entity by its uuid.
+ #[inline]
+ pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Entity> {
+ self.by_uuid.get(uuid).and_then(|id| self.by_id.get_mut(id))
+ }
+
+ /// Clear all entities in a chunk.
+ pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
+ if let Some(entities) = self.by_chunk.remove(chunk) {
+ for entity_id in entities {
+ if let Some(entity) = self.by_id.remove(&entity_id) {
+ self.by_uuid.remove(&entity.uuid);
+ } else {
+ warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}.");
+ }
+ }
+ }
+ }
+
+ /// Updates an entity from its old chunk.
+ #[inline]
+ pub fn update_entity_chunk(
+ &mut self,
+ entity_id: u32,
+ old_chunk: &ChunkPos,
+ new_chunk: &ChunkPos,
+ ) {
+ if let Some(entities) = self.by_chunk.get_mut(old_chunk) {
+ entities.remove(&entity_id);
+ }
+ self.by_chunk
+ .entry(*new_chunk)
+ .or_default()
+ .insert(entity_id);
+ }
+
+ /// Get an iterator over all entities.
+ #[inline]
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
+ self.by_id.values()
+ }
+
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ where
+ F: FnMut(&Entity) -> bool,
+ {
+ for entity in self.entities() {
+ if f(entity) {
+ return Some(entity);
+ }
+ }
+ None
+ }
+
+ pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity>
+ where
+ F: FnMut(&Entity) -> bool,
+ {
+ if let Some(entities) = self.by_chunk.get(chunk) {
+ for entity_id in entities {
+ if let Some(entity) = self.by_id.get(entity_id) {
+ if f(entity) {
+ return Some(entity);
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+impl Default for EntityStorage {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs
index e651e455..3afa4fee 100644
--- a/azalea-world/src/lib.rs
+++ b/azalea-world/src/lib.rs
@@ -1,19 +1,22 @@
#![feature(int_roundings)]
mod bit_storage;
+mod chunk;
+mod entity;
mod palette;
-use crate::palette::PalettedContainerType;
use azalea_block::BlockState;
-use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
-use azalea_protocol::mc_buf::{McBufReadable, McBufWritable};
+use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8};
+use azalea_entity::Entity;
pub use bit_storage::BitStorage;
-use palette::PalettedContainer;
+pub use chunk::{Chunk, ChunkStorage};
+pub use entity::EntityStorage;
use std::{
- io::{Read, Write},
+ io::Read,
ops::{Index, IndexMut},
sync::{Arc, Mutex},
};
+use uuid::Uuid;
#[cfg(test)]
mod tests {
@@ -24,230 +27,123 @@ mod tests {
}
}
-const SECTION_HEIGHT: u32 = 16;
-
+#[derive(Debug)]
pub struct World {
- pub storage: ChunkStorage,
- pub height: u32,
- pub min_y: i32,
+ chunk_storage: ChunkStorage,
+ entity_storage: EntityStorage,
}
impl World {
+ pub fn new(chunk_radius: u32, height: u32, min_y: i32) -> Self {
+ World {
+ chunk_storage: ChunkStorage::new(chunk_radius, height, min_y),
+ entity_storage: EntityStorage::new(),
+ }
+ }
+
pub fn replace_with_packet_data(
&mut self,
pos: &ChunkPos,
data: &mut impl Read,
) -> Result<(), String> {
- if !self.storage.in_range(pos) {
- println!(
- "Ignoring chunk since it's not in the view range: {}, {}",
- pos.x, pos.z
- );
- return Ok(());
- }
- // let existing_chunk = &self.storage[pos];
-
- let chunk = Arc::new(Mutex::new(Chunk::read_with_world(data, self)?));
- println!("Loaded chunk {:?}", pos);
- self.storage[pos] = Some(chunk);
-
- Ok(())
+ self.chunk_storage.replace_with_packet_data(pos, data)
}
pub fn update_view_center(&mut self, pos: &ChunkPos) {
- self.storage.view_center = *pos;
+ self.chunk_storage.view_center = *pos;
}
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
- self.storage.get_block_state(pos, self.min_y)
- }
-}
-impl Index<&ChunkPos> for World {
- type Output = Option<Arc<Mutex<Chunk>>>;
-
- fn index(&self, pos: &ChunkPos) -> &Self::Output {
- &self.storage[pos]
- }
-}
-impl IndexMut<&ChunkPos> for World {
- fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
- &mut self.storage[pos]
- }
-}
-// impl Index<&BlockPos> for World {
-// type Output = Option<Arc<Mutex<Chunk>>>;
-
-// fn index(&self, pos: &BlockPos) -> &Self::Output {
-// let chunk = &self[ChunkPos::from(pos)];
-// // chunk.
-
-// }
-// }
-
-pub struct ChunkStorage {
- view_center: ChunkPos,
- chunk_radius: u32,
- view_range: u32,
- // chunks is a list of size chunk_radius * chunk_radius
- chunks: Vec<Option<Arc<Mutex<Chunk>>>>,
-}
-
-// java moment
-// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case
-fn floor_mod(x: i32, y: u32) -> u32 {
- if x < 0 {
- y - ((-x) as u32 % y)
- } else {
- x as u32 % y
- }
-}
-
-impl ChunkStorage {
- pub fn new(chunk_radius: u32) -> Self {
- let view_range = chunk_radius * 2 + 1;
- ChunkStorage {
- view_center: ChunkPos::new(0, 0),
- chunk_radius,
- view_range,
- chunks: vec![None; (view_range * view_range) as usize],
+ self.chunk_storage.get_block_state(pos, self.min_y())
+ }
+
+ pub fn move_entity(&mut self, entity_id: u32, new_pos: EntityPos) -> Result<(), String> {
+ let entity = self
+ .entity_storage
+ .get_mut_by_id(entity_id)
+ .ok_or_else(|| "Moving entity that doesn't exist".to_string())?;
+
+ let old_chunk = ChunkPos::from(entity.pos());
+ let new_chunk = ChunkPos::from(&new_pos);
+ // this is fine because we update the chunk below
+ entity.unsafe_move(new_pos);
+ if old_chunk != new_chunk {
+ self.entity_storage
+ .update_entity_chunk(entity_id, &old_chunk, &new_chunk);
}
+ Ok(())
}
- fn get_index(&self, chunk_pos: &ChunkPos) -> usize {
- (floor_mod(chunk_pos.x, self.view_range) * self.view_range
- + floor_mod(chunk_pos.z, self.view_range)) as usize
+ pub fn move_entity_with_delta(
+ &mut self,
+ entity_id: u32,
+ delta: &PositionDelta8,
+ ) -> Result<(), String> {
+ let entity = self
+ .entity_storage
+ .get_mut_by_id(entity_id)
+ .ok_or_else(|| "Moving entity that doesn't exist".to_string())?;
+ let new_pos = entity.pos().with_delta(delta);
+
+ let old_chunk = ChunkPos::from(entity.pos());
+ let new_chunk = ChunkPos::from(&new_pos);
+ // this is fine because we update the chunk below
+
+ entity.unsafe_move(new_pos);
+ if old_chunk != new_chunk {
+ self.entity_storage
+ .update_entity_chunk(entity_id, &old_chunk, &new_chunk);
+ }
+ Ok(())
}
- pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
- (chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius
- && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
+ pub fn add_entity(&mut self, entity: Entity) {
+ self.entity_storage.insert(entity);
}
- pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
- let chunk_pos = ChunkPos::from(pos);
- println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
- let chunk = &self[&chunk_pos];
- match chunk {
- Some(chunk) => Some(chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)),
- None => None,
- }
+ pub fn height(&self) -> u32 {
+ self.chunk_storage.height
}
-}
-
-impl Index<&ChunkPos> for ChunkStorage {
- type Output = Option<Arc<Mutex<Chunk>>>;
- fn index(&self, pos: &ChunkPos) -> &Self::Output {
- &self.chunks[self.get_index(pos)]
+ pub fn min_y(&self) -> i32 {
+ self.chunk_storage.min_y
}
-}
-impl IndexMut<&ChunkPos> for ChunkStorage {
- fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
- let index = self.get_index(pos);
- &mut self.chunks[index]
- }
-}
-#[derive(Debug)]
-pub struct Chunk {
- pub sections: Vec<Section>,
-}
-
-impl Chunk {
- pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result<Self, String> {
- Self::read_with_world_height(buf, data.height)
+ pub fn entity_by_id(&self, id: u32) -> Option<&Entity> {
+ self.entity_storage.get_by_id(id)
}
- pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result<Self, String> {
- let section_count = world_height / SECTION_HEIGHT;
- let mut sections = Vec::with_capacity(section_count as usize);
- for _ in 0..section_count {
- let section = Section::read_into(buf)?;
- sections.push(section);
- }
- Ok(Chunk { sections })
+ pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> {
+ self.entity_storage.get_mut_by_id(id)
}
- pub fn section_index(&self, y: i32, min_y: i32) -> u32 {
- // TODO: check the build height and stuff, this code will be broken if the min build height is 0
- // (LevelHeightAccessor.getMinSection in vanilla code)
- assert!(y >= 0);
- let min_section_index = min_y.div_floor(16);
- (y.div_floor(16) - min_section_index) as u32
+ pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> {
+ self.entity_storage.get_by_uuid(uuid)
}
- pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> BlockState {
- let section_index = self.section_index(pos.y, min_y);
- // TODO: make sure the section exists
- let section = &self.sections[section_index as usize];
- let chunk_section_pos = ChunkSectionBlockPos::from(pos);
- let block_state = section.get(chunk_section_pos);
- block_state
+ /// Get an iterator over all entities.
+ #[inline]
+ pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> {
+ self.entity_storage.entities()
}
-}
-impl McBufWritable for Chunk {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- for section in &self.sections {
- section.write_into(buf)?;
- }
- Ok(())
+ pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity>
+ where
+ F: FnMut(&Entity) -> bool,
+ {
+ self.entity_storage.find_one_entity(|entity| f(entity))
}
}
-#[derive(Clone, Debug)]
-pub struct Section {
- pub block_count: u16,
- pub states: PalettedContainer,
- pub biomes: PalettedContainer,
-}
-
-impl McBufReadable for Section {
- fn read_into(buf: &mut impl Read) -> Result<Self, String> {
- let block_count = u16::read_into(buf)?;
-
- // this is commented out because the vanilla server is wrong
- // assert!(
- // block_count <= 16 * 16 * 16,
- // "A section has more blocks than what should be possible. This is a bug!"
- // );
-
- let states = PalettedContainer::read_with_type(buf, &PalettedContainerType::BlockStates)?;
-
- for i in 0..states.storage.size() {
- if !BlockState::is_valid_state(states.storage.get(i) as u32) {
- return Err(format!(
- "Invalid block state {} (index {}) found in section.",
- states.storage.get(i),
- i
- ));
- }
- }
-
- let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerType::Biomes)?;
- Ok(Section {
- block_count,
- states,
- biomes,
- })
- }
-}
+impl Index<&ChunkPos> for World {
+ type Output = Option<Arc<Mutex<Chunk>>>;
-impl McBufWritable for Section {
- fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
- self.block_count.write_into(buf)?;
- self.states.write_into(buf)?;
- self.biomes.write_into(buf)?;
- Ok(())
+ fn index(&self, pos: &ChunkPos) -> &Self::Output {
+ &self.chunk_storage[pos]
}
}
-
-impl Section {
- fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
- // TODO: use the unsafe method and do the check earlier
- self.states
- .get(pos.x as usize, pos.y as usize, pos.z as usize)
- .try_into()
- .expect("Invalid block state.")
+impl IndexMut<&ChunkPos> for World {
+ fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output {
+ &mut self.chunk_storage[pos]
}
}
diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs
index be6f022c..4779dc4b 100644
--- a/azalea-world/src/palette.rs
+++ b/azalea-world/src/palette.rs
@@ -1,4 +1,4 @@
-use azalea_protocol::mc_buf::{McBufReadable, McBufVarReadable, McBufWritable, Readable, Writable};
+use azalea_buf::{McBufReadable, McBufVarReadable, McBufWritable, Readable, Writable};
use std::io::{Read, Write};
use crate::BitStorage;
diff --git a/bot/Cargo.toml b/bot/Cargo.toml
index 1a6a44a3..974d6f61 100755
--- a/bot/Cargo.toml
+++ b/bot/Cargo.toml
@@ -10,3 +10,4 @@ azalea-client = {path = "../azalea-client"}
azalea-core = {path = "../azalea-core"}
azalea-protocol = {path = "../azalea-protocol"}
tokio = "1.19.2"
+uuid = "1.1.2"
diff --git a/bot/src/main.rs b/bot/src/main.rs
index e2e87456..2976920b 100644
--- a/bot/src/main.rs
+++ b/bot/src/main.rs
@@ -1,12 +1,12 @@
use azalea_client::{Account, Event};
-use azalea_core::BlockPos;
+use azalea_core::PositionXYZ;
#[tokio::main]
-async fn main() {
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello, world!");
// let address = "95.111.249.143:10000";
- let address = "localhost:61146";
+ let address = "localhost:65399";
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await
// .unwrap();
@@ -16,25 +16,47 @@ async fn main() {
let mut client = account.join(&address.try_into().unwrap()).await.unwrap();
println!("connected");
- while let Some(e) = client.next().await {
+ while let Some(e) = &client.next().await {
match e {
// TODO: have a "loaded" or "ready" event that fires when all chunks are loaded
Event::Login => {}
- Event::Chat(p) => {
- let state = client.state.lock().await;
- let world = state.world.as_ref().unwrap();
- // println!("{:?}", state.player.entity);
- // world.get_block_state(state.player.entity.pos);
- // println!("{}", p.message.to_ansi(None));
- // if p.message.to_ansi(None) == "<py5> ok" {
- // let state = client.state.lock().await;
- // let world = state.world.as_ref().unwrap();
- // let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
- // println!("block state: {:?}", c);
- // }
+ // Event::GameTick => {
+ // let world = client.world();
+ // if let Some(b) = world.find_one_entity(|e| {
+ // e.uuid == uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd")
+ // }) {
+ // // let world = state.world.as_ref().unwrap();
+ // // world.
+ // println!("{:?}", b);
+ // }
+ // // world.get_block_state(state.player.entity.pos);
+ // // println!("{}", p.message.to_ansi(None));
+ // // if p.message.to_ansi(None) == "<py5> ok" {
+ // // let state = client.state.lock();
+ // // let world = state.world.as_ref().unwrap();
+ // // let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
+ // // println!("block state: {:?}", c);
+ // // }
+ // }
+ Event::Chat(msg) => {
+ let new_pos = {
+ let state_lock = client.state.lock().unwrap();
+ let world = state_lock.world.as_ref().unwrap();
+ let player = &state_lock.player;
+ let entity = player
+ .entity(&world)
+ .expect("Player entity is not in world");
+ entity.pos().add_y(0.5)
+ };
+
+ println!("{:?}", new_pos);
+ client.move_to(new_pos).await.unwrap();
}
+ _ => {}
}
}
println!("done");
+
+ Ok(())
}
diff --git a/codegen/lib/code/packet.py b/codegen/lib/code/packet.py
index 2aabf39a..de8254e7 100644
--- a/codegen/lib/code/packet.py
+++ b/codegen/lib/code/packet.py
@@ -1,7 +1,9 @@
+from typing import Optional
from lib.code.utils import burger_type_to_rust_type, write_packet_file
from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location
from lib.mappings import Mappings
import os
+import re
def make_packet_mod_rs_line(packet_id: int, packet_class_name: str):
@@ -27,36 +29,22 @@ def generate_packet(burger_packets, mappings: Mappings, target_packet_id, target
uses = set()
generated_packet_code.append(
f'#[derive(Clone, Debug, McBuf, {to_camel_case(state)}Packet)]')
- uses.add(f'packet_macros::{{{to_camel_case(state)}Packet, McBuf}}')
+ uses.add(f'packet_macros::{to_camel_case(state)}Packet')
+ uses.add(f'azalea_buf::McBuf')
- obfuscated_class_name = packet['class'].split('.')[0].split('$')[0]
+ obfuscated_class_name = packet['class'].split('.')[0]
class_name = mappings.get_class(
- obfuscated_class_name).split('.')[-1].split('$')[0]
+ obfuscated_class_name).split('.')[-1]
+ if '$' in class_name:
+ class_name = class_name.replace('$', '')
generated_packet_code.append(
f'pub struct {to_camel_case(class_name)} {{')
for instruction in packet.get('instructions', []):
if instruction['operation'] == 'write':
- obfuscated_field_name = instruction['field']
- if '.' in obfuscated_field_name or ' ' in obfuscated_field_name or '(' in obfuscated_field_name:
- generated_packet_code.append(f'// TODO: {instruction}')
- continue
- field_name = mappings.get_field(
- obfuscated_class_name, obfuscated_field_name)
- if not field_name:
- generated_packet_code.append(
- f'// TODO: unknown field {instruction}')
- continue
-
- field_type = instruction['type']
- field_type_rs, is_var, instruction_uses = burger_type_to_rust_type(
- field_type)
- if is_var:
- generated_packet_code.append('#[var]')
- generated_packet_code.append(
- f'pub {to_snake_case(field_name)}: {field_type_rs},')
- uses.update(instruction_uses)
+ burger_instruction_to_code(
+ instruction, generated_packet_code, mappings, obfuscated_class_name, uses)
else:
generated_packet_code.append(f'// TODO: {instruction}')
continue
@@ -213,6 +201,41 @@ def get_packets(direction: str, state: str):
return packet_ids, packet_class_names
+def burger_instruction_to_code(instruction: dict, generated_packet_code: list[str], mappings: Mappings, obfuscated_class_name: str, uses: set):
+ field_type = instruction['type']
+ field_type_rs, is_var, instruction_uses = burger_type_to_rust_type(
+ field_type)
+
+ obfuscated_field_name = instruction['field']
+ if '.' in obfuscated_field_name or ' ' in obfuscated_field_name or '(' in obfuscated_field_name:
+ field_type_rs, obfuscated_field_name = burger_field_to_type(
+ obfuscated_field_name)
+ if not field_type_rs:
+ generated_packet_code.append(f'// TODO: {instruction}')
+ return
+ field_name = mappings.get_field(
+ obfuscated_class_name, obfuscated_field_name) or mappings.get_field(
+ obfuscated_class_name.split('$')[0], obfuscated_field_name)
+ if not field_name:
+ generated_packet_code.append(
+ f'// TODO: unknown field {instruction}')
+ return
+
+ if is_var:
+ generated_packet_code.append('#[var]')
+ generated_packet_code.append(
+ f'pub {to_snake_case(field_name)}: {field_type_rs},')
+ uses.update(instruction_uses)
+
+
+def burger_field_to_type(field) -> tuple[Optional[str], str]:
+ # match `(x) ? 1 : 0`
+ match = re.match(r'\((.*)\) \? 1 : 0', field)
+ if match:
+ return ('bool', match.group(1))
+ return None, field
+
+
def change_packet_ids(id_map: dict[int, int], direction: str, state: str):
existing_packet_ids, existing_packet_class_names = get_packets(
direction, state)
diff --git a/examples/craft_dig_straight_down.rs b/examples/craft_dig_straight_down.rs
index 1d1a89f6..54bf0015 100644
--- a/examples/craft_dig_straight_down.rs
+++ b/examples/craft_dig_straight_down.rs
@@ -8,12 +8,12 @@ bot.join("localhost".try_into().unwrap()).await.unwrap();
loop {
match bot.next().await {
Event::Message(m) {
- if m.username == bot.username { return };
+ if m.username == bot.player.username { return };
if m.message = "go" {
bot.goto_goal(
pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))
).await;
- let chest = bot.open_chest(&bot.world.find_one_block(|b| b.id == "minecraft:chest")).await.unwrap();
+ let chest = bot.open_container(&bot.world.find_one_block(|b| b.id == "minecraft:chest")).await.unwrap();
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks").await;
// when rust adds async drop this won't be necessary
chest.close().await;
@@ -25,7 +25,7 @@ loop {
bot.hold(&pickaxe);
loop {
- if let Err(e) = bot.dig(bot.feet_coords().down(1)).await {
+ if let Err(e) = bot.dig(bot.entity.feet_pos().down(1)).await {
println!("{:?}", e);
break;
}
diff --git a/examples/pvp.rs b/examples/pvp.rs
new file mode 100644
index 00000000..5febdd45
--- /dev/null
+++ b/examples/pvp.rs
@@ -0,0 +1,33 @@
+use azalea::{Account, Accounts, Event, pathfinder};
+
+#[tokio::main]
+async fn main() {
+ let accounts = Accounts::new();
+ for i in 0..10 {
+ accounts.add(Account::offline(format!("bot{}", i)));
+ }
+
+ let bots = accounts.join("localhost".try_into().unwrap()).await.unwrap();
+
+ match bots.next().await {
+ Event::Tick {
+ // choose an arbitrary player within render distance to target
+ if let Some(target) = bots.world.find_one_entity(|e| e.id == "minecraft:player") {
+ for bot in bots {
+ bot.tick_goto_goal(
+ pathfinder::Goals::Reach(target.bounding_box)
+ );
+ // if target.bounding_box.distance(bot.eyes) < bot.reach_distance() {
+ if bot.entity.can_reach(target.bounding_box) {
+ bot.swing();
+ }
+ if !h.using_held_item() && bot.state.lock().hunger <= 17 {
+ bot.hold(azalea::ItemGroup::Food);
+ tokio::task::spawn(bot.use_held_item());
+ }
+ }
+ }
+ },
+ _ => {}
+ }
+}