mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Merge branch 'main' into 1.19.1
This commit is contained in:
commit
c46eb556e2
131 changed files with 2942 additions and 1799 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
@ -34,9 +34,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "0.3.2"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
|
||||
checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -89,10 +89,21 @@ dependencies = [
|
|||
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",
|
||||
|
@ -132,6 +148,17 @@ dependencies = [
|
|||
"sha-1",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -145,6 +172,7 @@ dependencies = [
|
|||
name = "azalea-nbt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"azalea-buf",
|
||||
"byteorder",
|
||||
"criterion",
|
||||
"flate2",
|
||||
|
@ -160,15 +188,15 @@ dependencies = [
|
|||
"async-recursion",
|
||||
"azalea-auth",
|
||||
"azalea-brigadier",
|
||||
"azalea-buf",
|
||||
"azalea-chat",
|
||||
"azalea-core",
|
||||
"azalea-crypto",
|
||||
"azalea-entity",
|
||||
"azalea-nbt",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"flate2",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"packet-macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -184,9 +212,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]]
|
||||
|
@ -221,6 +253,7 @@ dependencies = [
|
|||
"azalea-core",
|
||||
"azalea-protocol",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -235,6 +268,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "buf-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.10.0"
|
||||
|
@ -763,6 +805,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -872,6 +920,15 @@ version = "11.1.3"
|
|||
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"
|
||||
|
@ -1220,6 +1277,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -1420,9 +1483,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
|
|
@ -12,6 +12,8 @@ members = [
|
|||
"azalea-world",
|
||||
"azalea-language",
|
||||
"azalea-block",
|
||||
"azalea-entity",
|
||||
"azalea-buf",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
@ -26,3 +28,5 @@ opt-level = 3
|
|||
opt-level = 3
|
||||
[profile.dev.package.aes]
|
||||
opt-level = 3
|
||||
[profile.dev.package.flate2]
|
||||
opt-level = 3
|
||||
|
|
|
@ -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 = "^0.8.2"
|
||||
uuid = "1.1.2"
|
||||
|
|
12
azalea-buf/Cargo.toml
Normal file
12
azalea-buf/Cargo.toml
Normal file
|
@ -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"
|
3
azalea-buf/README.md
Normal file
3
azalea-buf/README.md
Normal file
|
@ -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.
|
13
azalea-buf/buf-macros/Cargo.toml
Normal file
13
azalea-buf/buf-macros/Cargo.toml
Normal file
|
@ -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"
|
177
azalea-buf/buf-macros/src/lib.rs
Normal file
177
azalea-buf/buf-macros/src/lib.rs
Normal file
|
@ -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_from(buf)?;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let #field_name = azalea_buf::McBufReadable::read_from(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_from(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_from(buf: &mut impl std::io::Read) -> Result<Self, String>
|
||||
{
|
||||
let id = azalea_buf::McBufVarReadable::var_read_from(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()
|
||||
}
|
56
azalea-buf/src/definitions.rs
Normal file
56
azalea-buf/src/definitions.rs
Normal file
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
data: Vec::<u64>::read_from(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for BitSet {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
self.data.write_into(buf)
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
@ -180,33 +181,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();
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
@ -235,31 +201,31 @@ pub trait McBufReadable
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String>;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String>;
|
||||
}
|
||||
|
||||
pub trait McBufVarReadable
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String>;
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String>;
|
||||
}
|
||||
|
||||
impl McBufReadable for i32 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Readable::read_int(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufVarReadable for i32 {
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_varint()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufVarReadable for i64 {
|
||||
// fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L54
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let mut buffer = [0];
|
||||
let mut ans = 0;
|
||||
for i in 0..8 {
|
||||
|
@ -274,252 +240,141 @@ impl McBufVarReadable for i64 {
|
|||
}
|
||||
}
|
||||
impl McBufVarReadable for u64 {
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
i64::var_read_into(buf).map(|i| i as u64)
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
i64::var_read_from(buf).map(|i| i as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for UnsizedByteArray {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Ok(buf.read_bytes()?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: McBufReadable + Send> McBufReadable for Vec<T> {
|
||||
default fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
default fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let length = buf.read_varint()? as usize;
|
||||
let mut contents = Vec::with_capacity(length);
|
||||
for _ in 0..length {
|
||||
contents.push(T::read_into(buf)?);
|
||||
contents.push(T::read_from(buf)?);
|
||||
}
|
||||
Ok(contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: McBufReadable + Send + Eq + Hash, V: McBufReadable + Send> McBufReadable for HashMap<K, V> {
|
||||
default fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
default fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let length = buf.read_varint()? as usize;
|
||||
let mut contents = HashMap::with_capacity(length);
|
||||
for _ in 0..length {
|
||||
contents.insert(K::read_into(buf)?, V::read_into(buf)?);
|
||||
contents.insert(K::read_from(buf)?, V::read_from(buf)?);
|
||||
}
|
||||
Ok(contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for Vec<u8> {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_byte_array()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for String {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_utf()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Readable::read_int(buf).map(|i| i as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufVarReadable for u32 {
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_varint().map(|i| i as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for u16 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_short().map(|i| i as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for i16 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_short()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufVarReadable for u16 {
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_varint().map(|i| i as u16)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: McBufVarReadable> McBufVarReadable for Vec<T> {
|
||||
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn var_read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let length = buf.read_varint()? as usize;
|
||||
let mut contents = Vec::with_capacity(length);
|
||||
for _ in 0..length {
|
||||
contents.push(T::var_read_into(buf)?);
|
||||
contents.push(T::var_read_from(buf)?);
|
||||
}
|
||||
Ok(contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for i64 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_long()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for u64 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
i64::read_into(buf).map(|i| i as u64)
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
i64::read_from(buf).map(|i| i as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for bool {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_boolean()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for u8 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_byte()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for i8 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_byte().map(|i| i as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for f32 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_float()
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for f64 {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
buf.read_double()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
default fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let present = buf.read_boolean()?;
|
||||
Ok(if present {
|
||||
Some(T::read_into(buf)?)
|
||||
Some(T::read_from(buf)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
27
azalea-core/src/serializable_uuid.rs → azalea-buf/src/serializable_uuid.rs
Executable file → Normal file
27
azalea-core/src/serializable_uuid.rs → azalea-buf/src/serializable_uuid.rs
Executable file → Normal file
|
@ -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_from(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::*;
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let string = String::read_from(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_from(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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
tokio = {version = "1.18.0", features = ["sync"]}
|
||||
owning_ref = "0.4.1"
|
||||
tokio = {version = "1.19.2", features = ["sync"]}
|
||||
uuid = "1.1.2"
|
||||
|
|
20
azalea-client/src/account.rs
Normal file
20
azalea-client/src/account.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
675
azalea-client/src/client.rs
Normal file
675
azalea-client/src/client.rs
Normal file
|
@ -0,0 +1,675 @@
|
|||
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::Dimension;
|
||||
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<Dimension>,
|
||||
}
|
||||
|
||||
#[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(Dimension::new(16, height, min_y));
|
||||
|
||||
let entity = Entity::new(p.player_id, game_profile.uuid, EntityPos::default());
|
||||
state_lock
|
||||
.world
|
||||
.as_mut()
|
||||
.expect(
|
||||
"Dimension 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("Dimension 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("Dimension 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("Dimension 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 `Dimension` 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>, Dimension> {
|
||||
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("Dimension 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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)]
|
||||
|
|
45
azalea-client/src/movement.rs
Normal file
45
azalea-client/src/movement.rs
Normal file
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,27 @@
|
|||
use crate::Entity;
|
||||
use azalea_entity::Entity;
|
||||
use azalea_world::Dimension;
|
||||
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 Dimension) -> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = "^0.8.2"
|
||||
uuid = "^1.1.2"
|
||||
|
|
68
azalea-core/src/delta.rs
Normal file
68
azalea-core/src/delta.rs
Normal file
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Ok(Difficulty::by_id(u8::read_from(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::*;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
use azalea_buf::McBuf;
|
||||
|
||||
#[derive(Clone, Copy, Debug, McBuf)]
|
||||
pub enum Direction {
|
||||
Down = 0,
|
||||
Up = 1,
|
||||
|
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
GameType::from_id(u8::read_from(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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
GameType::from_optional_id(i8::read_from(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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 {
|
||||
|
@ -337,8 +157,8 @@ impl ParticleData {
|
|||
Ok(match id {
|
||||
0 => ParticleData::AmbientEntityEffect,
|
||||
1 => ParticleData::AngryVillager,
|
||||
2 => ParticleData::Block(BlockParticle::read_into(buf)?),
|
||||
3 => ParticleData::BlockMarker(BlockParticle::read_into(buf)?),
|
||||
2 => ParticleData::Block(BlockParticle::read_from(buf)?),
|
||||
3 => ParticleData::BlockMarker(BlockParticle::read_from(buf)?),
|
||||
4 => ParticleData::Bubble,
|
||||
5 => ParticleData::Cloud,
|
||||
6 => ParticleData::Crit,
|
||||
|
@ -349,8 +169,8 @@ impl ParticleData {
|
|||
11 => ParticleData::LandingLava,
|
||||
12 => ParticleData::DrippingWater,
|
||||
13 => ParticleData::FallingWater,
|
||||
14 => ParticleData::Dust(DustParticle::read_into(buf)?),
|
||||
15 => ParticleData::DustColorTransition(DustColorTransitionParticle::read_into(buf)?),
|
||||
14 => ParticleData::Dust(DustParticle::read_from(buf)?),
|
||||
15 => ParticleData::DustColorTransition(DustColorTransitionParticle::read_from(buf)?),
|
||||
16 => ParticleData::Effect,
|
||||
17 => ParticleData::ElderGuardian,
|
||||
18 => ParticleData::EnchantedHit,
|
||||
|
@ -359,7 +179,7 @@ impl ParticleData {
|
|||
21 => ParticleData::EntityEffect,
|
||||
22 => ParticleData::ExplosionEmitter,
|
||||
23 => ParticleData::Explosion,
|
||||
24 => ParticleData::FallingDust(BlockParticle::read_into(buf)?),
|
||||
24 => ParticleData::FallingDust(BlockParticle::read_from(buf)?),
|
||||
25 => ParticleData::Firework,
|
||||
26 => ParticleData::Fishing,
|
||||
27 => ParticleData::Flame,
|
||||
|
@ -370,8 +190,8 @@ impl ParticleData {
|
|||
32 => ParticleData::Composter,
|
||||
33 => ParticleData::Heart,
|
||||
34 => ParticleData::InstantEffect,
|
||||
35 => ParticleData::Item(ItemParticle::read_into(buf)?),
|
||||
36 => ParticleData::Vibration(VibrationParticle::read_into(buf)?),
|
||||
35 => ParticleData::Item(ItemParticle::read_from(buf)?),
|
||||
36 => ParticleData::Vibration(VibrationParticle::read_from(buf)?),
|
||||
37 => ParticleData::ItemSlime,
|
||||
38 => ParticleData::ItemSnowball,
|
||||
39 => ParticleData::LargeSmoke,
|
||||
|
@ -429,8 +249,8 @@ impl ParticleData {
|
|||
}
|
||||
|
||||
impl McBufReadable for ParticleData {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let id = u32::var_read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let id = u32::var_read_from(buf)?;
|
||||
ParticleData::read_from_particle_id(buf, id)
|
||||
}
|
||||
}
|
|
@ -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,6 +85,83 @@ impl ChunkSectionPos {
|
|||
ChunkSectionPos { x, y, z }
|
||||
}
|
||||
}
|
||||
/// The coordinates of a block inside a chunk.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct ChunkBlockPos {
|
||||
pub x: u8,
|
||||
pub y: i32,
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl ChunkBlockPos {
|
||||
pub fn new(x: u8, y: i32, z: u8) -> Self {
|
||||
ChunkBlockPos { x, y, z }
|
||||
}
|
||||
}
|
||||
/// The coordinates of a block inside a chunk section.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ChunkSectionBlockPos {
|
||||
/// A number between 0 and 16.
|
||||
pub x: u8,
|
||||
/// A number between 0 and 16.
|
||||
pub y: u8,
|
||||
/// A number between 0 and 16.
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl ChunkSectionBlockPos {
|
||||
pub fn new(x: u8, y: u8, z: u8) -> Self {
|
||||
ChunkSectionBlockPos { x, y, z }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -78,20 +179,6 @@ impl From<ChunkSectionPos> for ChunkPos {
|
|||
}
|
||||
}
|
||||
|
||||
/// The coordinates of a block inside a chunk.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct ChunkBlockPos {
|
||||
pub x: u8,
|
||||
pub y: i32,
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl ChunkBlockPos {
|
||||
pub fn new(x: u8, y: i32, z: u8) -> Self {
|
||||
ChunkBlockPos { x, y, z }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockPos> for ChunkBlockPos {
|
||||
fn from(pos: &BlockPos) -> Self {
|
||||
ChunkBlockPos {
|
||||
|
@ -102,23 +189,6 @@ impl From<&BlockPos> for ChunkBlockPos {
|
|||
}
|
||||
}
|
||||
|
||||
/// The coordinates of a block inside a chunk section.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ChunkSectionBlockPos {
|
||||
/// A number between 0 and 16.
|
||||
pub x: u8,
|
||||
/// A number between 0 and 16.
|
||||
pub y: u8,
|
||||
/// A number between 0 and 16.
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl ChunkSectionBlockPos {
|
||||
pub fn new(x: u8, y: u8, z: u8) -> Self {
|
||||
ChunkSectionBlockPos { x, y, z }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockPos> for ChunkSectionBlockPos {
|
||||
fn from(pos: &BlockPos) -> Self {
|
||||
ChunkSectionBlockPos {
|
||||
|
@ -138,32 +208,80 @@ impl From<&ChunkBlockPos> for 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, Default)]
|
||||
pub struct EntityPos {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
}
|
||||
|
||||
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,
|
||||
x: pos.x.floor() as i32,
|
||||
y: pos.y.floor() as i32,
|
||||
z: pos.z.floor() as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EntityPos> for ChunkPos {
|
||||
fn from(pos: &EntityPos) -> Self {
|
||||
ChunkPos::from(&BlockPos::from(pos))
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for BlockPos {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let val = u64::read_from(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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
Ok(GlobalPos {
|
||||
dimension: ResourceLocation::read_from(buf)?,
|
||||
pos: BlockPos::read_from(buf)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for ChunkSectionPos {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let long = i64::read_from(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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let location_string = String::read_from(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_from(&mut buf).unwrap(),
|
||||
ResourceLocation::new("minecraft:dirt").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let present = bool::read_from(buf)?;
|
||||
if !present {
|
||||
return Ok(Slot::Empty);
|
||||
}
|
||||
let slot = SlotData::read_from(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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"]}
|
||||
|
|
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let salt = u64::read_from(buf)?;
|
||||
let signature = Vec::<u8>::read_from(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(())
|
||||
}
|
||||
}
|
||||
|
|
13
azalea-entity/Cargo.toml
Normal file
13
azalea-entity/Cargo.toml
Normal file
|
@ -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"
|
144
azalea-entity/src/data.rs
Normal file
144
azalea-entity/src/data.rs
Normal file
|
@ -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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let mut metadata = Vec::new();
|
||||
loop {
|
||||
let index = u8::read_from(buf)?;
|
||||
if index == 0xff {
|
||||
break;
|
||||
}
|
||||
let value = EntityDataValue::read_from(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_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let data_type = i32::var_read_from(buf)?;
|
||||
Ok(match data_type {
|
||||
0 => EntityDataValue::Byte(u8::read_from(buf)?),
|
||||
1 => EntityDataValue::Int(i32::var_read_from(buf)?),
|
||||
2 => EntityDataValue::Float(f32::read_from(buf)?),
|
||||
3 => EntityDataValue::String(String::read_from(buf)?),
|
||||
4 => EntityDataValue::Component(Component::read_from(buf)?),
|
||||
5 => EntityDataValue::OptionalComponent(Option::<Component>::read_from(buf)?),
|
||||
6 => EntityDataValue::ItemStack(Slot::read_from(buf)?),
|
||||
7 => EntityDataValue::Boolean(bool::read_from(buf)?),
|
||||
8 => EntityDataValue::Rotations {
|
||||
x: f32::read_from(buf)?,
|
||||
y: f32::read_from(buf)?,
|
||||
z: f32::read_from(buf)?,
|
||||
},
|
||||
9 => EntityDataValue::BlockPos(BlockPos::read_from(buf)?),
|
||||
10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_from(buf)?),
|
||||
11 => EntityDataValue::Direction(Direction::read_from(buf)?),
|
||||
12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_from(buf)?),
|
||||
13 => EntityDataValue::OptionalBlockState({
|
||||
let val = i32::read_from(buf)?;
|
||||
if val == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(val)
|
||||
}
|
||||
}),
|
||||
14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_from(buf)?),
|
||||
15 => EntityDataValue::Particle(Particle::read_from(buf)?),
|
||||
16 => EntityDataValue::VillagerData(VillagerData::read_from(buf)?),
|
||||
17 => EntityDataValue::OptionalUnsignedInt({
|
||||
let val = u32::var_read_from(buf)?;
|
||||
if val == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((val - 1) as u32)
|
||||
}
|
||||
}),
|
||||
18 => EntityDataValue::Pose(Pose::read_from(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,
|
||||
}
|
59
azalea-entity/src/lib.rs
Normal file
59
azalea-entity/src/lib.rs
Normal file
|
@ -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);
|
||||
// }
|
||||
// }
|
|
@ -9,4 +9,4 @@ version = "0.1.0"
|
|||
lazy_static = "1.4.0"
|
||||
serde = "1.0.137"
|
||||
serde_json = "1.0.81"
|
||||
# tokio = {version = "1.18.2", features = ["fs"]}
|
||||
# tokio = {version = "1.19.2", features = ["fs"]}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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_from(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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_from(&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()),
|
||||
)])),
|
||||
)]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,29 @@ 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 = "^0.3.2"
|
||||
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"
|
||||
flate2 = "1.0.23"
|
||||
num-derive = "^0.3.3"
|
||||
num-traits = "^0.2.14"
|
||||
packet-macros = {path = "./packet-macros"}
|
||||
serde = {version = "1.0.130", features = ["serde_derive"]}
|
||||
serde_json = "^1.0.72"
|
||||
thiserror = "^1.0.30"
|
||||
tokio = {version = "^1.14.0", features = ["io-util", "net", "macros"]}
|
||||
tokio = {version = "^1.19.2", features = ["io-util", "net", "macros"]}
|
||||
tokio-util = "^0.6.9"
|
||||
trust-dns-resolver = "^0.20.3"
|
||||
uuid = "^0.8.2"
|
||||
uuid = "1.1.2"
|
||||
|
||||
[features]
|
||||
connecting = []
|
||||
default = ["packets"]
|
||||
packets = ["connecting", "dep:async-compression", "dep:azalea-core"]
|
||||
|
|
|
@ -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,14 +25,14 @@ 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;
|
||||
Ok(Self::read_into(buf)?.get())
|
||||
use azalea_buf::McBufReadable;
|
||||
Ok(Self::read_from(buf)?.get())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
@ -24,15 +25,15 @@ pub struct BrigadierNumber<T> {
|
|||
max: Option<T>,
|
||||
}
|
||||
impl<T: McBufReadable> McBufReadable for BrigadierNumber<T> {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let flags = buf.read_byte()?;
|
||||
let min = if flags & 0x01 != 0 {
|
||||
Some(T::read_into(buf)?)
|
||||
Some(T::read_from(buf)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let max = if flags & 0x02 != 0 {
|
||||
Some(T::read_into(buf)?)
|
||||
Some(T::read_from(buf)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -123,16 +124,16 @@ pub enum BrigadierParser {
|
|||
}
|
||||
|
||||
impl McBufReadable for BrigadierParser {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let parser_type = u32::var_read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let parser_type = u32::var_read_from(buf)?;
|
||||
|
||||
match parser_type {
|
||||
0 => Ok(BrigadierParser::Bool),
|
||||
1 => Ok(BrigadierParser::Float(BrigadierNumber::read_into(buf)?)),
|
||||
2 => Ok(BrigadierParser::Double(BrigadierNumber::read_into(buf)?)),
|
||||
3 => Ok(BrigadierParser::Integer(BrigadierNumber::read_into(buf)?)),
|
||||
4 => Ok(BrigadierParser::Long(BrigadierNumber::read_into(buf)?)),
|
||||
5 => Ok(BrigadierParser::String(BrigadierString::read_into(buf)?)),
|
||||
1 => Ok(BrigadierParser::Float(BrigadierNumber::read_from(buf)?)),
|
||||
2 => Ok(BrigadierParser::Double(BrigadierNumber::read_from(buf)?)),
|
||||
3 => Ok(BrigadierParser::Integer(BrigadierNumber::read_from(buf)?)),
|
||||
4 => Ok(BrigadierParser::Long(BrigadierNumber::read_from(buf)?)),
|
||||
5 => Ok(BrigadierParser::String(BrigadierString::read_from(buf)?)),
|
||||
6 => {
|
||||
let flags = buf.read_byte()?;
|
||||
Ok(BrigadierParser::Entity {
|
||||
|
@ -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_from(buf)?,
|
||||
}),
|
||||
44 => Ok(BrigadierParser::Resource {
|
||||
registry_key: buf.read_resource_location()?,
|
||||
registry_key: ResourceLocation::read_from(buf)?,
|
||||
}),
|
||||
45 => Ok(BrigadierParser::TemplateMirror),
|
||||
46 => Ok(BrigadierParser::TemplateRotation),
|
||||
|
@ -197,8 +198,8 @@ impl McBufReadable for BrigadierParser {
|
|||
|
||||
// TODO: BrigadierNodeStub should have more stuff
|
||||
impl McBufReadable for BrigadierNodeStub {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let flags = u8::read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let flags = u8::read_from(buf)?;
|
||||
if flags > 31 {
|
||||
println!(
|
||||
"Warning: The flags from a Brigadier node are over 31 ({flags}; {flags:#b}). This is probably a bug.",
|
||||
|
@ -216,9 +217,9 @@ impl McBufReadable for BrigadierNodeStub {
|
|||
// argument node
|
||||
if node_type == 2 {
|
||||
let _name = buf.read_utf()?;
|
||||
let _parser = BrigadierParser::read_into(buf)?;
|
||||
let _parser = BrigadierParser::read_from(buf)?;
|
||||
let _suggestions_type = if has_suggestions_type {
|
||||
Some(buf.read_resource_location()?)
|
||||
Some(ResourceLocation::read_from(buf)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use packet_macros::{GamePacket, McBuf};
|
||||
use azalea_buf::McBuf;
|
||||
use packet_macros::GamePacket;
|
||||
|
||||
use super::clientbound_light_update_packet::ClientboundLightUpdatePacketData;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -20,17 +20,17 @@ pub struct ClientboundLevelParticlesPacket {
|
|||
}
|
||||
|
||||
impl McBufReadable for ClientboundLevelParticlesPacket {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let particle_id = u32::var_read_into(buf)?;
|
||||
let override_limiter = bool::read_into(buf)?;
|
||||
let x = f64::read_into(buf)?;
|
||||
let y = f64::read_into(buf)?;
|
||||
let z = f64::read_into(buf)?;
|
||||
let x_dist = f32::read_into(buf)?;
|
||||
let y_dist = f32::read_into(buf)?;
|
||||
let z_dist = f32::read_into(buf)?;
|
||||
let max_speed = f32::read_into(buf)?;
|
||||
let count = u32::read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let particle_id = u32::var_read_from(buf)?;
|
||||
let override_limiter = bool::read_from(buf)?;
|
||||
let x = f64::read_from(buf)?;
|
||||
let y = f64::read_from(buf)?;
|
||||
let z = f64::read_from(buf)?;
|
||||
let x_dist = f32::read_from(buf)?;
|
||||
let y_dist = f32::read_from(buf)?;
|
||||
let z_dist = f32::read_from(buf)?;
|
||||
let max_speed = f32::read_from(buf)?;
|
||||
let count = u32::read_from(buf)?;
|
||||
|
||||
let data = ParticleData::read_from_particle_id(buf, particle_id)?;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
@ -19,7 +20,7 @@ pub struct PlayerAbilitiesFlags {
|
|||
}
|
||||
|
||||
impl McBufReadable for PlayerAbilitiesFlags {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let byte = buf.read_byte()?;
|
||||
Ok(PlayerAbilitiesFlags {
|
||||
invulnerable: byte & 1 != 0,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -62,14 +63,14 @@ pub struct RemovePlayer {
|
|||
}
|
||||
|
||||
impl McBufReadable for Action {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let id = buf.read_byte()?;
|
||||
Ok(match id {
|
||||
0 => Action::AddPlayer(Vec::<AddPlayer>::read_into(buf)?),
|
||||
1 => Action::UpdateGameMode(Vec::<UpdateGameMode>::read_into(buf)?),
|
||||
2 => Action::UpdateLatency(Vec::<UpdateLatency>::read_into(buf)?),
|
||||
3 => Action::UpdateDisplayName(Vec::<UpdateDisplayName>::read_into(buf)?),
|
||||
4 => Action::RemovePlayer(Vec::<RemovePlayer>::read_into(buf)?),
|
||||
0 => Action::AddPlayer(Vec::<AddPlayer>::read_from(buf)?),
|
||||
1 => Action::UpdateGameMode(Vec::<UpdateGameMode>::read_from(buf)?),
|
||||
2 => Action::UpdateLatency(Vec::<UpdateLatency>::read_from(buf)?),
|
||||
3 => Action::UpdateDisplayName(Vec::<UpdateDisplayName>::read_from(buf)?),
|
||||
4 => Action::RemovePlayer(Vec::<RemovePlayer>::read_from(buf)?),
|
||||
_ => panic!("Unknown player info action id: {}", id),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ pub struct RelativeArguments {
|
|||
}
|
||||
|
||||
impl McBufReadable for RelativeArguments {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let byte = buf.read_byte()?;
|
||||
Ok(RelativeArguments {
|
||||
x: byte & 0b1 != 0,
|
||||
|
|
|
@ -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)]
|
||||
|
@ -40,7 +41,7 @@ impl McBufWritable for State {
|
|||
}
|
||||
}
|
||||
impl McBufReadable for State {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let state = buf.read_varint()?;
|
||||
Ok(match state {
|
||||
0 => State::Init,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
@ -17,8 +18,8 @@ pub struct BlockStateWithPosition {
|
|||
}
|
||||
|
||||
impl McBufReadable for BlockStateWithPosition {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let data = u64::var_read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let data = u64::var_read_from(buf)?;
|
||||
let position_part = data & 4095;
|
||||
let state = (data >> 12) as u32;
|
||||
let position = ChunkSectionBlockPos {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
@ -16,14 +17,14 @@ pub struct EquipmentSlots {
|
|||
}
|
||||
|
||||
impl McBufReadable for EquipmentSlots {
|
||||
fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl std::io::Read) -> Result<Self, String> {
|
||||
let mut slots = vec![];
|
||||
|
||||
loop {
|
||||
let equipment_byte = u8::read_into(buf)?;
|
||||
let equipment_byte = u8::read_from(buf)?;
|
||||
let equipment_slot = EquipmentSlot::from_byte(equipment_byte & 127)
|
||||
.ok_or_else(|| format!("Invalid equipment slot byte {}", equipment_byte))?;
|
||||
let item = Slot::read_into(buf)?;
|
||||
let item = Slot::read_from(buf)?;
|
||||
slots.push((equipment_slot, item));
|
||||
if equipment_byte & 128 == 0 {
|
||||
break;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
@ -45,8 +45,8 @@ pub struct DisplayFlags {
|
|||
}
|
||||
|
||||
impl McBufReadable for DisplayFlags {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let data = u32::read_into(buf)?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let data = u32::read_from(buf)?;
|
||||
Ok(DisplayFlags {
|
||||
background: (data & 0b1) != 0,
|
||||
show_toast: (data & 0b10) != 0,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -33,7 +34,7 @@ enum Operation {
|
|||
}
|
||||
|
||||
impl McBufReadable for Operation {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
match buf.read_byte()? {
|
||||
0 => Ok(Operation::Addition),
|
||||
1 => Ok(Operation::MultiplyBase),
|
||||
|
|
|
@ -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>,
|
||||
}
|
|
@ -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 {
|
||||
|
@ -48,15 +49,15 @@ impl McBufWritable for ShapedRecipe {
|
|||
}
|
||||
}
|
||||
impl McBufReadable for ShapedRecipe {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let width = buf.read_varint()?.try_into().unwrap();
|
||||
let height = buf.read_varint()?.try_into().unwrap();
|
||||
let group = buf.read_utf()?;
|
||||
let mut ingredients = Vec::with_capacity(width * height);
|
||||
for _ in 0..width * height {
|
||||
ingredients.push(Ingredient::read_into(buf)?);
|
||||
ingredients.push(Ingredient::read_from(buf)?);
|
||||
}
|
||||
let result = Slot::read_into(buf)?;
|
||||
let result = Slot::read_from(buf)?;
|
||||
|
||||
Ok(ShapedRecipe {
|
||||
width,
|
||||
|
@ -128,17 +129,17 @@ 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()?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let recipe_type = ResourceLocation::read_from(buf)?;
|
||||
let identifier = ResourceLocation::read_from(buf)?;
|
||||
|
||||
// rust doesn't let us match ResourceLocation so we have to do a big
|
||||
// if-else chain :(
|
||||
let data = if recipe_type == ResourceLocation::new("minecraft:crafting_shapeless").unwrap()
|
||||
{
|
||||
RecipeData::CraftingShapeless(ShapelessRecipe::read_into(buf)?)
|
||||
RecipeData::CraftingShapeless(ShapelessRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:crafting_shaped").unwrap() {
|
||||
RecipeData::CraftingShaped(ShapedRecipe::read_into(buf)?)
|
||||
RecipeData::CraftingShaped(ShapedRecipe::read_from(buf)?)
|
||||
} else if recipe_type
|
||||
== ResourceLocation::new("minecraft:crafting_special_armordye").unwrap()
|
||||
{
|
||||
|
@ -196,17 +197,17 @@ impl McBufReadable for Recipe {
|
|||
{
|
||||
RecipeData::CraftingSpecialSuspiciousStew
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:smelting").unwrap() {
|
||||
RecipeData::Smelting(CookingRecipe::read_into(buf)?)
|
||||
RecipeData::Smelting(CookingRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:blasting").unwrap() {
|
||||
RecipeData::Blasting(CookingRecipe::read_into(buf)?)
|
||||
RecipeData::Blasting(CookingRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:smoking").unwrap() {
|
||||
RecipeData::Smoking(CookingRecipe::read_into(buf)?)
|
||||
RecipeData::Smoking(CookingRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:campfire_cooking").unwrap() {
|
||||
RecipeData::CampfireCooking(CookingRecipe::read_into(buf)?)
|
||||
RecipeData::CampfireCooking(CookingRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:stonecutting").unwrap() {
|
||||
RecipeData::Stonecutting(StoneCuttingRecipe::read_into(buf)?)
|
||||
RecipeData::Stonecutting(StoneCuttingRecipe::read_from(buf)?)
|
||||
} else if recipe_type == ResourceLocation::new("minecraft:smithing").unwrap() {
|
||||
RecipeData::Smithing(SmithingRecipe::read_into(buf)?)
|
||||
RecipeData::Smithing(SmithingRecipe::read_from(buf)?)
|
||||
} else {
|
||||
panic!("Unknown recipe type sent by server: {}", recipe_type);
|
||||
};
|
||||
|
|
|
@ -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,28 +19,31 @@ pub struct Tags {
|
|||
pub elements: Vec<i32>,
|
||||
}
|
||||
|
||||
impl McBufReadable for HashMap<ResourceLocation, Vec<Tags>> {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TagMap(HashMap<ResourceLocation, Vec<Tags>>);
|
||||
|
||||
impl McBufReadable for TagMap {
|
||||
fn read_from(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_from(buf)?;
|
||||
let tags_count = buf.read_varint()? as usize;
|
||||
let mut tags_vec = Vec::with_capacity(tags_count);
|
||||
for _ in 0..tags_count {
|
||||
let tags = Tags::read_into(buf)?;
|
||||
let tags = Tags::read_from(buf)?;
|
||||
tags_vec.push(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)?;
|
||||
}
|
||||
|
@ -46,8 +51,8 @@ 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()?;
|
||||
fn read_from(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let name = ResourceLocation::read_from(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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
use azalea_buf::McBuf;
|
||||
use packet_macros::GamePacket;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, GamePacket)]
|
||||
pub struct ServerboundAcceptTeleportationPacket {
|
||||
#[var]
|
||||
pub id: u32,
|
||||
}
|
|
@ -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 {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue