mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
merge main
This commit is contained in:
commit
1812bfbe79
132 changed files with 3889 additions and 918 deletions
115
Cargo.lock
generated
115
Cargo.lock
generated
|
@ -121,9 +121,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
@ -433,6 +433,7 @@ dependencies = [
|
|||
"azalea-registry",
|
||||
"indexmap",
|
||||
"simdnbt",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -449,6 +450,7 @@ dependencies = [
|
|||
name = "azalea-language"
|
||||
version = "0.11.0+mc1.21.4"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
@ -548,6 +550,7 @@ dependencies = [
|
|||
"nohash-hasher",
|
||||
"parking_lot",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"simdnbt",
|
||||
"thiserror 2.0.11",
|
||||
"tracing",
|
||||
|
@ -823,10 +826,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.13"
|
||||
name = "castaway"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -921,18 +933,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.29"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.29"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
|
@ -950,6 +962,21 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"rustversion",
|
||||
"ryu",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
|
@ -1244,9 +1271,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
|
@ -1500,9 +1527,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
|||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.24.3"
|
||||
version = "0.24.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad3d6d98c648ed628df039541a5577bee1a7c83e9e16fe3dbedeea4cdfeb971"
|
||||
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
|
@ -1524,9 +1551,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.3"
|
||||
version = "0.24.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf287bde7b776e85d7188e6e5db7cf410a2f9531fe82817eb87feed034c8d14"
|
||||
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
|
@ -1794,9 +1821,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
@ -1913,9 +1940,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
|
@ -1971,9 +1998,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
@ -2373,9 +2400,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
|
||||
checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
|
@ -2446,9 +2473,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -2542,9 +2569,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.9"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
||||
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
|
@ -2691,18 +2718,18 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2711,9 +2738,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
@ -2852,9 +2879,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
|
@ -2912,6 +2939,12 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
@ -3250,15 +3283,15 @@ checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
|
|||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
|
@ -3594,9 +3627,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
|
||||
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ resolver = "2"
|
|||
|
||||
[workspace.package]
|
||||
version = "0.11.0+mc1.21.4"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/azalea-rs/azalea"
|
||||
# homepage = "https://github.com/azalea-rs/azalea"
|
||||
|
@ -79,6 +79,7 @@ uuid = "1.12.1"
|
|||
num-format = "0.4.4"
|
||||
indexmap = "2.7.1"
|
||||
paste = "1.0.15"
|
||||
compact_str = "0.8.1"
|
||||
|
||||
# --- Profile Settings ---
|
||||
|
||||
|
|
|
@ -360,7 +360,7 @@ pub async fn get_ms_auth_token(
|
|||
tokio::time::sleep(std::time::Duration::from_secs(res.interval)).await;
|
||||
|
||||
trace!("Polling to check if user has logged in...");
|
||||
if let Ok(access_token_response) = client
|
||||
let res = client
|
||||
.post(format!(
|
||||
"https://login.live.com/oauth20_token.srf?client_id={client_id}"
|
||||
))
|
||||
|
@ -372,8 +372,8 @@ pub async fn get_ms_auth_token(
|
|||
.send()
|
||||
.await?
|
||||
.json::<AccessTokenResponse>()
|
||||
.await
|
||||
{
|
||||
.await;
|
||||
if let Ok(access_token_response) = res {
|
||||
trace!("access_token_response: {:?}", access_token_response);
|
||||
let expires_at = SystemTime::now()
|
||||
+ std::time::Duration::from_secs(access_token_response.expires_in);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use base64::Engine;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
|
||||
use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use tracing::trace;
|
||||
|
|
|
@ -159,7 +159,7 @@ pub async fn serverside_auth(
|
|||
StatusCode::FORBIDDEN => {
|
||||
return Err(ServerSessionServerError::Unknown(
|
||||
res.json::<ForbiddenError>().await?.error,
|
||||
))
|
||||
));
|
||||
}
|
||||
status_code => {
|
||||
// log the headers
|
||||
|
|
|
@ -9,13 +9,13 @@ use proc_macro::TokenStream;
|
|||
use proc_macro2::TokenTree;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
braced,
|
||||
Expr, Ident, LitStr, Token, braced,
|
||||
ext::IdentExt,
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token, Expr, Ident, LitStr, Token,
|
||||
token,
|
||||
};
|
||||
use utils::{combinations_of, to_pascal_case};
|
||||
|
||||
|
@ -511,13 +511,13 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());
|
||||
|
||||
// this terrible code just gets the property default as a string
|
||||
let property_default_as_string = if let TokenTree::Ident(ident) =
|
||||
property.default.clone().into_iter().last().unwrap()
|
||||
{
|
||||
ident.to_string()
|
||||
} else {
|
||||
panic!()
|
||||
};
|
||||
let property_default_as_string =
|
||||
match property.default.clone().into_iter().last().unwrap() {
|
||||
TokenTree::Ident(ident) => ident.to_string(),
|
||||
_ => {
|
||||
panic!()
|
||||
}
|
||||
};
|
||||
if property_default_as_string != combination[i] {
|
||||
is_default = false;
|
||||
}
|
||||
|
@ -565,15 +565,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
let Some(default_state_id) = default_state_id else {
|
||||
let defaults = properties_with_name
|
||||
.iter()
|
||||
.map(|p| {
|
||||
if let TokenTree::Ident(i) = p.default.clone().into_iter().last().unwrap() {
|
||||
i.to_string()
|
||||
} else {
|
||||
.map(|p| match p.default.clone().into_iter().last().unwrap() {
|
||||
TokenTree::Ident(i) => i.to_string(),
|
||||
_ => {
|
||||
panic!()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
panic!("Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}")
|
||||
panic!(
|
||||
"Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}"
|
||||
)
|
||||
};
|
||||
|
||||
// 7035..=7058 => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::{
|
||||
collections::{hash_set, HashSet},
|
||||
collections::{HashSet, hash_set},
|
||||
ops::{Add, RangeInclusive},
|
||||
};
|
||||
|
||||
use crate::{block_state::BlockStateIntegerRepr, BlockState};
|
||||
use crate::{BlockState, block_state::BlockStateIntegerRepr};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockStates {
|
||||
|
|
|
@ -108,23 +108,30 @@ impl<S> CommandDispatcher<S> {
|
|||
1
|
||||
}) {
|
||||
reader.skip();
|
||||
if let Some(redirect) = &child.read().redirect {
|
||||
let child_context =
|
||||
CommandContextBuilder::new(self, source, redirect.clone(), reader.cursor);
|
||||
let parse = self
|
||||
.parse_nodes(redirect, &reader, child_context)
|
||||
.expect("Parsing nodes failed");
|
||||
context.with_child(Rc::new(parse.context));
|
||||
return Ok(ParseResults {
|
||||
context,
|
||||
reader: parse.reader,
|
||||
exceptions: parse.exceptions,
|
||||
});
|
||||
} else {
|
||||
let parse = self
|
||||
.parse_nodes(&child, &reader, context)
|
||||
.expect("Parsing nodes failed");
|
||||
potentials.push(parse);
|
||||
match &child.read().redirect {
|
||||
Some(redirect) => {
|
||||
let child_context = CommandContextBuilder::new(
|
||||
self,
|
||||
source,
|
||||
redirect.clone(),
|
||||
reader.cursor,
|
||||
);
|
||||
let parse = self
|
||||
.parse_nodes(redirect, &reader, child_context)
|
||||
.expect("Parsing nodes failed");
|
||||
context.with_child(Rc::new(parse.context));
|
||||
return Ok(ParseResults {
|
||||
context,
|
||||
reader: parse.reader,
|
||||
exceptions: parse.exceptions,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
let parse = self
|
||||
.parse_nodes(&child, &reader, context)
|
||||
.expect("Parsing nodes failed");
|
||||
potentials.push(parse);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
potentials.push(ParseResults {
|
||||
|
@ -215,11 +222,14 @@ impl<S> CommandDispatcher<S> {
|
|||
pub fn find_node(&self, path: &[&str]) -> Option<Arc<RwLock<CommandNode<S>>>> {
|
||||
let mut node = self.root.clone();
|
||||
for name in path {
|
||||
if let Some(child) = node.clone().read().child(name) {
|
||||
node = child;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
match node.clone().read().child(name) {
|
||||
Some(child) => {
|
||||
node = child;
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
@ -258,31 +268,41 @@ impl<S> CommandDispatcher<S> {
|
|||
let modifier = &context.modifier;
|
||||
if let Some(modifier) = modifier {
|
||||
let results = modifier(context);
|
||||
if let Ok(results) = results {
|
||||
if !results.is_empty() {
|
||||
next.extend(results.iter().map(|s| child.copy_for(s.clone())));
|
||||
match results {
|
||||
Ok(results) => {
|
||||
if !results.is_empty() {
|
||||
next.extend(
|
||||
results.iter().map(|s| child.copy_for(s.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
// self.consumer.on_command_complete(context, false, 0);
|
||||
if !forked {
|
||||
return Err(results.err().unwrap());
|
||||
_ => {
|
||||
// TODO
|
||||
// self.consumer.on_command_complete(context, false, 0);
|
||||
if !forked {
|
||||
return Err(results.err().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next.push(child.copy_for(context.source.clone()));
|
||||
}
|
||||
}
|
||||
} else if let Some(context_command) = &context.command {
|
||||
found_command = true;
|
||||
} else {
|
||||
match &context.command {
|
||||
Some(context_command) => {
|
||||
found_command = true;
|
||||
|
||||
let value = context_command(context);
|
||||
result += value;
|
||||
// consumer.on_command_complete(context, true, value);
|
||||
successful_forks += 1;
|
||||
let value = context_command(context);
|
||||
result += value;
|
||||
// consumer.on_command_complete(context, true, value);
|
||||
successful_forks += 1;
|
||||
|
||||
// TODO: allow context_command to error and handle those
|
||||
// errors
|
||||
// TODO: allow context_command to error and handle
|
||||
// those errors
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,32 +352,35 @@ impl<S> CommandDispatcher<S> {
|
|||
if node.command.is_some() {
|
||||
result.push(prefix.to_owned());
|
||||
}
|
||||
if let Some(redirect) = &node.redirect {
|
||||
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
|
||||
"...".to_string()
|
||||
} else {
|
||||
format!("-> {}", redirect.read().usage_text())
|
||||
};
|
||||
if prefix.is_empty() {
|
||||
result.push(format!("{} {redirect}", node.usage_text()));
|
||||
} else {
|
||||
result.push(format!("{prefix} {redirect}"));
|
||||
match &node.redirect {
|
||||
Some(redirect) => {
|
||||
let redirect = if redirect.data_ptr() == self.root.data_ptr() {
|
||||
"...".to_string()
|
||||
} else {
|
||||
format!("-> {}", redirect.read().usage_text())
|
||||
};
|
||||
if prefix.is_empty() {
|
||||
result.push(format!("{} {redirect}", node.usage_text()));
|
||||
} else {
|
||||
result.push(format!("{prefix} {redirect}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for child in node.children.values() {
|
||||
let child = child.read();
|
||||
self.get_all_usage_recursive(
|
||||
&child,
|
||||
source,
|
||||
result,
|
||||
if prefix.is_empty() {
|
||||
child.usage_text()
|
||||
} else {
|
||||
format!("{prefix} {}", child.usage_text())
|
||||
}
|
||||
.as_str(),
|
||||
restricted,
|
||||
);
|
||||
_ => {
|
||||
for child in node.children.values() {
|
||||
let child = child.read();
|
||||
self.get_all_usage_recursive(
|
||||
&child,
|
||||
source,
|
||||
result,
|
||||
if prefix.is_empty() {
|
||||
child.usage_text()
|
||||
} else {
|
||||
format!("{prefix} {}", child.usage_text())
|
||||
}
|
||||
.as_str(),
|
||||
restricted,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
|
|||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument};
|
||||
use super::{ParsedArgument, parsed_command_node::ParsedCommandNode, string_range::StringRange};
|
||||
use crate::{
|
||||
modifier::RedirectModifier,
|
||||
tree::{Command, CommandNode},
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
|
|||
use parking_lot::RwLock;
|
||||
|
||||
use super::{
|
||||
command_context::CommandContext, parsed_command_node::ParsedCommandNode,
|
||||
string_range::StringRange, suggestion_context::SuggestionContext, ParsedArgument,
|
||||
ParsedArgument, command_context::CommandContext, parsed_command_node::ParsedCommandNode,
|
||||
string_range::StringRange, suggestion_context::SuggestionContext,
|
||||
};
|
||||
use crate::{
|
||||
command_dispatcher::CommandDispatcher,
|
||||
|
@ -107,18 +107,18 @@ impl<'a, S> CommandContextBuilder<'a, S> {
|
|||
}
|
||||
|
||||
if self.range.end() < cursor {
|
||||
if let Some(child) = &self.child {
|
||||
child.find_suggestion_context(cursor)
|
||||
} else if let Some(last) = self.nodes.last() {
|
||||
SuggestionContext {
|
||||
parent: Arc::clone(&last.node),
|
||||
start_pos: last.range.end() + 1,
|
||||
}
|
||||
} else {
|
||||
SuggestionContext {
|
||||
parent: Arc::clone(&self.root),
|
||||
start_pos: self.range.start(),
|
||||
}
|
||||
match &self.child {
|
||||
Some(child) => child.find_suggestion_context(cursor),
|
||||
_ => match self.nodes.last() {
|
||||
Some(last) => SuggestionContext {
|
||||
parent: Arc::clone(&last.node),
|
||||
start_pos: last.range.end() + 1,
|
||||
},
|
||||
_ => SuggestionContext {
|
||||
parent: Arc::clone(&self.root),
|
||||
start_pos: self.range.start(),
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let mut prev = &self.root;
|
||||
|
|
|
@ -292,18 +292,27 @@ impl<S> PartialEq for CommandNode<S> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(selfexecutes) = &self.command {
|
||||
// idk how to do this better since we can't compare `dyn Fn`s
|
||||
if let Some(otherexecutes) = &other.command {
|
||||
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||
if !Arc::ptr_eq(selfexecutes, otherexecutes) {
|
||||
match &self.command {
|
||||
Some(selfexecutes) => {
|
||||
// idk how to do this better since we can't compare `dyn Fn`s
|
||||
match &other.command {
|
||||
Some(otherexecutes) =>
|
||||
{
|
||||
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||
if !Arc::ptr_eq(selfexecutes, otherexecutes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if other.command.is_some() {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if other.command.is_some() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@ fn test_arguments() {
|
|||
let builder = builder.then(argument.clone());
|
||||
assert_eq!(builder.arguments().children.len(), 1);
|
||||
let built_argument = Rc::new(argument.build());
|
||||
assert!(builder
|
||||
.arguments()
|
||||
.children
|
||||
.values()
|
||||
.any(|e| *e.read() == *built_argument));
|
||||
assert!(
|
||||
builder
|
||||
.arguments()
|
||||
.children
|
||||
.values()
|
||||
.any(|e| *e.read() == *built_argument)
|
||||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
|
|
|
@ -3,7 +3,7 @@ mod write;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
#[proc_macro_derive(AzaleaRead, attributes(var))]
|
||||
pub fn derive_azalearead(input: TokenStream) -> TokenStream {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use quote::{quote, ToTokens};
|
||||
use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma};
|
||||
|
||||
fn read_named_fields(
|
||||
named: &Punctuated<Field, Comma>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident};
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::{Data, Field, FieldsNamed, Ident, punctuated::Punctuated, token::Comma};
|
||||
|
||||
fn write_named_fields(
|
||||
named: &Punctuated<Field, Comma>,
|
||||
|
|
|
@ -3,13 +3,14 @@ use std::{
|
|||
collections::HashMap,
|
||||
hash::Hash,
|
||||
io::{Cursor, Read},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
use byteorder::{BE, ReadBytesExt};
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
|
||||
use super::{UnsizedByteArray, MAX_STRING_LENGTH};
|
||||
use super::{MAX_STRING_LENGTH, UnsizedByteArray};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BufReadError {
|
||||
|
@ -19,8 +20,12 @@ pub enum BufReadError {
|
|||
InvalidVarLong,
|
||||
#[error("Error reading bytes")]
|
||||
CouldNotReadBytes,
|
||||
#[error("The received encoded string buffer length is longer than maximum allowed ({length} > {max_length})")]
|
||||
#[error(
|
||||
"The received encoded string buffer length is longer than maximum allowed ({length} > {max_length})"
|
||||
)]
|
||||
StringLengthTooLong { length: u32, max_length: u32 },
|
||||
#[error("The received Vec length is longer than maximum allowed ({length} > {max_length})")]
|
||||
VecLengthTooLong { length: u32, max_length: u32 },
|
||||
#[error("{source}")]
|
||||
Io {
|
||||
#[from]
|
||||
|
@ -183,7 +188,7 @@ impl AzaleaRead for UnsizedByteArray {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaRead + Send> AzaleaRead for Vec<T> {
|
||||
impl<T: AzaleaRead> AzaleaRead for Vec<T> {
|
||||
default fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let length = u32::azalea_read_var(buf)? as usize;
|
||||
// we limit the capacity to not get exploited into allocating a bunch
|
||||
|
@ -194,6 +199,23 @@ impl<T: AzaleaRead + Send> AzaleaRead for Vec<T> {
|
|||
Ok(contents)
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaRead> AzaleaReadLimited for Vec<T> {
|
||||
fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: usize) -> Result<Self, BufReadError> {
|
||||
let length = u32::azalea_read_var(buf)? as usize;
|
||||
if length > limit {
|
||||
return Err(BufReadError::VecLengthTooLong {
|
||||
length: length as u32,
|
||||
max_length: limit as u32,
|
||||
});
|
||||
}
|
||||
|
||||
let mut contents = Vec::with_capacity(usize::min(length, 65536));
|
||||
for _ in 0..length {
|
||||
contents.push(T::azalea_read(buf)?);
|
||||
}
|
||||
Ok(contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: AzaleaRead + Send + Eq + Hash, V: AzaleaRead + Send> AzaleaRead for HashMap<K, V> {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
|
@ -343,6 +365,16 @@ impl<T: AzaleaReadVar> AzaleaReadVar for Option<T> {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaReadLimited> AzaleaReadLimited for Option<T> {
|
||||
fn azalea_read_limited(buf: &mut Cursor<&[u8]>, limit: usize) -> Result<Self, BufReadError> {
|
||||
let present = bool::azalea_read(buf)?;
|
||||
Ok(if present {
|
||||
Some(T::azalea_read_limited(buf, limit)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// [String; 4]
|
||||
impl<T: AzaleaRead, const N: usize> AzaleaRead for [T; N] {
|
||||
|
@ -392,3 +424,9 @@ impl<A: AzaleaRead, B: AzaleaRead> AzaleaRead for (A, B) {
|
|||
Ok((A::azalea_read(buf)?, B::azalea_read(buf)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaRead> AzaleaRead for Arc<T> {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
Ok(Arc::new(T::azalea_read(buf)?))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Cursor, Write};
|
|||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{read::BufReadError, AzaleaRead, AzaleaWrite};
|
||||
use crate::{AzaleaRead, AzaleaWrite, read::BufReadError};
|
||||
|
||||
pub trait SerializableUuid {
|
||||
fn to_int_array(&self) -> [u32; 4];
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, Write},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
|
||||
use super::{UnsizedByteArray, MAX_STRING_LENGTH};
|
||||
use super::{MAX_STRING_LENGTH, UnsizedByteArray};
|
||||
|
||||
fn write_utf_with_len(buf: &mut impl Write, string: &str, len: usize) -> Result<(), io::Error> {
|
||||
if string.len() > len {
|
||||
|
@ -298,3 +299,9 @@ impl<A: AzaleaWrite, B: AzaleaWrite> AzaleaWrite for (A, B) {
|
|||
self.1.azalea_write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> AzaleaWrite for Arc<T> {
|
||||
fn azalea_write(&self, buf: &mut impl Write) -> Result<(), io::Error> {
|
||||
T::azalea_write(&**self, buf)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{style::Style, FormattedText};
|
||||
use crate::{FormattedText, style::Style};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)]
|
||||
pub struct BaseComponent {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{fmt::Display, sync::LazyLock};
|
|||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize, de};
|
||||
#[cfg(feature = "simdnbt")]
|
||||
use simdnbt::{Deserialize as _, FromNbtTag as _, Serialize as _};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
@ -371,7 +371,9 @@ impl FormattedText {
|
|||
} else if let Some(s) = primitive.string() {
|
||||
with_array.push(StringOrComponent::String(s.to_string()));
|
||||
} else {
|
||||
warn!("couldn't parse {item:?} as FormattedText because it has a disallowed primitive");
|
||||
warn!(
|
||||
"couldn't parse {item:?} as FormattedText because it has a disallowed primitive"
|
||||
);
|
||||
with_array.push(StringOrComponent::String("?".to_string()));
|
||||
}
|
||||
} else if let Some(c) = FormattedText::from_nbt_compound(item) {
|
||||
|
@ -392,7 +394,9 @@ impl FormattedText {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
warn!("couldn't parse {with:?} as FormattedText because it's not a list of compounds");
|
||||
warn!(
|
||||
"couldn't parse {with:?} as FormattedText because it's not a list of compounds"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
component =
|
||||
|
@ -456,12 +460,11 @@ impl From<&simdnbt::Mutf8Str> for FormattedText {
|
|||
impl AzaleaRead for FormattedText {
|
||||
fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let nbt = simdnbt::borrow::read_optional_tag(buf)?;
|
||||
if let Some(nbt) = nbt {
|
||||
FormattedText::from_nbt_tag(nbt.as_tag()).ok_or(BufReadError::Custom(
|
||||
match nbt {
|
||||
Some(nbt) => FormattedText::from_nbt_tag(nbt.as_tag()).ok_or(BufReadError::Custom(
|
||||
"couldn't convert nbt to chat message".to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(FormattedText::default())
|
||||
)),
|
||||
_ => Ok(FormattedText::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt, sync::LazyLock};
|
|||
|
||||
#[cfg(feature = "azalea-buf")]
|
||||
use azalea_buf::AzBuf;
|
||||
use serde::{ser::SerializeStruct, Serialize, Serializer};
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
use serde_json::Value;
|
||||
#[cfg(feature = "simdnbt")]
|
||||
use simdnbt::owned::{NbtCompound, NbtTag};
|
||||
|
@ -334,10 +334,15 @@ fn simdnbt_serialize_field(
|
|||
default: impl simdnbt::ToNbtTag,
|
||||
reset: bool,
|
||||
) {
|
||||
if let Some(value) = value {
|
||||
compound.insert(name, value);
|
||||
} else if reset {
|
||||
compound.insert(name, default);
|
||||
match value {
|
||||
Some(value) => {
|
||||
compound.insert(name, value);
|
||||
}
|
||||
_ => {
|
||||
if reset {
|
||||
compound.insert(name, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
|
||||
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
|
||||
|
||||
use crate::{base_component::BaseComponent, style::ChatFormatting, FormattedText};
|
||||
use crate::{FormattedText, base_component::BaseComponent, style::ChatFormatting};
|
||||
|
||||
/// A component that contains text that's the same in all locales.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
|
||||
use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::SerializeMap};
|
||||
#[cfg(feature = "simdnbt")]
|
||||
use simdnbt::Serialize as _;
|
||||
|
||||
use crate::{
|
||||
base_component::BaseComponent, style::Style, text_component::TextComponent, FormattedText,
|
||||
FormattedText, base_component::BaseComponent, style::Style, text_component::TextComponent,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use azalea_chat::{
|
||||
style::{Ansi, ChatFormatting, TextColor},
|
||||
FormattedText,
|
||||
style::{Ansi, ChatFormatting, TextColor},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea_auth::certs::{Certificates, FetchCertificatesError};
|
||||
use azalea_auth::AccessTokenResponse;
|
||||
use azalea_auth::certs::{Certificates, FetchCertificatesError};
|
||||
use bevy_ecs::component::Component;
|
||||
use parking_lot::Mutex;
|
||||
use thiserror::Error;
|
||||
|
|
225
azalea-client/src/chunks.rs
Normal file
225
azalea-client/src/chunks.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
//! Used for Minecraft's chunk batching introduced in 23w31a (1.20.2). It's used
|
||||
//! for making the server spread out how often it sends us chunk packets
|
||||
//! depending on our receiving speed.
|
||||
|
||||
use std::{
|
||||
io::Cursor,
|
||||
ops::Deref,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use azalea_core::position::ChunkPos;
|
||||
use azalea_protocol::packets::game::{
|
||||
c_level_chunk_with_light::ClientboundLevelChunkWithLight,
|
||||
s_chunk_batch_received::ServerboundChunkBatchReceived,
|
||||
};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use simdnbt::owned::BaseNbt;
|
||||
use tracing::{error, trace};
|
||||
|
||||
use crate::{
|
||||
InstanceHolder,
|
||||
interact::handle_block_interact_event,
|
||||
inventory::InventorySet,
|
||||
packet::game::{SendPacketEvent, handle_send_packet_event},
|
||||
respawn::perform_respawn,
|
||||
};
|
||||
|
||||
pub struct ChunkPlugin;
|
||||
impl Plugin for ChunkPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_chunk_batch_start_event,
|
||||
handle_receive_chunk_events,
|
||||
handle_chunk_batch_finished_event,
|
||||
)
|
||||
.chain()
|
||||
.before(handle_send_packet_event)
|
||||
.before(InventorySet)
|
||||
.before(handle_block_interact_event)
|
||||
.before(perform_respawn),
|
||||
)
|
||||
.add_event::<ReceiveChunkEvent>()
|
||||
.add_event::<ChunkBatchStartEvent>()
|
||||
.add_event::<ChunkBatchFinishedEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct ReceiveChunkEvent {
|
||||
pub entity: Entity,
|
||||
pub packet: ClientboundLevelChunkWithLight,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug)]
|
||||
pub struct ChunkBatchInfo {
|
||||
pub start_time: Instant,
|
||||
pub aggregated_duration_per_chunk: Duration,
|
||||
pub old_samples_weight: u32,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct ChunkBatchStartEvent {
|
||||
pub entity: Entity,
|
||||
}
|
||||
#[derive(Event)]
|
||||
pub struct ChunkBatchFinishedEvent {
|
||||
pub entity: Entity,
|
||||
pub batch_size: u32,
|
||||
}
|
||||
|
||||
pub fn handle_receive_chunk_events(
|
||||
mut events: EventReader<ReceiveChunkEvent>,
|
||||
mut query: Query<&mut InstanceHolder>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let pos = ChunkPos::new(event.packet.x, event.packet.z);
|
||||
|
||||
let local_player = query.get_mut(event.entity).unwrap();
|
||||
|
||||
let mut instance = local_player.instance.write();
|
||||
let mut partial_instance = local_player.partial_instance.write();
|
||||
|
||||
// OPTIMIZATION: if we already know about the chunk from the shared world (and
|
||||
// not ourselves), then we don't need to parse it again. This is only used when
|
||||
// we have a shared world, since we check that the chunk isn't currently owned
|
||||
// by this client.
|
||||
let shared_chunk = instance.chunks.get(&pos);
|
||||
let this_client_has_chunk = partial_instance.chunks.limited_get(&pos).is_some();
|
||||
|
||||
if !this_client_has_chunk {
|
||||
if let Some(shared_chunk) = shared_chunk {
|
||||
trace!("Skipping parsing chunk {pos:?} because we already know about it");
|
||||
partial_instance
|
||||
.chunks
|
||||
.limited_set(&pos, Some(shared_chunk));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let heightmaps_nbt = &event.packet.chunk_data.heightmaps;
|
||||
// necessary to make the unwrap_or work
|
||||
let empty_nbt = BaseNbt::default();
|
||||
let heightmaps = heightmaps_nbt.unwrap_or(&empty_nbt).deref();
|
||||
|
||||
if let Err(e) = partial_instance.chunks.replace_with_packet_data(
|
||||
&pos,
|
||||
&mut Cursor::new(&event.packet.chunk_data.data),
|
||||
heightmaps,
|
||||
&mut instance.chunks,
|
||||
) {
|
||||
error!(
|
||||
"Couldn't set chunk data: {e}. World height: {}",
|
||||
instance.chunks.height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkBatchInfo {
|
||||
pub fn batch_finished(&mut self, batch_size: u32) {
|
||||
if batch_size == 0 {
|
||||
return;
|
||||
}
|
||||
let batch_duration = self.start_time.elapsed();
|
||||
let duration_per_chunk = batch_duration / batch_size;
|
||||
let clamped_duration = Duration::clamp(
|
||||
duration_per_chunk,
|
||||
self.aggregated_duration_per_chunk / 3,
|
||||
self.aggregated_duration_per_chunk * 3,
|
||||
);
|
||||
self.aggregated_duration_per_chunk =
|
||||
((self.aggregated_duration_per_chunk * self.old_samples_weight) + clamped_duration)
|
||||
/ (self.old_samples_weight + 1);
|
||||
self.old_samples_weight = u32::min(49, self.old_samples_weight + 1);
|
||||
}
|
||||
|
||||
pub fn desired_chunks_per_tick(&self) -> f32 {
|
||||
(7000000. / self.aggregated_duration_per_chunk.as_nanos() as f64) as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_chunk_batch_start_event(
|
||||
mut query: Query<&mut ChunkBatchInfo>,
|
||||
mut events: EventReader<ChunkBatchStartEvent>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
if let Ok(mut chunk_batch_info) = query.get_mut(event.entity) {
|
||||
chunk_batch_info.start_time = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_chunk_batch_finished_event(
|
||||
mut query: Query<&mut ChunkBatchInfo>,
|
||||
mut events: EventReader<ChunkBatchFinishedEvent>,
|
||||
mut send_packets: EventWriter<SendPacketEvent>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
if let Ok(mut chunk_batch_info) = query.get_mut(event.entity) {
|
||||
chunk_batch_info.batch_finished(event.batch_size);
|
||||
let desired_chunks_per_tick = chunk_batch_info.desired_chunks_per_tick();
|
||||
send_packets.send(SendPacketEvent::new(
|
||||
event.entity,
|
||||
ServerboundChunkBatchReceived {
|
||||
desired_chunks_per_tick,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChunkReceiveSpeedAccumulator {
|
||||
batch_sizes: Vec<u32>,
|
||||
/// as milliseconds
|
||||
batch_durations: Vec<u32>,
|
||||
index: usize,
|
||||
filled_size: usize,
|
||||
}
|
||||
impl ChunkReceiveSpeedAccumulator {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
batch_sizes: vec![0; capacity],
|
||||
batch_durations: vec![0; capacity],
|
||||
index: 0,
|
||||
filled_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accumulate(&mut self, batch_size: u32, batch_duration: Duration) {
|
||||
self.batch_sizes[self.index] = batch_size;
|
||||
self.batch_durations[self.index] =
|
||||
f32::clamp(batch_duration.as_millis() as f32, 0., 15000.) as u32;
|
||||
self.index = (self.index + 1) % self.batch_sizes.len();
|
||||
if self.filled_size < self.batch_sizes.len() {
|
||||
self.filled_size += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_millis_per_chunk(&self) -> f64 {
|
||||
let mut total_batch_size = 0;
|
||||
let mut total_batch_duration = 0;
|
||||
for i in 0..self.filled_size {
|
||||
total_batch_size += self.batch_sizes[i];
|
||||
total_batch_duration += self.batch_durations[i];
|
||||
}
|
||||
if total_batch_size == 0 {
|
||||
return 0.;
|
||||
}
|
||||
total_batch_duration as f64 / total_batch_size as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChunkBatchInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start_time: Instant::now(),
|
||||
aggregated_duration_per_chunk: Duration::from_millis(2),
|
||||
old_samples_weight: 1,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,29 +14,29 @@ use azalea_core::{
|
|||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{
|
||||
EntityPlugin, EntityUpdateSet, EyeHeight, LocalEntity, Position,
|
||||
indexing::{EntityIdIndex, EntityUuidIndex},
|
||||
metadata::Health,
|
||||
EntityPlugin, EntityUpdateSet, EyeHeight, LocalEntity, Position,
|
||||
};
|
||||
use azalea_physics::PhysicsPlugin;
|
||||
use azalea_protocol::{
|
||||
ServerAddress,
|
||||
common::client_information::ClientInformation,
|
||||
connect::{Connection, ConnectionError, Proxy},
|
||||
packets::{
|
||||
self,
|
||||
self, ClientIntention, ConnectionProtocol, PROTOCOL_VERSION, Packet,
|
||||
config::{ClientboundConfigPacket, ServerboundConfigPacket},
|
||||
game::ServerboundGamePacket,
|
||||
handshake::{
|
||||
s_intention::ServerboundIntention, ClientboundHandshakePacket,
|
||||
ServerboundHandshakePacket,
|
||||
ClientboundHandshakePacket, ServerboundHandshakePacket,
|
||||
s_intention::ServerboundIntention,
|
||||
},
|
||||
login::{
|
||||
s_hello::ServerboundHello, s_key::ServerboundKey,
|
||||
s_login_acknowledged::ServerboundLoginAcknowledged, ClientboundLoginPacket,
|
||||
ClientboundLoginPacket, s_hello::ServerboundHello, s_key::ServerboundKey,
|
||||
s_login_acknowledged::ServerboundLoginAcknowledged,
|
||||
},
|
||||
ClientIntention, ConnectionProtocol, Packet, PROTOCOL_VERSION,
|
||||
},
|
||||
resolver, ServerAddress,
|
||||
resolver,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance};
|
||||
use bevy_app::{App, Plugin, PluginGroup, PluginGroupBuilder, Update};
|
||||
|
@ -61,6 +61,7 @@ use tracing::{debug, error};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
Account, PlayerInfo,
|
||||
attack::{self, AttackPlugin},
|
||||
brand::BrandPlugin,
|
||||
chat::ChatPlugin,
|
||||
|
@ -75,15 +76,14 @@ use crate::{
|
|||
mining::{self, MiningPlugin},
|
||||
movement::{LastSentLookDirection, MovementPlugin, PhysicsState},
|
||||
packet::{
|
||||
login::{self, LoginSendPacketQueue},
|
||||
PacketPlugin,
|
||||
login::{self, InLoginState, LoginSendPacketQueue},
|
||||
},
|
||||
player::retroactively_add_game_profile_component,
|
||||
raw_connection::RawConnection,
|
||||
respawn::RespawnPlugin,
|
||||
task_pool::TaskPoolPlugin,
|
||||
tick_end::TickEndPlugin,
|
||||
Account, PlayerInfo,
|
||||
};
|
||||
|
||||
/// `Client` has the things that a user interacting with the library will want.
|
||||
|
@ -369,7 +369,8 @@ impl Client {
|
|||
let (ecs_packets_tx, mut ecs_packets_rx) = mpsc::unbounded_channel();
|
||||
ecs_lock.lock().entity_mut(entity).insert((
|
||||
LoginSendPacketQueue { tx: ecs_packets_tx },
|
||||
login::IgnoreQueryIds::default(),
|
||||
crate::packet::login::IgnoreQueryIds::default(),
|
||||
InLoginState,
|
||||
));
|
||||
|
||||
// login
|
||||
|
@ -456,6 +457,7 @@ impl Client {
|
|||
p.game_profile
|
||||
);
|
||||
conn.write(ServerboundLoginAcknowledged {}).await?;
|
||||
|
||||
break (conn.config(), p.game_profile);
|
||||
}
|
||||
ClientboundLoginPacket::LoginDisconnect(p) => {
|
||||
|
@ -465,7 +467,7 @@ impl Client {
|
|||
ClientboundLoginPacket::CustomQuery(p) => {
|
||||
debug!("Got custom query {:?}", p);
|
||||
// replying to custom query is done in
|
||||
// packet_handling::login::process_packet_events
|
||||
// packet::login::process_packet_events
|
||||
}
|
||||
ClientboundLoginPacket::CookieRequest(p) => {
|
||||
debug!("Got cookie request {:?}", p);
|
||||
|
@ -484,7 +486,8 @@ impl Client {
|
|||
.lock()
|
||||
.entity_mut(entity)
|
||||
.remove::<login::IgnoreQueryIds>()
|
||||
.remove::<LoginSendPacketQueue>();
|
||||
.remove::<LoginSendPacketQueue>()
|
||||
.remove::<InLoginState>();
|
||||
|
||||
Ok((conn, profile))
|
||||
}
|
||||
|
@ -554,6 +557,11 @@ impl Client {
|
|||
self.query::<Option<&T>>(&mut self.ecs.lock()).cloned()
|
||||
}
|
||||
|
||||
/// Get a resource from the ECS. This will clone the resource and return it.
|
||||
pub fn resource<T: Resource + Clone>(&self) -> T {
|
||||
self.ecs.lock().resource::<T>().clone()
|
||||
}
|
||||
|
||||
/// Get a required component for this client and call the given function.
|
||||
///
|
||||
/// Similar to [`Self::component`], but doesn't clone the component since
|
||||
|
@ -866,6 +874,7 @@ async fn run_schedule_loop(
|
|||
let mut ecs = ecs.lock();
|
||||
|
||||
// if last tick is None or more than 50ms ago, run the GameTick schedule
|
||||
ecs.run_schedule(outer_schedule_label);
|
||||
if last_tick
|
||||
.map(|last_tick| last_tick.elapsed() > Duration::from_millis(50))
|
||||
.unwrap_or(true)
|
||||
|
@ -878,8 +887,6 @@ async fn run_schedule_loop(
|
|||
ecs.run_schedule(GameTick);
|
||||
}
|
||||
|
||||
ecs.run_schedule(outer_schedule_label);
|
||||
|
||||
ecs.clear_trackers();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,14 @@ mod player;
|
|||
mod plugins;
|
||||
pub mod raw_connection;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod test_simulation;
|
||||
|
||||
pub use account::{Account, AccountOpts};
|
||||
pub use azalea_protocol::common::client_information::ClientInformation;
|
||||
pub use client::{
|
||||
start_ecs_runner, Client, DefaultPlugins, InConfigState, JoinError, JoinedClientBundle,
|
||||
LocalPlayerBundle, StartClientOpts, TickBroadcast,
|
||||
Client, DefaultPlugins, InConfigState, JoinError, JoinedClientBundle, LocalPlayerBundle,
|
||||
StartClientOpts, TickBroadcast, start_ecs_runner,
|
||||
};
|
||||
pub use events::Event;
|
||||
pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList};
|
||||
|
|
|
@ -12,8 +12,7 @@ use tokio::sync::mpsc;
|
|||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::Event as AzaleaEvent;
|
||||
use crate::{ClientInformation, PlayerInfo};
|
||||
use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
|
||||
|
||||
/// A component that keeps strong references to our [`PartialInstance`] and
|
||||
/// [`Instance`] for local players.
|
||||
|
@ -97,6 +96,13 @@ pub struct PermissionLevel(pub u8);
|
|||
/// println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// For convenience, `TabList` is also a resource in the ECS.
|
||||
/// It's set to be the same as the tab list for the last client whose tab list
|
||||
/// was updated.
|
||||
/// This means you should avoid using `TabList` as a resource unless you know
|
||||
/// all of your clients will have the same tab list.
|
||||
#[derive(Component, Resource, Clone, Debug, Deref, DerefMut, Default)]
|
||||
pub struct TabList(HashMap<Uuid, PlayerInfo>);
|
||||
|
||||
|
|
271
azalea-client/src/packet_handling/configuration.rs
Normal file
271
azalea-client/src/packet_handling/configuration.rs
Normal file
|
@ -0,0 +1,271 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use azalea_entity::indexing::EntityIdIndex;
|
||||
use azalea_protocol::packets::config::s_finish_configuration::ServerboundFinishConfiguration;
|
||||
use azalea_protocol::packets::config::s_keep_alive::ServerboundKeepAlive;
|
||||
use azalea_protocol::packets::config::s_select_known_packs::ServerboundSelectKnownPacks;
|
||||
use azalea_protocol::packets::config::{
|
||||
self, ClientboundConfigPacket, ServerboundConfigPacket, ServerboundCookieResponse,
|
||||
ServerboundResourcePack,
|
||||
};
|
||||
use azalea_protocol::packets::{ConnectionProtocol, Packet};
|
||||
use azalea_protocol::read::deserialize_packet;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::system::SystemState;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::InstanceHolder;
|
||||
use crate::client::InConfigState;
|
||||
use crate::disconnect::DisconnectEvent;
|
||||
use crate::local_player::Hunger;
|
||||
use crate::packet::game::KeepAliveEvent;
|
||||
use crate::raw_connection::RawConnection;
|
||||
|
||||
#[derive(Event, Debug, Clone)]
|
||||
pub struct ConfigurationEvent {
|
||||
/// The client entity that received the packet.
|
||||
pub entity: Entity,
|
||||
/// The packet that was actually received.
|
||||
pub packet: ClientboundConfigPacket,
|
||||
}
|
||||
|
||||
pub fn send_packet_events(
|
||||
query: Query<(Entity, &RawConnection), With<InConfigState>>,
|
||||
mut packet_events: ResMut<Events<ConfigurationEvent>>,
|
||||
) {
|
||||
// we manually clear and send the events at the beginning of each update
|
||||
// since otherwise it'd cause issues with events in process_packet_events
|
||||
// running twice
|
||||
packet_events.clear();
|
||||
for (player_entity, raw_conn) in &query {
|
||||
let packets_lock = raw_conn.incoming_packet_queue();
|
||||
let mut packets = packets_lock.lock();
|
||||
if !packets.is_empty() {
|
||||
for raw_packet in packets.iter() {
|
||||
let packet = match deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(
|
||||
raw_packet,
|
||||
)) {
|
||||
Ok(packet) => packet,
|
||||
Err(err) => {
|
||||
error!("failed to read packet: {err:?}");
|
||||
debug!("packet bytes: {raw_packet:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
packet_events.send(ConfigurationEvent {
|
||||
entity: player_entity,
|
||||
packet,
|
||||
});
|
||||
}
|
||||
// clear the packets right after we read them
|
||||
packets.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_packet_events(ecs: &mut World) {
|
||||
let mut events_owned = Vec::new();
|
||||
let mut system_state: SystemState<EventReader<ConfigurationEvent>> = SystemState::new(ecs);
|
||||
let mut events = system_state.get_mut(ecs);
|
||||
for ConfigurationEvent {
|
||||
entity: player_entity,
|
||||
packet,
|
||||
} in events.read()
|
||||
{
|
||||
// we do this so `ecs` isn't borrowed for the whole loop
|
||||
events_owned.push((*player_entity, packet.clone()));
|
||||
}
|
||||
for (player_entity, packet) in events_owned {
|
||||
match packet {
|
||||
ClientboundConfigPacket::RegistryData(p) => {
|
||||
let mut system_state: SystemState<Query<&mut InstanceHolder>> =
|
||||
SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let instance_holder = query.get_mut(player_entity).unwrap();
|
||||
let mut instance = instance_holder.instance.write();
|
||||
|
||||
// add the new registry data
|
||||
instance.registries.append(p.registry_id, p.entries);
|
||||
}
|
||||
|
||||
ClientboundConfigPacket::CustomPayload(p) => {
|
||||
debug!("Got custom payload packet {p:?}");
|
||||
}
|
||||
ClientboundConfigPacket::Disconnect(p) => {
|
||||
warn!("Got disconnect packet {p:?}");
|
||||
let mut system_state: SystemState<EventWriter<DisconnectEvent>> =
|
||||
SystemState::new(ecs);
|
||||
let mut disconnect_events = system_state.get_mut(ecs);
|
||||
disconnect_events.send(DisconnectEvent {
|
||||
entity: player_entity,
|
||||
reason: Some(p.reason.clone()),
|
||||
});
|
||||
}
|
||||
ClientboundConfigPacket::FinishConfiguration(p) => {
|
||||
debug!("got FinishConfiguration packet: {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&mut RawConnection>> =
|
||||
SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let mut raw_conn = query.get_mut(player_entity).unwrap();
|
||||
|
||||
raw_conn
|
||||
.write_packet(ServerboundFinishConfiguration)
|
||||
.expect(
|
||||
"we should be in the right state and encoding this packet shouldn't fail",
|
||||
);
|
||||
raw_conn.set_state(ConnectionProtocol::Game);
|
||||
|
||||
// these components are added now that we're going to be in the Game state
|
||||
ecs.entity_mut(player_entity)
|
||||
.remove::<InConfigState>()
|
||||
.insert(crate::JoinedClientBundle {
|
||||
physics_state: crate::PhysicsState::default(),
|
||||
inventory: crate::inventory::Inventory::default(),
|
||||
tab_list: crate::local_player::TabList::default(),
|
||||
current_sequence_number: crate::interact::CurrentSequenceNumber::default(),
|
||||
last_sent_direction: crate::movement::LastSentLookDirection::default(),
|
||||
abilities: crate::local_player::PlayerAbilities::default(),
|
||||
permission_level: crate::local_player::PermissionLevel::default(),
|
||||
hunger: Hunger::default(),
|
||||
chunk_batch_info: crate::chunks::ChunkBatchInfo::default(),
|
||||
|
||||
entity_id_index: EntityIdIndex::default(),
|
||||
|
||||
mining: crate::mining::MineBundle::default(),
|
||||
attack: crate::attack::AttackBundle::default(),
|
||||
|
||||
_local_entity: azalea_entity::LocalEntity,
|
||||
});
|
||||
}
|
||||
ClientboundConfigPacket::KeepAlive(p) => {
|
||||
debug!("Got keep alive packet (in configuration) {p:?} for {player_entity:?}");
|
||||
|
||||
let mut system_state: SystemState<(
|
||||
Query<&RawConnection>,
|
||||
EventWriter<KeepAliveEvent>,
|
||||
)> = SystemState::new(ecs);
|
||||
let (query, mut keepalive_events) = system_state.get_mut(ecs);
|
||||
let raw_conn = query.get(player_entity).unwrap();
|
||||
|
||||
keepalive_events.send(KeepAliveEvent {
|
||||
entity: player_entity,
|
||||
id: p.id,
|
||||
});
|
||||
raw_conn
|
||||
.write_packet(ServerboundKeepAlive { id: p.id })
|
||||
.unwrap();
|
||||
}
|
||||
ClientboundConfigPacket::Ping(p) => {
|
||||
debug!("Got ping packet {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let raw_conn = query.get_mut(player_entity).unwrap();
|
||||
|
||||
raw_conn
|
||||
.write_packet(config::s_pong::ServerboundPong { id: p.id })
|
||||
.unwrap();
|
||||
}
|
||||
ClientboundConfigPacket::ResourcePackPush(p) => {
|
||||
debug!("Got resource pack packet {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let raw_conn = query.get_mut(player_entity).unwrap();
|
||||
|
||||
// always accept resource pack
|
||||
raw_conn
|
||||
.write_packet(ServerboundResourcePack {
|
||||
id: p.id,
|
||||
action: config::s_resource_pack::Action::Accepted,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
ClientboundConfigPacket::ResourcePackPop(_) => {
|
||||
// we can ignore this
|
||||
}
|
||||
ClientboundConfigPacket::UpdateEnabledFeatures(p) => {
|
||||
debug!("Got update enabled features packet {p:?}");
|
||||
}
|
||||
ClientboundConfigPacket::UpdateTags(_p) => {
|
||||
debug!("Got update tags packet");
|
||||
}
|
||||
ClientboundConfigPacket::CookieRequest(p) => {
|
||||
debug!("Got cookie request packet {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let raw_conn = query.get_mut(player_entity).unwrap();
|
||||
|
||||
raw_conn
|
||||
.write_packet(ServerboundCookieResponse {
|
||||
key: p.key,
|
||||
// cookies aren't implemented
|
||||
payload: None,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
ClientboundConfigPacket::ResetChat(p) => {
|
||||
debug!("Got reset chat packet {p:?}");
|
||||
}
|
||||
ClientboundConfigPacket::StoreCookie(p) => {
|
||||
debug!("Got store cookie packet {p:?}");
|
||||
}
|
||||
ClientboundConfigPacket::Transfer(p) => {
|
||||
debug!("Got transfer packet {p:?}");
|
||||
}
|
||||
ClientboundConfigPacket::SelectKnownPacks(p) => {
|
||||
debug!("Got select known packs packet {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&RawConnection>> = SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let raw_conn = query.get_mut(player_entity).unwrap();
|
||||
|
||||
// resource pack management isn't implemented
|
||||
raw_conn
|
||||
.write_packet(ServerboundSelectKnownPacks {
|
||||
known_packs: vec![],
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
ClientboundConfigPacket::ServerLinks(_) => {}
|
||||
ClientboundConfigPacket::CustomReportDetails(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event for sending a packet to the server while we're in the
|
||||
/// `configuration` state.
|
||||
#[derive(Event)]
|
||||
pub struct SendConfigurationEvent {
|
||||
pub sent_by: Entity,
|
||||
pub packet: ServerboundConfigPacket,
|
||||
}
|
||||
impl SendConfigurationEvent {
|
||||
pub fn new(sent_by: Entity, packet: impl Packet<ServerboundConfigPacket>) -> Self {
|
||||
let packet = packet.into_variant();
|
||||
Self { sent_by, packet }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_send_packet_event(
|
||||
mut send_packet_events: EventReader<SendConfigurationEvent>,
|
||||
mut query: Query<(&mut RawConnection, Option<&InConfigState>)>,
|
||||
) {
|
||||
for event in send_packet_events.read() {
|
||||
if let Ok((raw_conn, in_configuration_state)) = query.get_mut(event.sent_by) {
|
||||
if in_configuration_state.is_none() {
|
||||
error!(
|
||||
"Tried to send a configuration packet {:?} while not in configuration state",
|
||||
event.packet
|
||||
);
|
||||
continue;
|
||||
}
|
||||
debug!("Sending packet: {:?}", event.packet);
|
||||
if let Err(e) = raw_conn.write_packet(event.packet.clone()) {
|
||||
error!("Failed to send packet: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1585
azalea-client/src/packet_handling/game.rs
Normal file
1585
azalea-client/src/packet_handling/game.rs
Normal file
File diff suppressed because it is too large
Load diff
114
azalea-client/src/packet_handling/login.rs
Normal file
114
azalea-client/src/packet_handling/login.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
// login packets aren't actually handled here because compression/encryption
|
||||
// would make packet handling a lot messier
|
||||
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use azalea_protocol::packets::{
|
||||
Packet,
|
||||
login::{
|
||||
ClientboundLoginPacket, ServerboundLoginPacket,
|
||||
s_custom_query_answer::ServerboundCustomQueryAnswer,
|
||||
},
|
||||
};
|
||||
use bevy_ecs::{prelude::*, system::SystemState};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
// this struct is defined here anyways though so it's consistent with the other
|
||||
// ones
|
||||
|
||||
/// An event that's sent when we receive a login packet from the server. Note
|
||||
/// that if you want to handle this in a system, you must add
|
||||
/// `.before(azalea::packet::login::process_packet_events)` to it
|
||||
/// because that system clears the events.
|
||||
#[derive(Event, Debug, Clone)]
|
||||
pub struct LoginPacketEvent {
|
||||
/// The client entity that received the packet.
|
||||
pub entity: Entity,
|
||||
/// The packet that was actually received.
|
||||
pub packet: Arc<ClientboundLoginPacket>,
|
||||
}
|
||||
|
||||
/// Event for sending a login packet to the server.
|
||||
#[derive(Event)]
|
||||
pub struct SendLoginPacketEvent {
|
||||
pub entity: Entity,
|
||||
pub packet: ServerboundLoginPacket,
|
||||
}
|
||||
impl SendLoginPacketEvent {
|
||||
pub fn new(entity: Entity, packet: impl Packet<ServerboundLoginPacket>) -> Self {
|
||||
let packet = packet.into_variant();
|
||||
Self { entity, packet }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct LoginSendPacketQueue {
|
||||
pub tx: mpsc::UnboundedSender<ServerboundLoginPacket>,
|
||||
}
|
||||
|
||||
/// A marker component for local players that are currently in the
|
||||
/// `login` state.
|
||||
#[derive(Component, Clone, Debug)]
|
||||
pub struct InLoginState;
|
||||
|
||||
pub fn handle_send_packet_event(
|
||||
mut send_packet_events: EventReader<SendLoginPacketEvent>,
|
||||
mut query: Query<&mut LoginSendPacketQueue>,
|
||||
) {
|
||||
for event in send_packet_events.read() {
|
||||
if let Ok(queue) = query.get_mut(event.entity) {
|
||||
let _ = queue.tx.send(event.packet.clone());
|
||||
} else {
|
||||
error!("Sent SendPacketEvent for entity that doesn't have a LoginSendPacketQueue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugins can add to this set if they want to handle a custom query packet
|
||||
/// themselves. This component removed after the login state ends.
|
||||
#[derive(Component, Default, Debug, Deref, DerefMut)]
|
||||
pub struct IgnoreQueryIds(HashSet<u32>);
|
||||
|
||||
pub fn process_packet_events(ecs: &mut World) {
|
||||
let mut events_owned = Vec::new();
|
||||
let mut system_state: SystemState<ResMut<Events<LoginPacketEvent>>> = SystemState::new(ecs);
|
||||
let mut events = system_state.get_mut(ecs);
|
||||
for LoginPacketEvent {
|
||||
entity: player_entity,
|
||||
packet,
|
||||
} in events.drain()
|
||||
{
|
||||
// we do this so `ecs` isn't borrowed for the whole loop
|
||||
events_owned.push((player_entity, packet));
|
||||
}
|
||||
for (player_entity, packet) in events_owned {
|
||||
#[allow(clippy::single_match)]
|
||||
match packet.as_ref() {
|
||||
ClientboundLoginPacket::CustomQuery(p) => {
|
||||
let mut system_state: SystemState<(
|
||||
EventWriter<SendLoginPacketEvent>,
|
||||
Query<&IgnoreQueryIds>,
|
||||
)> = SystemState::new(ecs);
|
||||
let (mut send_packet_events, query) = system_state.get_mut(ecs);
|
||||
|
||||
let ignore_query_ids = query.get(player_entity).ok().map(|x| x.0.clone());
|
||||
if let Some(ignore_query_ids) = ignore_query_ids {
|
||||
if ignore_query_ids.contains(&p.transaction_id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
send_packet_events.send(SendLoginPacketEvent::new(
|
||||
player_entity,
|
||||
ServerboundCustomQueryAnswer {
|
||||
transaction_id: p.transaction_id,
|
||||
data: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,19 +3,20 @@
|
|||
use std::io;
|
||||
|
||||
use azalea_protocol::{
|
||||
ServerAddress,
|
||||
connect::{Connection, ConnectionError, Proxy},
|
||||
packets::{
|
||||
ClientIntention, PROTOCOL_VERSION,
|
||||
handshake::{
|
||||
s_intention::ServerboundIntention, ClientboundHandshakePacket,
|
||||
ServerboundHandshakePacket,
|
||||
ClientboundHandshakePacket, ServerboundHandshakePacket,
|
||||
s_intention::ServerboundIntention,
|
||||
},
|
||||
status::{
|
||||
c_status_response::ClientboundStatusResponse,
|
||||
s_status_request::ServerboundStatusRequest, ClientboundStatusPacket,
|
||||
ClientboundStatusPacket, c_status_response::ClientboundStatusResponse,
|
||||
s_status_request::ServerboundStatusRequest,
|
||||
},
|
||||
ClientIntention, PROTOCOL_VERSION,
|
||||
},
|
||||
resolver, ServerAddress,
|
||||
resolver,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use bevy_ecs::{
|
|||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{packet::game::AddPlayerEvent, GameProfileComponent};
|
||||
use crate::{GameProfileComponent, packet::game::AddPlayerEvent};
|
||||
|
||||
/// A player in the tab list.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use azalea_core::{game_type::GameMode, tick::GameTick};
|
||||
use azalea_entity::{
|
||||
Attributes, Physics,
|
||||
metadata::{ShiftKeyDown, Sprinting},
|
||||
update_bounding_box, Attributes, Physics,
|
||||
update_bounding_box,
|
||||
};
|
||||
use azalea_physics::PhysicsSet;
|
||||
use azalea_protocol::packets::game::s_interact::{self, ServerboundInteract};
|
||||
|
@ -10,9 +11,10 @@ use bevy_app::{App, Plugin, Update};
|
|||
use bevy_ecs::prelude::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use super::packet::game::SendPacketEvent;
|
||||
use crate::{
|
||||
interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
|
||||
packet::game::SendPacketEvent, respawn::perform_respawn, Client,
|
||||
Client, interact::SwingArmEvent, local_player::LocalGameMode, movement::MoveEventsSet,
|
||||
respawn::perform_respawn,
|
||||
};
|
||||
|
||||
pub struct AttackPlugin;
|
||||
|
|
|
@ -9,24 +9,27 @@ use azalea_protocol::{
|
|||
};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{client::InConfigState, packet::config::SendConfigPacketEvent};
|
||||
use super::packet::config::SendConfigPacketEvent;
|
||||
use crate::packet::login::InLoginState;
|
||||
|
||||
pub struct BrandPlugin;
|
||||
impl Plugin for BrandPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
handle_in_configuration_state.before(crate::packet::config::handle_send_packet_event),
|
||||
handle_end_login_state.before(crate::packet::config::handle_send_packet_event),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_in_configuration_state(
|
||||
query: Query<(Entity, &ClientInformation), Added<InConfigState>>,
|
||||
fn handle_end_login_state(
|
||||
mut removed: RemovedComponents<InLoginState>,
|
||||
query: Query<&ClientInformation>,
|
||||
mut send_packet_events: EventWriter<SendConfigPacketEvent>,
|
||||
) {
|
||||
for (entity, client_information) in query.iter() {
|
||||
for entity in removed.read() {
|
||||
let mut brand_data = Vec::new();
|
||||
// azalea pretends to be vanilla everywhere else so it makes sense to lie here
|
||||
// too
|
||||
|
@ -39,6 +42,17 @@ pub fn handle_in_configuration_state(
|
|||
},
|
||||
));
|
||||
|
||||
let client_information = match query.get(entity).ok() {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
warn!(
|
||||
"ClientInformation component was not set before leaving login state, using a default"
|
||||
);
|
||||
&ClientInformation::default()
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Writing ClientInformation while in config state: {client_information:?}");
|
||||
send_packet_events.send(SendConfigPacketEvent::new(
|
||||
entity,
|
||||
ServerboundClientInformation {
|
||||
|
|
|
@ -16,10 +16,11 @@ use bevy_ecs::{
|
|||
prelude::Event,
|
||||
schedule::IntoSystemConfigs,
|
||||
};
|
||||
use handler::{handle_send_chat_kind_event, SendChatKindEvent};
|
||||
use handler::{SendChatKindEvent, handle_send_chat_kind_event};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{client::Client, packet::game::handle_sendpacketevent};
|
||||
use super::packet::game::handle_outgoing_packets;
|
||||
use crate::client::Client;
|
||||
|
||||
pub struct ChatPlugin;
|
||||
impl Plugin for ChatPlugin {
|
||||
|
@ -31,7 +32,7 @@ impl Plugin for ChatPlugin {
|
|||
Update,
|
||||
(
|
||||
handle_send_chat_event,
|
||||
handle_send_chat_kind_event.after(handle_sendpacketevent),
|
||||
handle_send_chat_kind_event.after(handle_outgoing_packets),
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
@ -161,6 +162,9 @@ impl Client {
|
|||
|
||||
/// Send a command packet to the server. The `command` argument should not
|
||||
/// include the slash at the front.
|
||||
///
|
||||
/// You can also just use [`Client::chat`] and start your message with a `/`
|
||||
/// to send a command.
|
||||
pub fn send_command_packet(&self, command: &str) {
|
||||
self.ecs.lock().send_event(SendChatKindEvent {
|
||||
entity: self.entity,
|
||||
|
@ -173,8 +177,8 @@ impl Client {
|
|||
/// Send a message in chat.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use azalea_client::{Client, Event};
|
||||
/// # async fn handle(bot: Client, event: Event) -> anyhow::Result<()> {
|
||||
/// # use azalea_client::Client;
|
||||
/// # async fn example(bot: Client) -> anyhow::Result<()> {
|
||||
/// bot.chat("Hello, world!");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
|
|
@ -18,12 +18,10 @@ use bevy_ecs::prelude::*;
|
|||
use simdnbt::owned::BaseNbt;
|
||||
use tracing::{error, trace};
|
||||
|
||||
use super::packet::game::handle_outgoing_packets;
|
||||
use crate::{
|
||||
interact::handle_block_interact_event,
|
||||
inventory::InventorySet,
|
||||
packet::game::{handle_sendpacketevent, SendPacketEvent},
|
||||
respawn::perform_respawn,
|
||||
InstanceHolder,
|
||||
InstanceHolder, interact::handle_block_interact_event, inventory::InventorySet,
|
||||
packet::game::SendPacketEvent, respawn::perform_respawn,
|
||||
};
|
||||
|
||||
pub struct ChunksPlugin;
|
||||
|
@ -37,7 +35,7 @@ impl Plugin for ChunksPlugin {
|
|||
handle_chunk_batch_finished_event,
|
||||
)
|
||||
.chain()
|
||||
.before(handle_sendpacketevent)
|
||||
.before(handle_outgoing_packets)
|
||||
.before(InventorySet)
|
||||
.before(handle_block_interact_event)
|
||||
.before(perform_respawn),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Disconnect a client from the server.
|
||||
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_entity::LocalEntity;
|
||||
use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
|
@ -15,7 +15,10 @@ use bevy_ecs::{
|
|||
use derive_more::Deref;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{client::JoinedClientBundle, events::LocalPlayerEvents, raw_connection::RawConnection};
|
||||
use crate::{
|
||||
InstanceHolder, client::JoinedClientBundle, events::LocalPlayerEvents,
|
||||
raw_connection::RawConnection,
|
||||
};
|
||||
|
||||
pub struct DisconnectPlugin;
|
||||
impl Plugin for DisconnectPlugin {
|
||||
|
@ -39,23 +42,35 @@ pub struct DisconnectEvent {
|
|||
pub reason: Option<FormattedText>,
|
||||
}
|
||||
|
||||
/// System that removes the [`JoinedClientBundle`] from the entity when it
|
||||
/// receives a [`DisconnectEvent`].
|
||||
/// A system that removes the several components from our clients when they get
|
||||
/// a [`DisconnectEvent`].
|
||||
pub fn remove_components_from_disconnected_players(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<DisconnectEvent>,
|
||||
mut loaded_by_query: Query<&mut azalea_entity::LoadedBy>,
|
||||
) {
|
||||
for DisconnectEvent { entity, .. } in events.read() {
|
||||
trace!("Got DisconnectEvent for {entity:?}");
|
||||
commands
|
||||
.entity(*entity)
|
||||
.remove::<JoinedClientBundle>()
|
||||
.remove::<EntityBundle>()
|
||||
.remove::<InstanceHolder>()
|
||||
.remove::<PlayerMetadataBundle>()
|
||||
.remove::<InLoadedChunk>()
|
||||
// this makes it close the tcp connection
|
||||
.remove::<RawConnection>()
|
||||
// swarm detects when this tx gets dropped to fire SwarmEvent::Disconnect
|
||||
.remove::<LocalPlayerEvents>();
|
||||
// note that we don't remove the client from the ECS, so if they decide
|
||||
// to reconnect they'll keep their state
|
||||
|
||||
// now we have to remove ourselves from the LoadedBy for every entity.
|
||||
// in theory this could be inefficient if we have massive swarms... but in
|
||||
// practice this is fine.
|
||||
for mut loaded_by in &mut loaded_by_query.iter_mut() {
|
||||
loaded_by.remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use azalea_chat::FormattedText;
|
|||
use azalea_core::tick::GameTick;
|
||||
use azalea_entity::Dead;
|
||||
use azalea_protocol::packets::game::{
|
||||
c_player_combat_kill::ClientboundPlayerCombatKill, ClientboundGamePacket,
|
||||
ClientboundGamePacket, c_player_combat_kill::ClientboundPlayerCombatKill,
|
||||
};
|
||||
use azalea_world::{InstanceName, MinecraftEntityId};
|
||||
use bevy_app::{App, Plugin, PreUpdate, Update};
|
||||
|
@ -22,13 +22,13 @@ use derive_more::{Deref, DerefMut};
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
PlayerInfo,
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
disconnect::DisconnectEvent,
|
||||
packet::game::{
|
||||
AddPlayerEvent, DeathEvent, KeepAliveEvent, ReceivePacketEvent, RemovePlayerEvent,
|
||||
UpdatePlayerEvent,
|
||||
},
|
||||
PlayerInfo,
|
||||
};
|
||||
|
||||
// (for contributors):
|
||||
|
|
|
@ -8,9 +8,9 @@ use azalea_core::{
|
|||
position::{BlockPos, Vec3},
|
||||
};
|
||||
use azalea_entity::{
|
||||
clamp_look_direction, view_vector, Attributes, EyeHeight, LocalEntity, LookDirection, Position,
|
||||
Attributes, EyeHeight, LocalEntity, LookDirection, Position, clamp_look_direction, view_vector,
|
||||
};
|
||||
use azalea_inventory::{components, ItemStack, ItemStackData};
|
||||
use azalea_inventory::{ItemStack, ItemStackData, components};
|
||||
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||
use azalea_protocol::packets::game::{
|
||||
s_interact::InteractionHand,
|
||||
|
@ -30,14 +30,15 @@ use bevy_ecs::{
|
|||
use derive_more::{Deref, DerefMut};
|
||||
use tracing::warn;
|
||||
|
||||
use super::packet::game::handle_outgoing_packets;
|
||||
use crate::{
|
||||
Client,
|
||||
attack::handle_attack_event,
|
||||
inventory::{Inventory, InventorySet},
|
||||
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
movement::MoveEventsSet,
|
||||
packet::game::{handle_sendpacketevent, SendPacketEvent},
|
||||
packet::game::SendPacketEvent,
|
||||
respawn::perform_respawn,
|
||||
Client,
|
||||
};
|
||||
|
||||
/// A plugin that allows clients to interact with blocks in the world.
|
||||
|
@ -54,7 +55,7 @@ impl Plugin for InteractPlugin {
|
|||
handle_block_interact_event,
|
||||
handle_swing_arm_event,
|
||||
)
|
||||
.before(handle_sendpacketevent)
|
||||
.before(handle_outgoing_packets)
|
||||
.after(InventorySet)
|
||||
.after(perform_respawn)
|
||||
.after(handle_attack_event)
|
||||
|
@ -245,15 +246,16 @@ pub fn check_is_interaction_restricted(
|
|||
// way of modifying that
|
||||
|
||||
let held_item = inventory.held_item();
|
||||
if let ItemStack::Present(item) = &held_item {
|
||||
let block = instance.chunks.get_block_state(block_pos);
|
||||
let Some(block) = block else {
|
||||
// block isn't loaded so just say that it is restricted
|
||||
return true;
|
||||
};
|
||||
check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
|
||||
} else {
|
||||
true
|
||||
match &held_item {
|
||||
ItemStack::Present(item) => {
|
||||
let block = instance.chunks.get_block_state(block_pos);
|
||||
let Some(block) = block else {
|
||||
// block isn't loaded so just say that it is restricted
|
||||
return true;
|
||||
};
|
||||
check_block_can_be_broken_by_item_in_adventure_mode(item, &block)
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
GameMode::Spectator => true,
|
||||
|
|
|
@ -25,11 +25,9 @@ use bevy_ecs::{
|
|||
};
|
||||
use tracing::warn;
|
||||
|
||||
use super::packet::game::handle_outgoing_packets;
|
||||
use crate::{
|
||||
local_player::PlayerAbilities,
|
||||
packet::game::{handle_sendpacketevent, SendPacketEvent},
|
||||
respawn::perform_respawn,
|
||||
Client,
|
||||
Client, local_player::PlayerAbilities, packet::game::SendPacketEvent, respawn::perform_respawn,
|
||||
};
|
||||
|
||||
pub struct InventoryPlugin;
|
||||
|
@ -48,7 +46,7 @@ impl Plugin for InventoryPlugin {
|
|||
handle_menu_opened_event,
|
||||
handle_set_container_content_event,
|
||||
handle_container_click_event,
|
||||
handle_container_close_event.before(handle_sendpacketevent),
|
||||
handle_container_close_event.before(handle_outgoing_packets),
|
||||
handle_client_side_close_container_event,
|
||||
)
|
||||
.chain()
|
||||
|
@ -124,10 +122,9 @@ impl Inventory {
|
|||
///
|
||||
/// Use [`Self::menu_mut`] if you need a mutable reference.
|
||||
pub fn menu(&self) -> &azalea_inventory::Menu {
|
||||
if let Some(menu) = &self.container_menu {
|
||||
menu
|
||||
} else {
|
||||
&self.inventory_menu
|
||||
match &self.container_menu {
|
||||
Some(menu) => menu,
|
||||
_ => &self.inventory_menu,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,10 +134,9 @@ impl Inventory {
|
|||
///
|
||||
/// Use [`Self::menu`] if you don't need a mutable reference.
|
||||
pub fn menu_mut(&mut self) -> &mut azalea_inventory::Menu {
|
||||
if let Some(menu) = &mut self.container_menu {
|
||||
menu
|
||||
} else {
|
||||
&mut self.inventory_menu
|
||||
match &mut self.container_menu {
|
||||
Some(menu) => menu,
|
||||
_ => &mut self.inventory_menu,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,10 +282,9 @@ impl Inventory {
|
|||
carried_count -= new_carried.count - slot_item_count;
|
||||
// we have to inline self.menu_mut() here to avoid the borrow checker
|
||||
// complaining
|
||||
let menu = if let Some(menu) = &mut self.container_menu {
|
||||
menu
|
||||
} else {
|
||||
&mut self.inventory_menu
|
||||
let menu = match &mut self.container_menu {
|
||||
Some(menu) => menu,
|
||||
_ => &mut self.inventory_menu,
|
||||
};
|
||||
*menu.slot_mut(slot_index as usize).unwrap() =
|
||||
ItemStack::Present(new_carried);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use azalea_block::{fluid_state::FluidState, Block, BlockState};
|
||||
use azalea_block::{Block, BlockState, fluid_state::FluidState};
|
||||
use azalea_core::{direction::Direction, game_type::GameMode, position::BlockPos, tick::GameTick};
|
||||
use azalea_entity::{mining::get_mine_progress, FluidOnEyes, Physics};
|
||||
use azalea_entity::{FluidOnEyes, Physics, mining::get_mine_progress};
|
||||
use azalea_inventory::ItemStack;
|
||||
use azalea_physics::PhysicsSet;
|
||||
use azalea_protocol::packets::game::s_player_action::{self, ServerboundPlayerAction};
|
||||
|
@ -10,15 +10,15 @@ use bevy_ecs::prelude::*;
|
|||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
Client,
|
||||
interact::{
|
||||
can_use_game_master_blocks, check_is_interaction_restricted, CurrentSequenceNumber,
|
||||
HitResultComponent, SwingArmEvent,
|
||||
CurrentSequenceNumber, HitResultComponent, SwingArmEvent, can_use_game_master_blocks,
|
||||
check_is_interaction_restricted,
|
||||
},
|
||||
inventory::{Inventory, InventorySet},
|
||||
local_player::{LocalGameMode, PermissionLevel, PlayerAbilities},
|
||||
movement::MoveEventsSet,
|
||||
packet::game::SendPacketEvent,
|
||||
Client,
|
||||
};
|
||||
|
||||
/// A plugin that allows clients to break blocks in the world.
|
||||
|
|
|
@ -2,18 +2,18 @@ use std::backtrace::Backtrace;
|
|||
|
||||
use azalea_core::position::Vec3;
|
||||
use azalea_core::tick::GameTick;
|
||||
use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
|
||||
use azalea_entity::{Attributes, Jumping, metadata::Sprinting};
|
||||
use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
|
||||
use azalea_physics::{ai_step, PhysicsSet};
|
||||
use azalea_physics::{PhysicsSet, ai_step};
|
||||
use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
|
||||
use azalea_protocol::packets::{
|
||||
Packet,
|
||||
game::{
|
||||
s_move_player_pos::ServerboundMovePlayerPos,
|
||||
s_move_player_pos_rot::ServerboundMovePlayerPosRot,
|
||||
s_move_player_rot::ServerboundMovePlayerRot,
|
||||
s_move_player_status_only::ServerboundMovePlayerStatusOnly,
|
||||
},
|
||||
Packet,
|
||||
};
|
||||
use azalea_world::{MinecraftEntityId, MoveEntityError};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
|
|
|
@ -8,8 +8,8 @@ use azalea_core::resource_location::ResourceLocation;
|
|||
use azalea_entity::LocalEntity;
|
||||
use azalea_protocol::{
|
||||
packets::{
|
||||
game::{ClientboundGamePacket, ClientboundPlayerCombatKill, ServerboundGamePacket},
|
||||
Packet,
|
||||
game::{ClientboundGamePacket, ClientboundPlayerCombatKill, ServerboundGamePacket},
|
||||
},
|
||||
read::deserialize_packet,
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ use parking_lot::RwLock;
|
|||
use tracing::{debug, error};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{raw_connection::RawConnection, PlayerInfo};
|
||||
use crate::{PlayerInfo, raw_connection::RawConnection};
|
||||
|
||||
/// An event that's sent when we receive a packet.
|
||||
/// ```
|
||||
|
@ -62,7 +62,7 @@ impl SendPacketEvent {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_sendpacketevent(
|
||||
pub fn handle_outgoing_packets(
|
||||
mut send_packet_events: EventReader<SendPacketEvent>,
|
||||
mut query: Query<&mut RawConnection>,
|
||||
) {
|
||||
|
|
|
@ -6,13 +6,12 @@ use azalea_core::{
|
|||
game_type::GameMode,
|
||||
math,
|
||||
position::{ChunkPos, Vec3},
|
||||
resource_location::ResourceLocation,
|
||||
};
|
||||
use azalea_entity::{
|
||||
indexing::{EntityIdIndex, EntityUuidIndex},
|
||||
metadata::{apply_metadata, Health},
|
||||
Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection,
|
||||
Physics, Position, RelativeEntityUpdate,
|
||||
indexing::{EntityIdIndex, EntityUuidIndex},
|
||||
metadata::{Health, apply_metadata},
|
||||
};
|
||||
use azalea_protocol::packets::game::*;
|
||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
||||
|
@ -21,6 +20,7 @@ pub use events::*;
|
|||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
ClientInformation, PlayerInfo,
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
chunks, declare_packet_handlers,
|
||||
disconnect::DisconnectEvent,
|
||||
|
@ -32,7 +32,6 @@ use crate::{
|
|||
},
|
||||
movement::{KnockbackEvent, KnockbackType},
|
||||
packet::as_system,
|
||||
ClientInformation, PlayerInfo,
|
||||
};
|
||||
|
||||
pub fn process_packet_events(ecs: &mut World) {
|
||||
|
@ -239,100 +238,92 @@ impl GamePacketHandler<'_> {
|
|||
mut instance_holder,
|
||||
) = query.get_mut(self.player).unwrap();
|
||||
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
|
||||
if let Some(mut instance_name) = instance_name {
|
||||
*instance_name = instance_name.clone();
|
||||
} else {
|
||||
commands
|
||||
.entity(self.player)
|
||||
.insert(InstanceName(new_instance_name.clone()));
|
||||
}
|
||||
|
||||
let Some((_dimension_type, dimension_data)) = p
|
||||
.common
|
||||
.dimension_type(&instance_holder.instance.read().registries)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// add this world to the instance_container (or don't if it's already
|
||||
// there)
|
||||
let weak_instance = instance_container.insert(
|
||||
new_instance_name.clone(),
|
||||
dimension_data.height,
|
||||
dimension_data.min_y,
|
||||
&instance_holder.instance.read().registries,
|
||||
);
|
||||
instance_loaded_events.send(InstanceLoadedEvent {
|
||||
entity: self.player,
|
||||
name: new_instance_name.clone(),
|
||||
instance: Arc::downgrade(&weak_instance),
|
||||
});
|
||||
|
||||
// set the partial_world to an empty world
|
||||
// (when we add chunks or entities those will be in the
|
||||
// instance_container)
|
||||
|
||||
*instance_holder.partial_instance.write() = PartialInstance::new(
|
||||
azalea_world::chunk_storage::calculate_chunk_storage_range(
|
||||
client_information.view_distance.into(),
|
||||
),
|
||||
// this argument makes it so other clients don't update this player entity
|
||||
// in a shared instance
|
||||
Some(self.player),
|
||||
);
|
||||
{
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
|
||||
if let Some(mut instance_name) = instance_name {
|
||||
*instance_name = instance_name.clone();
|
||||
} else {
|
||||
commands
|
||||
.entity(self.player)
|
||||
.insert(InstanceName(new_instance_name.clone()));
|
||||
let map = instance_holder.instance.read().registries.map.clone();
|
||||
let new_registries = &mut weak_instance.write().registries;
|
||||
// add the registries from this instance to the weak instance
|
||||
for (registry_name, registry) in map {
|
||||
new_registries.map.insert(registry_name, registry);
|
||||
}
|
||||
}
|
||||
instance_holder.instance = weak_instance;
|
||||
|
||||
let Some(dimension_type_element) =
|
||||
instance_holder.instance.read().registries.dimension_type()
|
||||
else {
|
||||
error!("Server didn't send dimension type registry, can't log in");
|
||||
return;
|
||||
};
|
||||
let entity_bundle = EntityBundle::new(
|
||||
game_profile.uuid,
|
||||
Vec3::default(),
|
||||
azalea_registry::EntityKind::Player,
|
||||
new_instance_name,
|
||||
);
|
||||
let entity_id = p.player_id;
|
||||
// insert our components into the ecs :)
|
||||
commands.entity(self.player).insert((
|
||||
entity_id,
|
||||
LocalGameMode {
|
||||
current: p.common.game_type,
|
||||
previous: p.common.previous_game_type.into(),
|
||||
},
|
||||
entity_bundle,
|
||||
));
|
||||
|
||||
let dimension_name = ResourceLocation::new(&p.common.dimension.to_string());
|
||||
azalea_entity::indexing::add_entity_to_indexes(
|
||||
entity_id,
|
||||
self.player,
|
||||
Some(game_profile.uuid),
|
||||
&mut entity_id_index,
|
||||
&mut entity_uuid_index,
|
||||
&mut instance_holder.instance.write(),
|
||||
);
|
||||
|
||||
let Some(dimension) = dimension_type_element.map.get(&dimension_name) else {
|
||||
error!("No dimension_type with name {dimension_name}");
|
||||
return;
|
||||
};
|
||||
|
||||
// add this world to the instance_container (or don't if it's already
|
||||
// there)
|
||||
let weak_instance = instance_container.insert(
|
||||
new_instance_name.clone(),
|
||||
dimension.height,
|
||||
dimension.min_y,
|
||||
);
|
||||
instance_loaded_events.send(InstanceLoadedEvent {
|
||||
entity: self.player,
|
||||
name: new_instance_name.clone(),
|
||||
instance: Arc::downgrade(&weak_instance),
|
||||
});
|
||||
|
||||
// set the partial_world to an empty world
|
||||
// (when we add chunks or entities those will be in the
|
||||
// instance_container)
|
||||
|
||||
*instance_holder.partial_instance.write() = PartialInstance::new(
|
||||
azalea_world::chunk_storage::calculate_chunk_storage_range(
|
||||
client_information.view_distance.into(),
|
||||
),
|
||||
// this argument makes it so other clients don't update this player entity
|
||||
// in a shared instance
|
||||
Some(self.player),
|
||||
);
|
||||
{
|
||||
let map = instance_holder.instance.read().registries.map.clone();
|
||||
let new_registries = &mut weak_instance.write().registries;
|
||||
// add the registries from this instance to the weak instance
|
||||
for (registry_name, registry) in map {
|
||||
new_registries.map.insert(registry_name, registry);
|
||||
}
|
||||
}
|
||||
instance_holder.instance = weak_instance;
|
||||
|
||||
let entity_bundle = EntityBundle::new(
|
||||
game_profile.uuid,
|
||||
Vec3::default(),
|
||||
azalea_registry::EntityKind::Player,
|
||||
new_instance_name,
|
||||
);
|
||||
let entity_id = p.player_id;
|
||||
// insert our components into the ecs :)
|
||||
commands.entity(self.player).insert((
|
||||
entity_id,
|
||||
LocalGameMode {
|
||||
current: p.common.game_type,
|
||||
previous: p.common.previous_game_type.into(),
|
||||
},
|
||||
entity_bundle,
|
||||
));
|
||||
|
||||
azalea_entity::indexing::add_entity_to_indexes(
|
||||
entity_id,
|
||||
self.player,
|
||||
Some(game_profile.uuid),
|
||||
&mut entity_id_index,
|
||||
&mut entity_uuid_index,
|
||||
&mut instance_holder.instance.write(),
|
||||
);
|
||||
|
||||
// update or insert loaded_by
|
||||
if let Some(mut loaded_by) = loaded_by {
|
||||
loaded_by.insert(self.player);
|
||||
} else {
|
||||
commands
|
||||
.entity(self.player)
|
||||
.insert(LoadedBy(HashSet::from_iter(vec![self.player])));
|
||||
}
|
||||
// update or insert loaded_by
|
||||
if let Some(mut loaded_by) = loaded_by {
|
||||
loaded_by.insert(self.player);
|
||||
} else {
|
||||
commands
|
||||
.entity(self.player)
|
||||
.insert(LoadedBy(HashSet::from_iter(vec![self.player])));
|
||||
}
|
||||
|
||||
// send the client information that we have set
|
||||
|
@ -340,11 +331,8 @@ impl GamePacketHandler<'_> {
|
|||
"Sending client information because login: {:?}",
|
||||
client_information
|
||||
);
|
||||
send_packet_events.send(SendPacketEvent::new(
|
||||
self.player,
|
||||
ServerboundClientInformation {
|
||||
information: client_information.clone(),
|
||||
},
|
||||
send_packet_events.send(SendPacketEvent::new(self.player,
|
||||
azalea_protocol::packets::game::s_client_information::ServerboundClientInformation { information: client_information.clone() },
|
||||
));
|
||||
},
|
||||
);
|
||||
|
@ -447,11 +435,7 @@ impl GamePacketHandler<'_> {
|
|||
**last_sent_position = **position;
|
||||
|
||||
fn apply_change<T: Add<Output = T>>(base: T, condition: bool, change: T) -> T {
|
||||
if condition {
|
||||
base + change
|
||||
} else {
|
||||
change
|
||||
}
|
||||
if condition { base + change } else { change }
|
||||
}
|
||||
|
||||
let new_x = apply_change(position.x, p.relative.x, p.change.pos.x);
|
||||
|
@ -684,9 +668,13 @@ impl GamePacketHandler<'_> {
|
|||
let entity_in_ecs = entity_query.get(ecs_entity).is_ok();
|
||||
|
||||
if entity_in_ecs {
|
||||
error!("LoadedBy for entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id");
|
||||
error!(
|
||||
"LoadedBy for entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
|
||||
);
|
||||
} else {
|
||||
error!("Entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id");
|
||||
error!(
|
||||
"Entity {entity_id:?} ({ecs_entity:?}) isn't in the ecs, but the entity is in entity_by_id"
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
@ -1067,7 +1055,9 @@ impl GamePacketHandler<'_> {
|
|||
|
||||
for &id in &p.entity_ids {
|
||||
let Some(entity) = entity_id_index.remove(id) else {
|
||||
debug!("Tried to remove entity with id {id} but it wasn't in the EntityIdIndex");
|
||||
debug!(
|
||||
"Tried to remove entity with id {id} but it wasn't in the EntityIdIndex"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
|
||||
|
@ -1431,64 +1421,56 @@ impl GamePacketHandler<'_> {
|
|||
let (mut instance_holder, game_profile, client_information) =
|
||||
query.get_mut(self.player).unwrap();
|
||||
|
||||
{
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
|
||||
let Some(dimension_type_element) =
|
||||
instance_holder.instance.read().registries.dimension_type()
|
||||
else {
|
||||
error!("Server didn't send dimension type registry, can't log in.");
|
||||
return;
|
||||
};
|
||||
let Some((_dimension_type, dimension_data)) = p
|
||||
.common
|
||||
.dimension_type(&instance_holder.instance.read().registries)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let dimension_name = ResourceLocation::new(&p.common.dimension.to_string());
|
||||
// add this world to the instance_container (or don't if it's already
|
||||
// there)
|
||||
let weak_instance = instance_container.insert(
|
||||
new_instance_name.clone(),
|
||||
dimension_data.height,
|
||||
dimension_data.min_y,
|
||||
&instance_holder.instance.read().registries,
|
||||
);
|
||||
events.send(InstanceLoadedEvent {
|
||||
entity: self.player,
|
||||
name: new_instance_name.clone(),
|
||||
instance: Arc::downgrade(&weak_instance),
|
||||
});
|
||||
|
||||
let Some(dimension) = dimension_type_element.map.get(&dimension_name) else {
|
||||
error!("No dimension_type with name {dimension_name}");
|
||||
return;
|
||||
};
|
||||
// set the partial_world to an empty world
|
||||
// (when we add chunks or entities those will be in the
|
||||
// instance_container)
|
||||
|
||||
// add this world to the instance_container (or don't if it's already
|
||||
// there)
|
||||
let weak_instance = instance_container.insert(
|
||||
new_instance_name.clone(),
|
||||
dimension.height,
|
||||
dimension.min_y,
|
||||
);
|
||||
events.send(InstanceLoadedEvent {
|
||||
entity: self.player,
|
||||
name: new_instance_name.clone(),
|
||||
instance: Arc::downgrade(&weak_instance),
|
||||
});
|
||||
*instance_holder.partial_instance.write() = PartialInstance::new(
|
||||
azalea_world::chunk_storage::calculate_chunk_storage_range(
|
||||
client_information.view_distance.into(),
|
||||
),
|
||||
Some(self.player),
|
||||
);
|
||||
instance_holder.instance = weak_instance;
|
||||
|
||||
// set the partial_world to an empty world
|
||||
// (when we add chunks or entities those will be in the
|
||||
// instance_container)
|
||||
|
||||
*instance_holder.partial_instance.write() = PartialInstance::new(
|
||||
azalea_world::chunk_storage::calculate_chunk_storage_range(
|
||||
client_information.view_distance.into(),
|
||||
),
|
||||
Some(self.player),
|
||||
);
|
||||
instance_holder.instance = weak_instance;
|
||||
|
||||
// this resets a bunch of our components like physics and stuff
|
||||
let entity_bundle = EntityBundle::new(
|
||||
game_profile.uuid,
|
||||
Vec3::default(),
|
||||
azalea_registry::EntityKind::Player,
|
||||
new_instance_name,
|
||||
);
|
||||
// update the local gamemode and metadata things
|
||||
commands.entity(self.player).insert((
|
||||
LocalGameMode {
|
||||
current: p.common.game_type,
|
||||
previous: p.common.previous_game_type.into(),
|
||||
},
|
||||
entity_bundle,
|
||||
));
|
||||
}
|
||||
// this resets a bunch of our components like physics and stuff
|
||||
let entity_bundle = EntityBundle::new(
|
||||
game_profile.uuid,
|
||||
Vec3::default(),
|
||||
azalea_registry::EntityKind::Player,
|
||||
new_instance_name,
|
||||
);
|
||||
// update the local gamemode and metadata things
|
||||
commands.entity(self.player).insert((
|
||||
LocalGameMode {
|
||||
current: p.common.game_type,
|
||||
previous: p.common.previous_game_type.into(),
|
||||
},
|
||||
entity_bundle,
|
||||
));
|
||||
|
||||
// Remove the Dead marker component from the player.
|
||||
commands.entity(self.player).remove::<Dead>();
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use azalea_protocol::packets::{
|
||||
login::{
|
||||
s_custom_query_answer::ServerboundCustomQueryAnswer, ClientboundLoginPacket,
|
||||
ServerboundLoginPacket,
|
||||
},
|
||||
Packet,
|
||||
login::{
|
||||
ClientboundLoginPacket, ServerboundLoginPacket,
|
||||
s_custom_query_answer::ServerboundCustomQueryAnswer,
|
||||
},
|
||||
};
|
||||
use bevy_ecs::{prelude::*, system::SystemState};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
@ -20,7 +20,7 @@ use tracing::error;
|
|||
|
||||
/// An event that's sent when we receive a login packet from the server. Note
|
||||
/// that if you want to handle this in a system, you must add
|
||||
/// `.before(azalea::packet_handling::login::process_packet_events)` to it
|
||||
/// `.before(azalea::packet::login::process_packet_events)` to it
|
||||
/// because that system clears the events.
|
||||
#[derive(Event, Debug, Clone)]
|
||||
pub struct LoginPacketEvent {
|
||||
|
@ -48,6 +48,11 @@ pub struct LoginSendPacketQueue {
|
|||
pub tx: mpsc::UnboundedSender<ServerboundLoginPacket>,
|
||||
}
|
||||
|
||||
/// A marker component for local players that are currently in the
|
||||
/// `login` state.
|
||||
#[derive(Component, Clone, Debug)]
|
||||
pub struct InLoginState;
|
||||
|
||||
pub fn handle_send_packet_event(
|
||||
mut send_packet_events: EventReader<SendLoginPacketEvent>,
|
||||
mut query: Query<&mut LoginSendPacketQueue>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea_entity::{metadata::Health, EntityUpdateSet};
|
||||
use azalea_entity::{EntityUpdateSet, metadata::Health};
|
||||
use bevy_app::{App, First, Plugin, PreUpdate, Update};
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
|
@ -56,7 +56,7 @@ impl Plugin for PacketPlugin {
|
|||
(
|
||||
(
|
||||
config::handle_send_packet_event,
|
||||
game::handle_sendpacketevent,
|
||||
game::handle_outgoing_packets,
|
||||
)
|
||||
.chain(),
|
||||
death_event_on_0_health.before(death_listener),
|
||||
|
|
|
@ -2,7 +2,8 @@ use azalea_protocol::packets::game::s_client_command::{self, ServerboundClientCo
|
|||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::packet::game::{handle_sendpacketevent, SendPacketEvent};
|
||||
use super::packet::game::handle_outgoing_packets;
|
||||
use crate::packet::game::SendPacketEvent;
|
||||
|
||||
/// Tell the server that we're respawning.
|
||||
#[derive(Event, Debug, Clone)]
|
||||
|
@ -15,7 +16,7 @@ pub struct RespawnPlugin;
|
|||
impl Plugin for RespawnPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<PerformRespawnEvent>()
|
||||
.add_systems(Update, perform_respawn.before(handle_sendpacketevent));
|
||||
.add_systems(Update, perform_respawn.before(handle_outgoing_packets));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ use std::marker::PhantomData;
|
|||
use bevy_app::{App, Last, Plugin};
|
||||
use bevy_ecs::system::{NonSend, Resource};
|
||||
use bevy_tasks::{
|
||||
tick_global_task_pools_on_main_thread, AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool,
|
||||
TaskPoolBuilder,
|
||||
AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder,
|
||||
tick_global_task_pools_on_main_thread,
|
||||
};
|
||||
|
||||
/// Setup of default task pools: `AsyncComputeTaskPool`, `ComputeTaskPool`,
|
||||
|
|
|
@ -1,102 +1,106 @@
|
|||
use std::{fmt::Debug, sync::Arc, time::Duration};
|
||||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_client::{
|
||||
events::LocalPlayerEvents,
|
||||
raw_connection::{RawConnection, RawConnectionReader, RawConnectionWriter},
|
||||
ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle,
|
||||
};
|
||||
use azalea_core::{
|
||||
game_type::{GameMode, OptionalGameType},
|
||||
resource_location::ResourceLocation,
|
||||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{
|
||||
metadata::{Health, PlayerMetadataBundle},
|
||||
LocalEntity,
|
||||
};
|
||||
use azalea_protocol::packets::{
|
||||
common::CommonPlayerSpawnInfo,
|
||||
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
|
||||
game::{ClientboundLogin, ClientboundSetHealth},
|
||||
ConnectionProtocol, Packet, ProtocolPacket,
|
||||
use azalea_buf::AzaleaWrite;
|
||||
use azalea_core::game_type::{GameMode, OptionalGameType};
|
||||
use azalea_core::position::ChunkPos;
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_core::tick::GameTick;
|
||||
use azalea_entity::metadata::PlayerMetadataBundle;
|
||||
use azalea_protocol::packets::common::CommonPlayerSpawnInfo;
|
||||
use azalea_protocol::packets::game::c_level_chunk_with_light::ClientboundLevelChunkPacketData;
|
||||
use azalea_protocol::packets::game::c_light_update::ClientboundLightUpdatePacketData;
|
||||
use azalea_protocol::packets::game::{
|
||||
ClientboundLevelChunkWithLight, ClientboundLogin, ClientboundRespawn,
|
||||
};
|
||||
use azalea_protocol::packets::{ConnectionProtocol, Packet, ProtocolPacket};
|
||||
use azalea_registry::DimensionType;
|
||||
use azalea_world::{Instance, MinecraftEntityId};
|
||||
use azalea_world::palette::{PalettedContainer, PalettedContainerKind};
|
||||
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section};
|
||||
use bevy_app::App;
|
||||
use bevy_app::PluginGroup;
|
||||
use bevy_ecs::{prelude::*, schedule::ExecutorKind};
|
||||
use bevy_log::{tracing_subscriber, LogPlugin};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use simdnbt::owned::{NbtCompound, NbtTag};
|
||||
use simdnbt::owned::Nbt;
|
||||
use tokio::{sync::mpsc, time::sleep};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn test_set_health_before_login() {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
use crate::{
|
||||
ClientInformation, GameProfileComponent, InConfigState, InstanceHolder, LocalPlayerBundle,
|
||||
events::LocalPlayerEvents,
|
||||
raw_connection::{RawConnection, RawConnectionReader, RawConnectionWriter},
|
||||
};
|
||||
|
||||
let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
|
||||
assert!(simulation.has_component::<InConfigState>());
|
||||
/// A way to simulate a client in a server, used for some internal tests.
|
||||
pub struct Simulation {
|
||||
pub app: App,
|
||||
pub entity: Entity,
|
||||
|
||||
simulation.receive_packet(ClientboundRegistryData {
|
||||
registry_id: ResourceLocation::new("minecraft:dimension_type"),
|
||||
entries: vec![(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
Some(NbtCompound::from_values(vec![
|
||||
("height".into(), NbtTag::Int(384)),
|
||||
("min_y".into(), NbtTag::Int(-64)),
|
||||
])),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
});
|
||||
simulation.tick();
|
||||
simulation.receive_packet(ClientboundFinishConfiguration);
|
||||
simulation.tick();
|
||||
// the runtime needs to be kept around for the tasks to be considered alive
|
||||
pub rt: tokio::runtime::Runtime,
|
||||
|
||||
assert!(!simulation.has_component::<InConfigState>());
|
||||
assert!(simulation.has_component::<LocalEntity>());
|
||||
|
||||
simulation.receive_packet(ClientboundSetHealth {
|
||||
health: 15.,
|
||||
food: 20,
|
||||
saturation: 20.,
|
||||
});
|
||||
simulation.tick();
|
||||
assert_eq!(*simulation.component::<Health>(), 15.);
|
||||
|
||||
simulation.receive_packet(ClientboundLogin {
|
||||
player_id: MinecraftEntityId(0),
|
||||
hardcore: false,
|
||||
levels: vec![],
|
||||
max_players: 20,
|
||||
chunk_radius: 8,
|
||||
simulation_distance: 8,
|
||||
reduced_debug_info: false,
|
||||
show_death_screen: true,
|
||||
do_limited_crafting: false,
|
||||
common: CommonPlayerSpawnInfo {
|
||||
dimension_type: DimensionType::Overworld,
|
||||
dimension: ResourceLocation::new("minecraft:overworld"),
|
||||
seed: 0,
|
||||
game_type: GameMode::Survival,
|
||||
previous_game_type: OptionalGameType(None),
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
last_death_location: None,
|
||||
portal_cooldown: 0,
|
||||
sea_level: 63,
|
||||
},
|
||||
enforces_secure_chat: false,
|
||||
});
|
||||
simulation.tick();
|
||||
|
||||
// health should stay the same
|
||||
assert_eq!(*simulation.component::<Health>(), 15.);
|
||||
pub incoming_packet_queue: Arc<Mutex<Vec<Box<[u8]>>>>,
|
||||
pub outgoing_packets_receiver: mpsc::UnboundedReceiver<Box<[u8]>>,
|
||||
}
|
||||
|
||||
pub fn create_local_player_bundle(
|
||||
impl Simulation {
|
||||
pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self {
|
||||
let mut app = create_simulation_app();
|
||||
let mut entity = app.world_mut().spawn_empty();
|
||||
let (player, outgoing_packets_receiver, incoming_packet_queue, rt) =
|
||||
create_local_player_bundle(entity.id(), initial_connection_protocol);
|
||||
entity.insert(player);
|
||||
|
||||
let entity = entity.id();
|
||||
|
||||
tick_app(&mut app);
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match initial_connection_protocol {
|
||||
ConnectionProtocol::Configuration => {
|
||||
app.world_mut().entity_mut(entity).insert(InConfigState);
|
||||
tick_app(&mut app);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self {
|
||||
app,
|
||||
entity,
|
||||
rt,
|
||||
incoming_packet_queue,
|
||||
outgoing_packets_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_packet<P: ProtocolPacket + Debug>(&mut self, packet: impl Packet<P>) {
|
||||
let buf = azalea_protocol::write::serialize_packet(&packet.into_variant()).unwrap();
|
||||
self.incoming_packet_queue.lock().push(buf);
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
tick_app(&mut self.app);
|
||||
}
|
||||
pub fn component<T: Component + Clone>(&self) -> T {
|
||||
self.app.world().get::<T>(self.entity).unwrap().clone()
|
||||
}
|
||||
pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
|
||||
self.app.world().get::<T>(self.entity).cloned()
|
||||
}
|
||||
pub fn has_component<T: Component>(&self) -> bool {
|
||||
self.app.world().get::<T>(self.entity).is_some()
|
||||
}
|
||||
|
||||
pub fn chunk(&self, chunk_pos: ChunkPos) -> Option<Arc<RwLock<Chunk>>> {
|
||||
self.component::<InstanceHolder>()
|
||||
.instance
|
||||
.read()
|
||||
.chunks
|
||||
.get(&chunk_pos)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn create_local_player_bundle(
|
||||
entity: Entity,
|
||||
connection_protocol: ConnectionProtocol,
|
||||
) -> (
|
||||
|
@ -164,7 +168,12 @@ pub fn create_local_player_bundle(
|
|||
|
||||
fn create_simulation_app() -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(azalea_client::DefaultPlugins.build().disable::<LogPlugin>());
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
app.add_plugins(
|
||||
bevy_app::PluginGroup::build(crate::DefaultPlugins).disable::<bevy_log::LogPlugin>(),
|
||||
);
|
||||
|
||||
app.edit_schedule(bevy_app::Main, |schedule| {
|
||||
// makes test results more reproducible
|
||||
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
|
||||
|
@ -172,66 +181,85 @@ fn create_simulation_app() -> App {
|
|||
app
|
||||
}
|
||||
|
||||
pub struct Simulation {
|
||||
pub app: App,
|
||||
pub entity: Entity,
|
||||
|
||||
// the runtime needs to be kept around for the tasks to be considered alive
|
||||
pub rt: tokio::runtime::Runtime,
|
||||
|
||||
pub incoming_packet_queue: Arc<Mutex<Vec<Box<[u8]>>>>,
|
||||
pub outgoing_packets_receiver: mpsc::UnboundedReceiver<Box<[u8]>>,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new(initial_connection_protocol: ConnectionProtocol) -> Self {
|
||||
let mut app = create_simulation_app();
|
||||
let mut entity = app.world_mut().spawn_empty();
|
||||
let (player, outgoing_packets_receiver, incoming_packet_queue, rt) =
|
||||
create_local_player_bundle(entity.id(), initial_connection_protocol);
|
||||
entity.insert(player);
|
||||
|
||||
let entity = entity.id();
|
||||
|
||||
tick_app(&mut app);
|
||||
|
||||
match initial_connection_protocol {
|
||||
ConnectionProtocol::Configuration => {
|
||||
app.world_mut().entity_mut(entity).insert(InConfigState);
|
||||
tick_app(&mut app);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self {
|
||||
app,
|
||||
entity,
|
||||
rt,
|
||||
incoming_packet_queue,
|
||||
outgoing_packets_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_packet<P: ProtocolPacket + Debug>(&mut self, packet: impl Packet<P>) {
|
||||
let buf = azalea_protocol::write::serialize_packet(&packet.into_variant()).unwrap();
|
||||
self.incoming_packet_queue.lock().push(buf.into());
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
tick_app(&mut self.app);
|
||||
}
|
||||
pub fn component<T: Component + Clone>(&self) -> T {
|
||||
self.app.world().get::<T>(self.entity).unwrap().clone()
|
||||
}
|
||||
pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
|
||||
self.app.world().get::<T>(self.entity).cloned()
|
||||
}
|
||||
pub fn has_component<T: Component>(&self) -> bool {
|
||||
self.app.world().get::<T>(self.entity).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
fn tick_app(app: &mut App) {
|
||||
app.update();
|
||||
app.world_mut().run_schedule(GameTick);
|
||||
}
|
||||
|
||||
pub fn make_basic_login_packet(
|
||||
dimension_type: DimensionType,
|
||||
dimension: ResourceLocation,
|
||||
) -> ClientboundLogin {
|
||||
ClientboundLogin {
|
||||
player_id: MinecraftEntityId(0),
|
||||
hardcore: false,
|
||||
levels: vec![],
|
||||
max_players: 20,
|
||||
chunk_radius: 8,
|
||||
simulation_distance: 8,
|
||||
reduced_debug_info: false,
|
||||
show_death_screen: true,
|
||||
do_limited_crafting: false,
|
||||
common: CommonPlayerSpawnInfo {
|
||||
dimension_type,
|
||||
dimension,
|
||||
seed: 0,
|
||||
game_type: GameMode::Survival,
|
||||
previous_game_type: OptionalGameType(None),
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
last_death_location: None,
|
||||
portal_cooldown: 0,
|
||||
sea_level: 63,
|
||||
},
|
||||
enforces_secure_chat: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_basic_respawn_packet(
|
||||
dimension_type: DimensionType,
|
||||
dimension: ResourceLocation,
|
||||
) -> ClientboundRespawn {
|
||||
ClientboundRespawn {
|
||||
common: CommonPlayerSpawnInfo {
|
||||
dimension_type,
|
||||
dimension,
|
||||
seed: 0,
|
||||
game_type: GameMode::Survival,
|
||||
previous_game_type: OptionalGameType(None),
|
||||
is_debug: false,
|
||||
is_flat: false,
|
||||
last_death_location: None,
|
||||
portal_cooldown: 0,
|
||||
sea_level: 63,
|
||||
},
|
||||
data_to_keep: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_basic_empty_chunk(
|
||||
pos: ChunkPos,
|
||||
section_count: usize,
|
||||
) -> ClientboundLevelChunkWithLight {
|
||||
let mut chunk_bytes = Vec::new();
|
||||
let mut sections = Vec::new();
|
||||
for _ in 0..section_count {
|
||||
sections.push(Section {
|
||||
block_count: 0,
|
||||
states: PalettedContainer::new(PalettedContainerKind::BlockStates),
|
||||
biomes: PalettedContainer::new(PalettedContainerKind::Biomes),
|
||||
});
|
||||
}
|
||||
sections.azalea_write(&mut chunk_bytes).unwrap();
|
||||
|
||||
ClientboundLevelChunkWithLight {
|
||||
x: pos.x,
|
||||
z: pos.z,
|
||||
chunk_data: ClientboundLevelChunkPacketData {
|
||||
heightmaps: Nbt::None,
|
||||
data: chunk_bytes.into(),
|
||||
block_entities: vec![],
|
||||
},
|
||||
light_data: ClientboundLightUpdatePacketData::default(),
|
||||
}
|
||||
}
|
147
azalea-client/tests/change_dimension_to_nether_and_back.rs
Normal file
147
azalea-client/tests/change_dimension_to_nether_and_back.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use azalea_client::{InConfigState, test_simulation::*};
|
||||
use azalea_core::{position::ChunkPos, resource_location::ResourceLocation};
|
||||
use azalea_entity::{LocalEntity, metadata::Health};
|
||||
use azalea_protocol::packets::{
|
||||
ConnectionProtocol,
|
||||
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
|
||||
game::ClientboundSetHealth,
|
||||
};
|
||||
use azalea_registry::DimensionType;
|
||||
use azalea_world::InstanceName;
|
||||
use bevy_log::tracing_subscriber;
|
||||
use simdnbt::owned::{NbtCompound, NbtTag};
|
||||
|
||||
#[test]
|
||||
fn test_change_dimension_to_nether_and_back() {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
|
||||
assert!(simulation.has_component::<InConfigState>());
|
||||
|
||||
simulation.receive_packet(ClientboundRegistryData {
|
||||
registry_id: ResourceLocation::new("minecraft:dimension_type"),
|
||||
entries: vec![
|
||||
(
|
||||
// this dimension should never be created. it just exists to make sure we're not
|
||||
// hard-coding the dimension type id anywhere.
|
||||
ResourceLocation::new("azalea:fakedimension"),
|
||||
Some(NbtCompound::from_values(vec![
|
||||
("height".into(), NbtTag::Int(16)),
|
||||
("min_y".into(), NbtTag::Int(0)),
|
||||
])),
|
||||
),
|
||||
(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
Some(NbtCompound::from_values(vec![
|
||||
("height".into(), NbtTag::Int(384)),
|
||||
("min_y".into(), NbtTag::Int(-64)),
|
||||
])),
|
||||
),
|
||||
(
|
||||
ResourceLocation::new("minecraft:nether"),
|
||||
Some(NbtCompound::from_values(vec![
|
||||
("height".into(), NbtTag::Int(256)),
|
||||
("min_y".into(), NbtTag::Int(0)),
|
||||
])),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
});
|
||||
simulation.tick();
|
||||
simulation.receive_packet(ClientboundFinishConfiguration);
|
||||
simulation.tick();
|
||||
|
||||
assert!(!simulation.has_component::<InConfigState>());
|
||||
assert!(simulation.has_component::<LocalEntity>());
|
||||
|
||||
simulation.receive_packet(ClientboundSetHealth {
|
||||
health: 15.,
|
||||
food: 20,
|
||||
saturation: 20.,
|
||||
});
|
||||
simulation.tick();
|
||||
assert_eq!(*simulation.component::<Health>(), 15.);
|
||||
|
||||
//
|
||||
// OVERWORLD
|
||||
//
|
||||
|
||||
simulation.receive_packet(make_basic_login_packet(
|
||||
DimensionType::new_raw(1), // overworld
|
||||
ResourceLocation::new("azalea:a"),
|
||||
));
|
||||
simulation.tick();
|
||||
|
||||
assert_eq!(
|
||||
*simulation.component::<InstanceName>(),
|
||||
ResourceLocation::new("azalea:a"),
|
||||
"InstanceName should be azalea:a after setting dimension to that"
|
||||
);
|
||||
|
||||
simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16));
|
||||
simulation.tick();
|
||||
// make sure the chunk exists
|
||||
simulation
|
||||
.chunk(ChunkPos::new(0, 0))
|
||||
.expect("chunk should exist");
|
||||
|
||||
//
|
||||
// NETHER
|
||||
//
|
||||
|
||||
simulation.receive_packet(make_basic_respawn_packet(
|
||||
DimensionType::new_raw(2), // nether
|
||||
ResourceLocation::new("azalea:b"),
|
||||
));
|
||||
simulation.tick();
|
||||
|
||||
assert!(
|
||||
simulation.chunk(ChunkPos::new(0, 0)).is_none(),
|
||||
"chunk should not exist immediately after changing dimensions"
|
||||
);
|
||||
assert_eq!(
|
||||
*simulation.component::<InstanceName>(),
|
||||
ResourceLocation::new("azalea:b"),
|
||||
"InstanceName should be azalea:b after changing dimensions to that"
|
||||
);
|
||||
|
||||
simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), 256 / 16));
|
||||
simulation.tick();
|
||||
// make sure the chunk exists
|
||||
simulation
|
||||
.chunk(ChunkPos::new(0, 0))
|
||||
.expect("chunk should exist");
|
||||
simulation.receive_packet(make_basic_respawn_packet(
|
||||
DimensionType::new_raw(2), // nether
|
||||
ResourceLocation::new("minecraft:nether"),
|
||||
));
|
||||
simulation.tick();
|
||||
|
||||
//
|
||||
// BACK TO OVERWORLD
|
||||
//
|
||||
|
||||
simulation.receive_packet(make_basic_login_packet(
|
||||
DimensionType::new_raw(1), // overworld
|
||||
ResourceLocation::new("azalea:a"),
|
||||
));
|
||||
simulation.tick();
|
||||
|
||||
assert_eq!(
|
||||
*simulation.component::<InstanceName>(),
|
||||
ResourceLocation::new("azalea:a"),
|
||||
"InstanceName should be azalea:a after setting dimension back to that"
|
||||
);
|
||||
assert!(
|
||||
simulation.chunk(ChunkPos::new(0, 0)).is_none(),
|
||||
"chunk should not exist immediately after switching back to overworld"
|
||||
);
|
||||
|
||||
simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16));
|
||||
simulation.tick();
|
||||
// make sure the chunk exists
|
||||
simulation
|
||||
.chunk(ChunkPos::new(0, 0))
|
||||
.expect("chunk should exist");
|
||||
}
|
55
azalea-client/tests/set_health_before_login.rs
Normal file
55
azalea-client/tests/set_health_before_login.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use azalea_client::{InConfigState, test_simulation::*};
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_entity::{LocalEntity, metadata::Health};
|
||||
use azalea_protocol::packets::{
|
||||
ConnectionProtocol,
|
||||
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
|
||||
game::ClientboundSetHealth,
|
||||
};
|
||||
use azalea_registry::DimensionType;
|
||||
use bevy_log::tracing_subscriber;
|
||||
use simdnbt::owned::{NbtCompound, NbtTag};
|
||||
|
||||
#[test]
|
||||
fn test_set_health_before_login() {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let mut simulation = Simulation::new(ConnectionProtocol::Configuration);
|
||||
assert!(simulation.has_component::<InConfigState>());
|
||||
|
||||
simulation.receive_packet(ClientboundRegistryData {
|
||||
registry_id: ResourceLocation::new("minecraft:dimension_type"),
|
||||
entries: vec![(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
Some(NbtCompound::from_values(vec![
|
||||
("height".into(), NbtTag::Int(384)),
|
||||
("min_y".into(), NbtTag::Int(-64)),
|
||||
])),
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
});
|
||||
simulation.tick();
|
||||
simulation.receive_packet(ClientboundFinishConfiguration);
|
||||
simulation.tick();
|
||||
|
||||
assert!(!simulation.has_component::<InConfigState>());
|
||||
assert!(simulation.has_component::<LocalEntity>());
|
||||
|
||||
simulation.receive_packet(ClientboundSetHealth {
|
||||
health: 15.,
|
||||
food: 20,
|
||||
saturation: 20.,
|
||||
});
|
||||
simulation.tick();
|
||||
assert_eq!(*simulation.component::<Health>(), 15.);
|
||||
|
||||
simulation.receive_packet(make_basic_login_packet(
|
||||
DimensionType::new_raw(0), // overworld
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
));
|
||||
simulation.tick();
|
||||
|
||||
// health should stay the same
|
||||
assert_eq!(*simulation.component::<Health>(), 15.);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::str::FromStr;
|
||||
use std::{io::Cursor, str::FromStr};
|
||||
|
||||
use azalea_registry::DataRegistry;
|
||||
use simdnbt::owned::NbtCompound;
|
||||
|
@ -23,5 +23,25 @@ pub trait ResolvableDataRegistry: DataRegistry {
|
|||
let resolved = registry_values.get_index(self.protocol_id() as usize)?;
|
||||
Some(resolved)
|
||||
}
|
||||
|
||||
fn resolve_and_deserialize<T: simdnbt::Deserialize>(
|
||||
&self,
|
||||
registries: &RegistryHolder,
|
||||
) -> Option<Result<(ResourceLocation, T), simdnbt::DeserializeError>> {
|
||||
let (name, value) = self.resolve(registries)?;
|
||||
|
||||
let mut nbt_bytes = Vec::new();
|
||||
value.write(&mut nbt_bytes);
|
||||
let nbt_borrow_compound =
|
||||
simdnbt::borrow::read_compound(&mut Cursor::new(&nbt_bytes)).ok()?;
|
||||
let value = match T::from_compound((&nbt_borrow_compound).into()) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Some(Err(err));
|
||||
}
|
||||
};
|
||||
|
||||
Some(Ok((name.clone(), value)))
|
||||
}
|
||||
}
|
||||
impl<T: DataRegistry> ResolvableDataRegistry for T {}
|
||||
|
|
55
azalea-core/src/filterable.rs
Normal file
55
azalea-core/src/filterable.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use azalea_buf::{AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite};
|
||||
|
||||
/// Used for written books.
|
||||
pub struct Filterable<T> {
|
||||
pub raw: T,
|
||||
pub filtered: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: AzaleaWrite> azalea_buf::AzaleaWrite for Filterable<T> {
|
||||
fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
self.raw.azalea_write(buf)?;
|
||||
self.filtered.azalea_write(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaRead> azalea_buf::AzaleaRead for Filterable<T> {
|
||||
fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
|
||||
let raw = AzaleaRead::azalea_read(buf)?;
|
||||
let filtered = AzaleaRead::azalea_read(buf)?;
|
||||
Ok(Self { raw, filtered })
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaReadLimited> azalea_buf::AzaleaReadLimited for Filterable<T> {
|
||||
fn azalea_read_limited(
|
||||
buf: &mut Cursor<&[u8]>,
|
||||
limit: usize,
|
||||
) -> Result<Self, azalea_buf::BufReadError> {
|
||||
let raw = AzaleaReadLimited::azalea_read_limited(buf, limit)?;
|
||||
let filtered = AzaleaReadLimited::azalea_read_limited(buf, limit)?;
|
||||
Ok(Self { raw, filtered })
|
||||
}
|
||||
}
|
||||
impl<T: AzaleaReadVar> azalea_buf::AzaleaReadVar for Filterable<T> {
|
||||
fn azalea_read_var(buf: &mut Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
|
||||
let raw = AzaleaReadVar::azalea_read_var(buf)?;
|
||||
let filtered = AzaleaReadVar::azalea_read_var(buf)?;
|
||||
Ok(Self { raw, filtered })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Filterable<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
filtered: self.filtered.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: PartialEq> PartialEq for Filterable<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.raw == other.raw && self.filtered == other.filtered
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ pub mod data_registry;
|
|||
pub mod delta;
|
||||
pub mod difficulty;
|
||||
pub mod direction;
|
||||
pub mod filterable;
|
||||
pub mod game_type;
|
||||
pub mod math;
|
||||
pub mod objectives;
|
||||
|
|
|
@ -91,18 +91,10 @@ pub fn to_degrees(radians: f64) -> f64 {
|
|||
///
|
||||
/// This function exists because f64::signum doesn't check for 0.
|
||||
pub fn sign(num: f64) -> f64 {
|
||||
if num == 0. {
|
||||
0.
|
||||
} else {
|
||||
num.signum()
|
||||
}
|
||||
if num == 0. { 0. } else { num.signum() }
|
||||
}
|
||||
pub fn sign_as_int(num: f64) -> i32 {
|
||||
if num == 0. {
|
||||
0
|
||||
} else {
|
||||
num.signum() as i32
|
||||
}
|
||||
if num == 0. { 0 } else { num.signum() as i32 }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -9,8 +9,8 @@ use std::{collections::HashMap, io::Cursor};
|
|||
|
||||
use indexmap::IndexMap;
|
||||
use simdnbt::{
|
||||
owned::{NbtCompound, NbtTag},
|
||||
Deserialize, FromNbtTag, Serialize, ToNbtTag,
|
||||
owned::{NbtCompound, NbtTag},
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
|
@ -19,6 +19,11 @@ use crate::resource_location::ResourceLocation;
|
|||
/// The base of the registry.
|
||||
///
|
||||
/// This is the registry that is sent to the client upon login.
|
||||
///
|
||||
/// Note that `azalea-client` stores registries per-world instead of per-client
|
||||
/// like you might expect. This is an optimization for swarms to reduce memory
|
||||
/// usage, since registries are expected to be the same for every client in a
|
||||
/// world.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct RegistryHolder {
|
||||
pub map: HashMap<ResourceLocation, IndexMap<ResourceLocation, NbtCompound>>,
|
||||
|
|
|
@ -8,8 +8,8 @@ use std::{
|
|||
|
||||
use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use simdnbt::{owned::NbtTag, FromNbtTag, ToNbtTag};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use simdnbt::{FromNbtTag, ToNbtTag, owned::NbtTag};
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq)]
|
||||
pub struct ResourceLocation {
|
||||
|
|
|
@ -4,5 +4,7 @@ use bevy_ecs::schedule::ScheduleLabel;
|
|||
///
|
||||
/// Many client systems run on this schedule, the most important one being
|
||||
/// physics.
|
||||
///
|
||||
/// This schedule runs either zero or one times after every Bevy `Update`.
|
||||
#[derive(ScheduleLabel, Hash, Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct GameTick;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea_crypto::{create_cipher, decrypt_packet, encrypt_packet};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
|
||||
fn bench(c: &mut Criterion) {
|
||||
let (mut enc, dec) = create_cipher(b"0123456789abcdef");
|
||||
|
|
|
@ -4,10 +4,10 @@ mod signing;
|
|||
|
||||
use aes::cipher::inout::InOutBuf;
|
||||
use aes::{
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
|
||||
Aes128,
|
||||
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
|
||||
};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use sha1::{Digest, Sha1};
|
||||
pub use signing::*;
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
|
||||
use azalea_buf::{AzBuf, AzaleaWrite};
|
||||
use rsa::{
|
||||
signature::{RandomizedSigner, SignatureEncoding},
|
||||
RsaPrivateKey,
|
||||
signature::{RandomizedSigner, SignatureEncoding},
|
||||
};
|
||||
use sha2::Sha256;
|
||||
use uuid::Uuid;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! See <https://minecraft.fandom.com/wiki/Attribute>.
|
||||
|
||||
use std::collections::{hash_map, HashMap};
|
||||
use std::collections::{HashMap, hash_map};
|
||||
|
||||
use azalea_buf::AzBuf;
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
|||
};
|
||||
|
||||
pub use attributes::Attributes;
|
||||
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||
use azalea_block::{BlockState, fluid_state::FluidKind};
|
||||
use azalea_buf::AzBuf;
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
|
|
|
@ -2,7 +2,7 @@ use azalea_block::{Block, BlockBehavior};
|
|||
use azalea_core::tier::get_item_tier;
|
||||
use azalea_registry as registry;
|
||||
|
||||
use crate::{effects, FluidOnEyes, Physics};
|
||||
use crate::{FluidOnEyes, Physics, effects};
|
||||
|
||||
/// How much progress is made towards mining the block per tick, as a
|
||||
/// percentage. If this is 1 then the block gets broken instantly.
|
||||
|
|
|
@ -155,8 +155,8 @@ pub fn remove_despawned_entities_from_indexes(
|
|||
Changed<LoadedBy>,
|
||||
>,
|
||||
) {
|
||||
for (entity, uuid, minecraft_id, position, world_name, loaded_by) in &query {
|
||||
let Some(instance_lock) = instance_container.get(world_name) else {
|
||||
for (entity, uuid, minecraft_id, position, instance_name, loaded_by) in &query {
|
||||
let Some(instance_lock) = instance_container.get(instance_name) else {
|
||||
// the instance isn't even loaded by us, so we can safely delete the entity
|
||||
debug!(
|
||||
"Despawned entity {entity:?} because it's in an instance that isn't loaded anymore"
|
||||
|
@ -181,19 +181,24 @@ pub fn remove_despawned_entities_from_indexes(
|
|||
|
||||
// remove the entity from the chunk index
|
||||
let chunk = ChunkPos::from(*position);
|
||||
if let Some(entities_in_chunk) = instance.entities_by_chunk.get_mut(&chunk) {
|
||||
if entities_in_chunk.remove(&entity) {
|
||||
// remove the chunk if there's no entities in it anymore
|
||||
if entities_in_chunk.is_empty() {
|
||||
instance.entities_by_chunk.remove(&chunk);
|
||||
match instance.entities_by_chunk.get_mut(&chunk) {
|
||||
Some(entities_in_chunk) => {
|
||||
if entities_in_chunk.remove(&entity) {
|
||||
// remove the chunk if there's no entities in it anymore
|
||||
if entities_in_chunk.is_empty() {
|
||||
instance.entities_by_chunk.remove(&chunk);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Tried to remove entity {entity:?} from chunk {chunk:?} but the entity was not there."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Tried to remove entity {entity:?} from chunk {chunk:?} but the entity was not there."
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
"Tried to remove entity {entity:?} from chunk {chunk:?} but the chunk was not found."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug!("Tried to remove entity {entity:?} from chunk {chunk:?} but the chunk was not found.");
|
||||
}
|
||||
// remove it from the uuid index
|
||||
if entity_uuid_index.entity_by_uuid.remove(uuid).is_none() {
|
||||
|
|
|
@ -3,7 +3,7 @@ mod relative_updates;
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::{fluid_state::FluidKind, BlockState};
|
||||
use azalea_block::{BlockState, fluid_state::FluidKind};
|
||||
use azalea_core::{
|
||||
position::{BlockPos, ChunkPos, Vec3},
|
||||
tick::GameTick,
|
||||
|
@ -17,8 +17,8 @@ pub use relative_updates::RelativeEntityUpdate;
|
|||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable,
|
||||
Physics, Position,
|
||||
Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable, Physics, Position,
|
||||
metadata::Health,
|
||||
};
|
||||
|
||||
/// A Bevy [`SystemSet`] for various types of entity updates.
|
||||
|
@ -168,10 +168,10 @@ fn is_trapdoor_useable_as_ladder(
|
|||
}
|
||||
// and the ladder must be facing the same direction as the trapdoor
|
||||
let ladder_facing = block_below
|
||||
.property::<azalea_block::properties::Facing>()
|
||||
.property::<azalea_block::properties::FacingCardinal>()
|
||||
.expect("ladder block must have facing property");
|
||||
let trapdoor_facing = block_state
|
||||
.property::<azalea_block::properties::Facing>()
|
||||
.property::<azalea_block::properties::FacingCardinal>()
|
||||
.expect("trapdoor block must have facing property");
|
||||
if ladder_facing != trapdoor_facing {
|
||||
return false;
|
||||
|
@ -230,3 +230,61 @@ pub fn update_in_loaded_chunk(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use azalea_block::{
|
||||
blocks::{Ladder, OakTrapdoor},
|
||||
properties::{FacingCardinal, TopBottom},
|
||||
};
|
||||
use azalea_core::position::{BlockPos, ChunkPos};
|
||||
use azalea_world::{Chunk, ChunkStorage, Instance, PartialInstance};
|
||||
|
||||
use super::is_trapdoor_useable_as_ladder;
|
||||
|
||||
#[test]
|
||||
fn test_is_trapdoor_useable_as_ladder() {
|
||||
let mut partial_instance = PartialInstance::default();
|
||||
let mut chunks = ChunkStorage::default();
|
||||
partial_instance.chunks.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Chunk::default()),
|
||||
&mut chunks,
|
||||
);
|
||||
partial_instance.chunks.set_block_state(
|
||||
&BlockPos::new(0, 0, 0),
|
||||
azalea_registry::Block::Stone.into(),
|
||||
&chunks,
|
||||
);
|
||||
|
||||
let ladder = Ladder {
|
||||
facing: FacingCardinal::East,
|
||||
waterlogged: false,
|
||||
};
|
||||
partial_instance
|
||||
.chunks
|
||||
.set_block_state(&BlockPos::new(0, 0, 0), ladder.into(), &chunks);
|
||||
|
||||
let trapdoor = OakTrapdoor {
|
||||
facing: FacingCardinal::East,
|
||||
half: TopBottom::Bottom,
|
||||
open: true,
|
||||
powered: false,
|
||||
waterlogged: false,
|
||||
};
|
||||
partial_instance
|
||||
.chunks
|
||||
.set_block_state(&BlockPos::new(0, 1, 0), trapdoor.into(), &chunks);
|
||||
|
||||
let instance = Instance::from(chunks);
|
||||
let trapdoor_matches_ladder = is_trapdoor_useable_as_ladder(
|
||||
instance
|
||||
.get_block_state(&BlockPos::new(0, 1, 0))
|
||||
.unwrap_or_default(),
|
||||
BlockPos::new(0, 1, 0),
|
||||
&instance,
|
||||
);
|
||||
|
||||
assert!(trapdoor_matches_ladder);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,4 +17,5 @@ azalea-registry = { path = "../azalea-registry", version = "0.11.0" }
|
|||
indexmap.workspace = true
|
||||
|
||||
simdnbt.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
|
|
|
@ -8,7 +8,7 @@ use parse_macro::{DeclareMenus, Field};
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Ident};
|
||||
use syn::{Ident, parse_macro_input};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn declare_menus(input: TokenStream) -> TokenStream {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use syn::{
|
||||
braced,
|
||||
Ident, LitInt, Token, braced,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
Ident, LitInt, Token,
|
||||
};
|
||||
|
||||
/// An identifier, colon, and number
|
||||
|
|
|
@ -3,12 +3,15 @@ use std::{any::Any, collections::HashMap, io::Cursor};
|
|||
|
||||
use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_core::{position::GlobalPos, resource_location::ResourceLocation};
|
||||
use azalea_core::{
|
||||
filterable::Filterable, position::GlobalPos, resource_location::ResourceLocation,
|
||||
};
|
||||
use azalea_registry::{
|
||||
Attribute, Block, ConsumeEffectKind, DataComponentKind, Enchantment, EntityKind, HolderSet,
|
||||
Item, MobEffect, Potion, SoundEvent, TrimMaterial, TrimPattern,
|
||||
};
|
||||
use simdnbt::owned::{Nbt, NbtCompound};
|
||||
use tracing::trace;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ItemStack;
|
||||
|
@ -39,10 +42,9 @@ where
|
|||
}
|
||||
fn eq(&self, other: Box<dyn EncodableDataComponent>) -> bool {
|
||||
let other_any: Box<dyn Any> = other;
|
||||
if let Some(other) = other_any.downcast_ref::<T>() {
|
||||
self == other
|
||||
} else {
|
||||
false
|
||||
match other_any.downcast_ref::<T>() {
|
||||
Some(other) => self == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +56,8 @@ pub fn from_kind(
|
|||
// if this is causing a compile-time error, look at DataComponents.java in the
|
||||
// decompiled vanilla code to see how to implement new components
|
||||
|
||||
trace!("Reading data component {kind}");
|
||||
|
||||
// note that this match statement is updated by genitemcomponents.py
|
||||
Ok(match kind {
|
||||
DataComponentKind::CustomData => Box::new(CustomData::azalea_read(buf)?),
|
||||
|
@ -328,8 +332,10 @@ impl DataComponent for AttributeModifiers {
|
|||
|
||||
#[derive(Clone, PartialEq, AzBuf)]
|
||||
pub struct CustomModelData {
|
||||
#[var]
|
||||
pub value: i32,
|
||||
pub floats: Vec<f32>,
|
||||
pub flags: Vec<bool>,
|
||||
pub strings: Vec<String>,
|
||||
pub colors: Vec<i32>,
|
||||
}
|
||||
impl DataComponent for CustomModelData {
|
||||
const KIND: DataComponentKind = DataComponentKind::CustomModelData;
|
||||
|
@ -535,13 +541,15 @@ impl DataComponent for WritableBookContent {
|
|||
|
||||
#[derive(Clone, PartialEq, AzBuf)]
|
||||
pub struct WrittenBookContent {
|
||||
pub title: String,
|
||||
#[limit(32)]
|
||||
pub title: Filterable<String>,
|
||||
pub author: String,
|
||||
#[var]
|
||||
pub generation: i32,
|
||||
pub pages: Vec<FormattedText>,
|
||||
pub pages: Vec<Filterable<FormattedText>>,
|
||||
pub resolved: bool,
|
||||
}
|
||||
|
||||
impl DataComponent for WrittenBookContent {
|
||||
const KIND: DataComponentKind = DataComponentKind::WrittenBookContent;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
/// Representations of various inventory data structures in Minecraft.
|
||||
pub mod components;
|
||||
pub mod item;
|
||||
|
|
|
@ -3,14 +3,14 @@ use std::ops::RangeInclusive;
|
|||
use azalea_buf::AzBuf;
|
||||
|
||||
use crate::{
|
||||
item::MaxStackSizeExt, AnvilMenuLocation, BeaconMenuLocation, BlastFurnaceMenuLocation,
|
||||
BrewingStandMenuLocation, CartographyTableMenuLocation, Crafter3x3MenuLocation,
|
||||
CraftingMenuLocation, EnchantmentMenuLocation, FurnaceMenuLocation, Generic3x3MenuLocation,
|
||||
Generic9x1MenuLocation, Generic9x2MenuLocation, Generic9x3MenuLocation, Generic9x4MenuLocation,
|
||||
Generic9x5MenuLocation, Generic9x6MenuLocation, GrindstoneMenuLocation, HopperMenuLocation,
|
||||
ItemStack, ItemStackData, LecternMenuLocation, LoomMenuLocation, Menu, MenuLocation,
|
||||
MerchantMenuLocation, Player, PlayerMenuLocation, ShulkerBoxMenuLocation, SmithingMenuLocation,
|
||||
SmokerMenuLocation, StonecutterMenuLocation,
|
||||
AnvilMenuLocation, BeaconMenuLocation, BlastFurnaceMenuLocation, BrewingStandMenuLocation,
|
||||
CartographyTableMenuLocation, Crafter3x3MenuLocation, CraftingMenuLocation,
|
||||
EnchantmentMenuLocation, FurnaceMenuLocation, Generic3x3MenuLocation, Generic9x1MenuLocation,
|
||||
Generic9x2MenuLocation, Generic9x3MenuLocation, Generic9x4MenuLocation, Generic9x5MenuLocation,
|
||||
Generic9x6MenuLocation, GrindstoneMenuLocation, HopperMenuLocation, ItemStack, ItemStackData,
|
||||
LecternMenuLocation, LoomMenuLocation, Menu, MenuLocation, MerchantMenuLocation, Player,
|
||||
PlayerMenuLocation, ShulkerBoxMenuLocation, SmithingMenuLocation, SmokerMenuLocation,
|
||||
StonecutterMenuLocation, item::MaxStackSizeExt,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -310,21 +310,24 @@ impl PartialEq for DataComponentPatch {
|
|||
return false;
|
||||
}
|
||||
for (kind, component) in &self.components {
|
||||
if let Some(other_component) = other.components.get(kind) {
|
||||
// we can't use PartialEq, but we can use our own eq method
|
||||
if let Some(component) = component {
|
||||
if let Some(other_component) = other_component {
|
||||
if !component.eq((*other_component).clone()) {
|
||||
match other.components.get(kind) {
|
||||
Some(other_component) => {
|
||||
// we can't use PartialEq, but we can use our own eq method
|
||||
if let Some(component) = component {
|
||||
if let Some(other_component) = other_component {
|
||||
if !component.eq((*other_component).clone()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
} else if other_component.is_some() {
|
||||
return false;
|
||||
}
|
||||
} else if other_component.is_some() {
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
|
|
|
@ -7,5 +7,6 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
compact_str = { workspace = true, features = ["serde"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
pub static STORAGE: LazyLock<HashMap<String, String>> =
|
||||
use compact_str::CompactString;
|
||||
|
||||
pub static STORAGE: LazyLock<HashMap<CompactString, CompactString>> =
|
||||
LazyLock::new(|| serde_json::from_str(include_str!("en_us.json")).unwrap());
|
||||
|
||||
pub fn get(key: &str) -> Option<&str> {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::{
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
BlockState,
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
};
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
block_hit_result::BlockHitResult,
|
||||
direction::{Axis, Direction},
|
||||
math::{self, lerp, EPSILON},
|
||||
math::{self, EPSILON, lerp},
|
||||
position::{BlockPos, Vec3},
|
||||
};
|
||||
use azalea_inventory::ItemStack;
|
||||
use azalea_world::ChunkStorage;
|
||||
use bevy_ecs::entity::Entity;
|
||||
|
||||
use crate::collision::{BlockWithShape, VoxelShape, EMPTY_SHAPE};
|
||||
use crate::collision::{BlockWithShape, EMPTY_SHAPE, VoxelShape};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClipContext {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::cmp::{self, Ordering};
|
||||
|
||||
use azalea_core::math::{gcd, lcm, EPSILON};
|
||||
use azalea_core::math::{EPSILON, gcd, lcm};
|
||||
|
||||
use super::CubePointRange;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ mod world_collisions;
|
|||
|
||||
use std::{ops::Add, sync::LazyLock};
|
||||
|
||||
use azalea_block::{fluid_state::FluidState, BlockState};
|
||||
use azalea_block::{BlockState, fluid_state::FluidState};
|
||||
use azalea_core::{
|
||||
aabb::AABB,
|
||||
direction::Axis,
|
||||
|
|
|
@ -3,12 +3,12 @@ use std::{cmp, num::NonZeroU32, sync::LazyLock};
|
|||
use azalea_core::{
|
||||
block_hit_result::BlockHitResult,
|
||||
direction::{Axis, AxisCycle, Direction},
|
||||
math::{binary_search, EPSILON},
|
||||
math::{EPSILON, binary_search},
|
||||
position::{BlockPos, Vec3},
|
||||
};
|
||||
|
||||
use super::mergers::IndexMerger;
|
||||
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
||||
use crate::collision::{AABB, BitSetDiscreteVoxelShape, DiscreteVoxelShape};
|
||||
|
||||
pub struct Shapes;
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ use azalea_core::{
|
|||
use azalea_world::{Chunk, Instance};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use super::{Shapes, BLOCK_SHAPE};
|
||||
use crate::collision::{BlockWithShape, VoxelShape, AABB};
|
||||
use super::{BLOCK_SHAPE, Shapes};
|
||||
use crate::collision::{AABB, BlockWithShape, VoxelShape};
|
||||
|
||||
pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
|
||||
let mut state = BlockCollisionsState::new(world, aabb);
|
||||
|
@ -27,12 +27,11 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec<VoxelShape> {
|
|||
|
||||
let item_chunk_pos = ChunkPos::from(item.pos);
|
||||
let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
|
||||
if let Some(initial_chunk) = &initial_chunk {
|
||||
initial_chunk
|
||||
match &initial_chunk {
|
||||
Some(initial_chunk) => initial_chunk
|
||||
.get(&ChunkBlockPos::from(item.pos), state.world.chunks.min_y)
|
||||
.unwrap_or(BlockState::AIR)
|
||||
} else {
|
||||
BlockState::AIR
|
||||
.unwrap_or(BlockState::AIR),
|
||||
_ => BlockState::AIR,
|
||||
}
|
||||
} else {
|
||||
state.get_block_state(item.pos)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use azalea_block::{
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
BlockState,
|
||||
fluid_state::{FluidKind, FluidState},
|
||||
};
|
||||
use azalea_core::{
|
||||
direction::Direction,
|
||||
position::{BlockPos, Vec3},
|
||||
resource_location::ResourceLocation,
|
||||
};
|
||||
use azalea_entity::{InLoadedChunk, LocalEntity, Physics, Position};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
|
@ -31,11 +32,18 @@ pub fn update_in_water_state_and_do_fluid_pushing(
|
|||
|
||||
update_in_water_state_and_do_water_current_pushing(&mut physics, &world, position);
|
||||
|
||||
// right now doing registries.dimension_type() clones the entire registry which
|
||||
// is very inefficient, so for now we're doing this instead
|
||||
|
||||
let is_ultrawarm = world
|
||||
.registries
|
||||
.dimension_type()
|
||||
.and_then(|d| d.map.get(instance_name).map(|d| d.ultrawarm))
|
||||
== Some(Some(true));
|
||||
.map
|
||||
.get(&ResourceLocation::new("minecraft:dimension_type"))
|
||||
.and_then(|d| {
|
||||
d.get(&**instance_name)
|
||||
.map(|d| d.byte("ultrawarm") != Some(0))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let lava_push_factor = if is_ultrawarm {
|
||||
0.007
|
||||
} else {
|
||||
|
|
|
@ -8,15 +8,15 @@ pub mod travel;
|
|||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use azalea_block::{fluid_state::FluidState, properties, Block, BlockState};
|
||||
use azalea_block::{Block, BlockState, fluid_state::FluidState, properties};
|
||||
use azalea_core::{
|
||||
math,
|
||||
position::{BlockPos, Vec3},
|
||||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity,
|
||||
LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
Attributes, InLoadedChunk, Jumping, LocalEntity, LookDirection, OnClimbable, Physics, Pose,
|
||||
Position, metadata::Sprinting, move_relative,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_app::{App, Plugin};
|
||||
|
@ -27,7 +27,7 @@ use bevy_ecs::{
|
|||
world::Mut,
|
||||
};
|
||||
use clip::box_traverse_blocks;
|
||||
use collision::{move_colliding, BlockWithShape, MoverType, VoxelShape, BLOCK_SHAPE};
|
||||
use collision::{BLOCK_SHAPE, BlockWithShape, MoverType, VoxelShape, move_colliding};
|
||||
|
||||
/// A Bevy [`SystemSet`] for running physics that makes entities do things.
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
|
@ -476,11 +476,7 @@ fn get_friction_influenced_speed(
|
|||
speed * (0.216f32 / (friction * friction * friction))
|
||||
} else {
|
||||
// entity.flying_speed
|
||||
if is_sprinting {
|
||||
0.025999999f32
|
||||
} else {
|
||||
0.02
|
||||
}
|
||||
if is_sprinting { 0.025999999f32 } else { 0.02 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use azalea_block::{Block, BlockState};
|
||||
use azalea_core::{aabb::AABB, position::Vec3};
|
||||
use azalea_entity::{
|
||||
metadata::Sprinting, move_relative, Attributes, InLoadedChunk, Jumping, LocalEntity,
|
||||
LookDirection, OnClimbable, Physics, Pose, Position,
|
||||
Attributes, InLoadedChunk, Jumping, LocalEntity, LookDirection, OnClimbable, Physics, Pose,
|
||||
Position, metadata::Sprinting, move_relative,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
collision::{move_colliding, MoverType},
|
||||
get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
|
||||
HandleRelativeFrictionAndCalculateMovementOpts,
|
||||
collision::{MoverType, move_colliding},
|
||||
get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
|
||||
};
|
||||
|
||||
/// Move the entity with the given acceleration while handling friction,
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use azalea_core::{
|
||||
position::{BlockPos, ChunkPos, Vec3},
|
||||
registry_holder::RegistryHolder,
|
||||
resource_location::ResourceLocation,
|
||||
tick::GameTick,
|
||||
};
|
||||
use azalea_entity::{EntityBundle, EntityPlugin, LocalEntity, Physics, Position};
|
||||
use azalea_physics::PhysicsPlugin;
|
||||
use azalea_world::{Chunk, InstanceContainer, MinecraftEntityId, PartialInstance};
|
||||
use azalea_world::{Chunk, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
|
||||
use bevy_app::App;
|
||||
use parking_lot::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// You need an app to spawn entities in the world and do updates.
|
||||
|
@ -17,14 +21,19 @@ fn make_test_app() -> App {
|
|||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gravity() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
pub fn insert_overworld(app: &mut App) -> Arc<RwLock<Instance>> {
|
||||
app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
&RegistryHolder::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gravity() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = insert_overworld(&mut app);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
// the entity has to be in a loaded chunk for physics to work
|
||||
partial_world.chunks.set(
|
||||
|
@ -80,11 +89,7 @@ fn test_gravity() {
|
|||
#[test]
|
||||
fn test_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let world_lock = insert_overworld(&mut app);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
|
@ -140,11 +145,7 @@ fn test_collision() {
|
|||
#[test]
|
||||
fn test_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let world_lock = insert_overworld(&mut app);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
|
@ -194,11 +195,7 @@ fn test_slab_collision() {
|
|||
#[test]
|
||||
fn test_top_slab_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world_mut().resource_mut::<InstanceContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let world_lock = insert_overworld(&mut app);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
|
@ -251,6 +248,7 @@ fn test_weird_wall_collision() {
|
|||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
&RegistryHolder::default(),
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
|
@ -309,6 +307,7 @@ fn test_negative_coordinates_weird_wall_collision() {
|
|||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
&RegistryHolder::default(),
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
|
@ -371,6 +370,7 @@ fn spawn_and_unload_world() {
|
|||
ResourceLocation::new("minecraft:overworld"),
|
||||
384,
|
||||
-64,
|
||||
&RegistryHolder::default(),
|
||||
);
|
||||
let mut partial_world = PartialInstance::default();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
bracketed,
|
||||
DeriveInput, Ident, Token, bracketed,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, DeriveInput, Ident, Token,
|
||||
parse_macro_input,
|
||||
};
|
||||
|
||||
fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream {
|
||||
|
|
|
@ -6,17 +6,17 @@ use std::{error::Error, sync::LazyLock};
|
|||
use azalea_protocol::{
|
||||
connect::Connection,
|
||||
packets::{
|
||||
ClientIntention, PROTOCOL_VERSION, VERSION_NAME,
|
||||
handshake::{
|
||||
s_intention::ServerboundIntention, ClientboundHandshakePacket,
|
||||
ServerboundHandshakePacket,
|
||||
ClientboundHandshakePacket, ServerboundHandshakePacket,
|
||||
s_intention::ServerboundIntention,
|
||||
},
|
||||
login::{s_hello::ServerboundHello, ServerboundLoginPacket},
|
||||
login::{ServerboundLoginPacket, s_hello::ServerboundHello},
|
||||
status::{
|
||||
ServerboundStatusPacket,
|
||||
c_pong_response::ClientboundPongResponse,
|
||||
c_status_response::{ClientboundStatusResponse, Players, Version},
|
||||
ServerboundStatusPacket,
|
||||
},
|
||||
ClientIntention, PROTOCOL_VERSION, VERSION_NAME,
|
||||
},
|
||||
read::ReadPacketError,
|
||||
};
|
||||
|
|
|
@ -10,19 +10,19 @@ use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerEr
|
|||
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncWriteExt, BufStream};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::packets::ProtocolPacket;
|
||||
use crate::packets::config::{ClientboundConfigPacket, ServerboundConfigPacket};
|
||||
use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket};
|
||||
use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket};
|
||||
use crate::packets::login::c_hello::ClientboundHello;
|
||||
use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
|
||||
use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
|
||||
use crate::packets::ProtocolPacket;
|
||||
use crate::read::{deserialize_packet, read_raw_packet, try_read_raw_packet, ReadPacketError};
|
||||
use crate::read::{ReadPacketError, deserialize_packet, read_raw_packet, try_read_raw_packet};
|
||||
use crate::write::{serialize_packet, write_raw_packet};
|
||||
|
||||
pub struct RawReadConnection {
|
||||
|
|
|
@ -115,9 +115,9 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
packets::{
|
||||
game::s_chat::{LastSeenMessagesUpdate, ServerboundChat},
|
||||
login::{s_hello::ServerboundHello, ServerboundLoginPacket},
|
||||
Packet,
|
||||
game::s_chat::{LastSeenMessagesUpdate, ServerboundChat},
|
||||
login::{ServerboundLoginPacket, s_hello::ServerboundHello},
|
||||
},
|
||||
read::{compression_decoder, read_packet},
|
||||
write::{compression_encoder, serialize_packet, write_packet},
|
||||
|
@ -136,7 +136,9 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
stream,
|
||||
[22, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
[
|
||||
22, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
);
|
||||
|
||||
let mut stream = Cursor::new(stream);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use azalea_buf::AzBuf;
|
||||
use azalea_core::{
|
||||
data_registry::ResolvableDataRegistry,
|
||||
game_type::{GameMode, OptionalGameType},
|
||||
position::GlobalPos,
|
||||
registry_holder::{DimensionTypeElement, RegistryHolder},
|
||||
resource_location::ResourceLocation,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Clone, Debug, AzBuf)]
|
||||
pub struct CommonPlayerSpawnInfo {
|
||||
|
@ -20,3 +23,29 @@ pub struct CommonPlayerSpawnInfo {
|
|||
#[var]
|
||||
pub sea_level: i32,
|
||||
}
|
||||
impl CommonPlayerSpawnInfo {
|
||||
pub fn dimension_type(
|
||||
&self,
|
||||
registry_holder: &RegistryHolder,
|
||||
) -> Option<(ResourceLocation, DimensionTypeElement)> {
|
||||
let dimension_res = self
|
||||
.dimension_type
|
||||
.resolve_and_deserialize::<DimensionTypeElement>(registry_holder);
|
||||
let Some(dimension_res) = dimension_res else {
|
||||
error!("Couldn't resolve dimension_type {:?}", self.dimension_type);
|
||||
return None;
|
||||
};
|
||||
let (dimension_type, dimension_data) = match dimension_res {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Couldn't deserialize dimension_type {:?}: {err:?}",
|
||||
self.dimension_type
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((dimension_type, dimension_data))
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use azalea_buf::AzBuf;
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use simdnbt::owned::Nbt;
|
||||
|
@ -6,6 +8,7 @@ use super::c_light_update::ClientboundLightUpdatePacketData;
|
|||
|
||||
#[derive(Clone, Debug, AzBuf, ClientboundGamePacket)]
|
||||
pub struct ClientboundLevelChunkWithLight {
|
||||
// this can't be a ChunkPos since that reads z first and then x
|
||||
pub x: i32,
|
||||
pub z: i32,
|
||||
pub chunk_data: ClientboundLevelChunkPacketData,
|
||||
|
@ -15,8 +18,14 @@ pub struct ClientboundLevelChunkWithLight {
|
|||
#[derive(Clone, Debug, AzBuf)]
|
||||
pub struct ClientboundLevelChunkPacketData {
|
||||
pub heightmaps: Nbt,
|
||||
// we can't parse the data in azalea-protocol because it depends on context from other packets
|
||||
pub data: Vec<u8>,
|
||||
/// The raw chunk sections.
|
||||
///
|
||||
/// We can't parse the data in azalea-protocol because it depends on context
|
||||
/// from other packets
|
||||
///
|
||||
/// This is an Arc because it's often very big and we want it to be cheap to
|
||||
/// clone.
|
||||
pub data: Arc<Vec<u8>>,
|
||||
pub block_entities: Vec<BlockEntity>,
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct ClientboundLightUpdate {
|
|||
pub light_data: ClientboundLightUpdatePacketData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, AzBuf)]
|
||||
#[derive(Clone, Debug, AzBuf, Default)]
|
||||
pub struct ClientboundLightUpdatePacketData {
|
||||
pub sky_y_mask: BitSet,
|
||||
pub block_y_mask: BitSet,
|
||||
|
|
|
@ -116,7 +116,14 @@ fn parse_frame(buffer: &mut Cursor<Vec<u8>>) -> Result<Box<[u8]>, FrameSplitterE
|
|||
if buffer.position() == buffer.get_ref().len() as u64 {
|
||||
// reset the inner vec once we've reached the end of the buffer so we don't keep
|
||||
// leaking memory
|
||||
*buffer.get_mut() = Vec::new();
|
||||
buffer.get_mut().clear();
|
||||
|
||||
// we just cap the capacity to 64KB instead of resetting it to save some
|
||||
// allocations.
|
||||
// and the reason we bother capping it at all is to avoid wasting memory if we
|
||||
// get a big packet once and then never again.
|
||||
buffer.get_mut().shrink_to(1024 * 64);
|
||||
|
||||
buffer.set_position(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ use std::net::{IpAddr, SocketAddr};
|
|||
|
||||
use async_recursion::async_recursion;
|
||||
use hickory_resolver::{
|
||||
config::{ResolverConfig, ResolverOpts},
|
||||
Name, TokioAsyncResolver,
|
||||
config::{ResolverConfig, ResolverOpts},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
|
||||
use azalea_buf::AzaleaWriteVar;
|
||||
use azalea_crypto::Aes128CfbEnc;
|
||||
use flate2::{bufread::ZlibEncoder, Compression};
|
||||
use flate2::{Compression, bufread::ZlibEncoder};
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
use tracing::trace;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
braced,
|
||||
Attribute, Ident, LitStr, Token, braced,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
Attribute, Ident, LitStr, Token,
|
||||
};
|
||||
|
||||
struct RegistryItem {
|
||||
|
|
|
@ -22,3 +22,20 @@ impl DataRegistry for Enchantment {
|
|||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, AzBuf, PartialEq, Eq, Hash)]
|
||||
pub struct DimensionType {
|
||||
#[var]
|
||||
id: u32,
|
||||
}
|
||||
impl DimensionType {
|
||||
pub fn new_raw(id: u32) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
}
|
||||
impl DataRegistry for DimensionType {
|
||||
const NAME: &'static str = "dimension_type";
|
||||
fn protocol_id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,15 +27,6 @@ impl Default for WolfVariant {
|
|||
}
|
||||
}
|
||||
|
||||
registry! {
|
||||
enum DimensionType {
|
||||
Overworld => "minecraft:overworld",
|
||||
Nether => "minecraft:the_nether",
|
||||
End => "minecraft:the_end",
|
||||
OverworldCaves => "minecraft:overworld_caves",
|
||||
}
|
||||
}
|
||||
|
||||
registry! {
|
||||
enum TrimMaterial {
|
||||
Quartz => "minecraft:quartz",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue