1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00
* add configuration state

* start updating to 23w31a

* implement a bit more of 23w31a

* chunk batching

* start adding configuration state

* ioasfhjgsd

* almost works

* configuration state mostly implemented

* handle other packets in configuration state and fix keepalive

* cleanup, fix warnings

* 23w32a

* fix some doctests

* 23w33a

* 23w35a

* 1.20.2-pre2

* fix system conflicts

* 1.20.2-pre4

* make tests compile

* tests pass

* 1.20.2-rc2

* 1.20.2

* Revert "1.20.2"

This reverts commit dd152fd265.

* didn't mean to commit that code

---------

Co-authored-by: mat <git@matdoes.dev>
This commit is contained in:
mat 2023-09-21 11:16:29 -05:00 committed by GitHub
parent 83cce23614
commit 7b3e2e4bf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 3103 additions and 1876 deletions

164
Cargo.lock generated
View file

@ -69,9 +69,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.1" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
@ -92,9 +92,9 @@ dependencies = [
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d495b6dc0184693324491a5ac05f559acc97bf937ab31d7a1c33dd0016be6d2b" checksum = "bb42b2197bf15ccb092b62c74515dbd8b86d0effd934795f6687c93b6e679a2c"
dependencies = [ dependencies = [
"flate2", "flate2",
"futures-core", "futures-core",
@ -134,7 +134,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -151,7 +151,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -232,7 +232,7 @@ version = "0.8.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -262,7 +262,7 @@ version = "0.8.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -309,6 +309,8 @@ dependencies = [
"parking_lot", "parking_lot",
"regex", "regex",
"reqwest", "reqwest",
"serde",
"serde_json",
"thiserror", "thiserror",
"tokio", "tokio",
"uuid", "uuid",
@ -384,7 +386,7 @@ version = "0.8.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -479,7 +481,7 @@ version = "0.8.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -498,7 +500,7 @@ version = "0.8.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -540,9 +542,9 @@ dependencies = [
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.3" version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
@ -574,7 +576,7 @@ checksum = "c5cc78985f4d0ad1fd7b8ead06dcfaa192685775a7b1be158191c788c7d52298"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -607,7 +609,7 @@ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -634,7 +636,7 @@ checksum = "d1cd460205fe05634d58b32d9bb752b1b4eaf32b2d29cbd4161ba35eb44a2f8c"
dependencies = [ dependencies = [
"quote", "quote",
"rustc-hash", "rustc-hash",
"syn 2.0.31", "syn 2.0.33",
"toml_edit", "toml_edit",
] ]
@ -685,7 +687,7 @@ dependencies = [
"bit-set", "bit-set",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
"uuid", "uuid",
] ]
@ -742,7 +744,7 @@ checksum = "0d104f29e231123c703e8b394e2341d2425c33c5a2e9ab8cc8d0a554bdb62a41"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -768,9 +770,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.3.3" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@ -899,18 +901,18 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.11" version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.3.11" version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@ -918,9 +920,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]] [[package]]
name = "compact_str" name = "compact_str"
@ -957,9 +959,9 @@ dependencies = [
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.4" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
@ -1082,9 +1084,9 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.7" version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"pem-rfc7468", "pem-rfc7468",
@ -1123,9 +1125,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]] [[package]]
name = "either" name = "either"
version = "1.8.1" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@ -1145,7 +1147,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -1169,18 +1171,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "erased-serde" name = "erased-serde"
version = "0.3.29" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -1326,7 +1328,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -1658,9 +1660,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]] [[package]]
name = "libm" name = "libm"
@ -1676,9 +1678,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.3" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1815,9 +1817,9 @@ dependencies = [
[[package]] [[package]]
name = "num-complex" name = "num-complex"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -1951,12 +1953,12 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "petgraph" name = "petgraph"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [ dependencies = [
"fixedbitset", "fixedbitset",
"indexmap 1.9.3", "indexmap 2.0.0",
] ]
[[package]] [[package]]
@ -2038,9 +2040,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.66" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -2279,11 +2281,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.4" version = "0.38.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
dependencies = [ dependencies = [
"bitflags 2.3.3", "bitflags 2.4.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -2313,9 +2315,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.101.4" version = "0.101.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -2323,9 +2325,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.13" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]] [[package]]
name = "ryu" name = "ryu"
@ -2360,9 +2362,9 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]] [[package]]
name = "serde" name = "serde"
@ -2375,9 +2377,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_bytes" name = "serde_bytes"
version = "0.11.11" version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -2390,14 +2392,14 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.105" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2517,9 +2519,9 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys",
@ -2566,9 +2568,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.31" version = "2.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2601,14 +2603,14 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
name = "thread-id" name = "thread-id"
version = "4.1.0" version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" checksum = "79474f573561cdc4871a0de34a51c92f7f5a56039113fbb5b9c9f96bdb756669"
dependencies = [ dependencies = [
"libc", "libc",
"redox_syscall 0.2.16", "redox_syscall 0.2.16",
@ -2664,7 +2666,7 @@ dependencies = [
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.3", "socket2 0.5.4",
"tokio-macros", "tokio-macros",
"windows-sys", "windows-sys",
] ]
@ -2677,7 +2679,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -2712,9 +2714,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.14" version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap 2.0.0", "indexmap 2.0.0",
"toml_datetime", "toml_datetime",
@ -2747,7 +2749,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -2864,9 +2866,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.11" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -2930,9 +2932,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.3" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [ dependencies = [
"same-file", "same-file",
"winapi-util", "winapi-util",
@ -2974,7 +2976,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3008,7 +3010,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.33",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View file

@ -9,7 +9,7 @@ A collection of Rust crates for making Minecraft bots, clients, and tools.
</p> </p>
<!-- The line below is automatically read and updated by the migrate script, so don't change it manually. --> <!-- The line below is automatically read and updated by the migrate script, so don't change it manually. -->
*Currently supported Minecraft version: `1.20.1`.* *Currently supported Minecraft version: `1.20.2`.*
## ⚠️ Azalea is still very unfinished, though most crates are in a somewhat useable state ## ⚠️ Azalea is still very unfinished, though most crates are in a somewhat useable state

View file

@ -40,6 +40,8 @@ thiserror = "^1.0.48"
tokio = { version = "^1.32.0", features = ["sync"] } tokio = { version = "^1.32.0", features = ["sync"] }
uuid = "^1.4.1" uuid = "^1.4.1"
azalea-entity = { version = "0.8.0", path = "../azalea-entity" } azalea-entity = { version = "0.8.0", path = "../azalea-entity" }
serde_json = "1.0.104"
serde = "1.0.183"
[features] [features]
default = ["log"] default = ["log"]

View file

@ -0,0 +1,146 @@
//! 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::time::{Duration, Instant};
use azalea_protocol::packets::game::serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket;
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use crate::{
interact::handle_block_interact_event,
inventory::InventorySet,
local_player::{handle_send_packet_event, SendPacketEvent},
respawn::perform_respawn,
};
pub struct ChunkBatchingPlugin;
impl Plugin for ChunkBatchingPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
handle_chunk_batch_start_event,
handle_chunk_batch_finished_event,
)
.chain()
.before(handle_send_packet_event)
.before(InventorySet)
.before(handle_block_interact_event)
.before(perform_respawn),
)
.add_event::<ChunkBatchStartEvent>()
.add_event::<ChunkBatchFinishedEvent>();
}
}
#[derive(Component, Clone, Debug)]
pub struct ChunkBatchInfo {
pub start_time: Instant,
pub accumulator: ChunkReceiveSpeedAccumulator,
}
#[derive(Event)]
pub struct ChunkBatchStartEvent {
pub entity: Entity,
}
#[derive(Event)]
pub struct ChunkBatchFinishedEvent {
pub entity: Entity,
pub batch_size: u32,
}
pub fn handle_chunk_batch_start_event(
mut query: Query<&mut ChunkBatchInfo>,
mut events: EventReader<ChunkBatchStartEvent>,
) {
for event in events.iter() {
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.iter() {
if let Ok(mut chunk_batch_info) = query.get_mut(event.entity) {
let batch_duration = chunk_batch_info.start_time.elapsed();
if event.batch_size > 0 {
chunk_batch_info
.accumulator
.accumulate(event.batch_size, batch_duration);
}
let millis_per_chunk =
f64::max(0., chunk_batch_info.accumulator.get_millis_per_chunk());
let desired_chunks_per_tick = if millis_per_chunk == 0. {
// make it the server's problem instead
f32::NAN
} else {
(25. / millis_per_chunk) as f32
};
send_packets.send(SendPacketEvent {
entity: event.entity,
packet: ServerboundChunkBatchReceivedPacket {
desired_chunks_per_tick,
}
.get(),
});
}
}
}
#[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(),
accumulator: ChunkReceiveSpeedAccumulator::new(50),
}
}
}

View file

@ -1,26 +1,29 @@
use crate::{ use crate::{
attack::{self, AttackPlugin}, attack::{self, AttackPlugin},
chat::ChatPlugin, chat::ChatPlugin,
chunk_batching::{ChunkBatchInfo, ChunkBatchingPlugin},
disconnect::{DisconnectEvent, DisconnectPlugin}, disconnect::{DisconnectEvent, DisconnectPlugin},
events::{Event, EventPlugin, LocalPlayerEvents}, events::{Event, EventPlugin, LocalPlayerEvents},
interact::{CurrentSequenceNumber, InteractPlugin}, interact::{CurrentSequenceNumber, InteractPlugin},
inventory::{InventoryComponent, InventoryPlugin}, inventory::{InventoryComponent, InventoryPlugin},
local_player::{ local_player::{
death_event, handle_send_packet_event, GameProfileComponent, Hunger, LocalPlayer, death_event, handle_send_packet_event, GameProfileComponent, Hunger, InstanceHolder,
SendPacketEvent, PermissionLevel, PlayerAbilities, SendPacketEvent, TabList,
}, },
mining::{self, MinePlugin}, mining::{self, MinePlugin},
movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin}, movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin},
packet_handling::{self, PacketHandlerPlugin, PacketReceiver}, packet_handling::PacketHandlerPlugin,
player::retroactively_add_game_profile_component, player::retroactively_add_game_profile_component,
raw_connection::RawConnection,
respawn::RespawnPlugin, respawn::RespawnPlugin,
task_pool::TaskPoolPlugin, task_pool::TaskPoolPlugin,
Account, PlayerInfo, Account, PlayerInfo, ReceivedRegistries,
}; };
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError}; use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
use azalea_buf::McBufWritable;
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
use azalea_core::Vec3; use azalea_core::{ResourceLocation, Vec3};
use azalea_entity::{ use azalea_entity::{
indexing::{EntityIdIndex, Loaded}, indexing::{EntityIdIndex, Loaded},
metadata::Health, metadata::Health,
@ -30,19 +33,21 @@ use azalea_physics::PhysicsPlugin;
use azalea_protocol::{ use azalea_protocol::{
connect::{Connection, ConnectionError}, connect::{Connection, ConnectionError},
packets::{ packets::{
game::{ configuration::{
clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, serverbound_client_information_packet::ClientInformation,
serverbound_client_information_packet::ServerboundClientInformationPacket, ClientboundConfigurationPacket, ServerboundConfigurationPacket,
ClientboundGamePacket, ServerboundGamePacket,
}, },
handshake::{ game::ServerboundGamePacket,
handshaking::{
client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket, client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
ServerboundHandshakePacket, ServerboundHandshakePacket,
}, },
login::{ login::{
serverbound_custom_query_packet::ServerboundCustomQueryPacket, serverbound_custom_query_answer_packet::ServerboundCustomQueryAnswerPacket,
serverbound_hello_packet::ServerboundHelloPacket, serverbound_hello_packet::ServerboundHelloPacket,
serverbound_key_packet::ServerboundKeyPacket, ClientboundLoginPacket, serverbound_key_packet::ServerboundKeyPacket,
serverbound_login_acknowledged_packet::ServerboundLoginAcknowledgedPacket,
ClientboundLoginPacket,
}, },
ConnectionProtocol, PROTOCOL_VERSION, ConnectionProtocol, PROTOCOL_VERSION,
}, },
@ -59,10 +64,12 @@ use bevy_ecs::{
world::World, world::World,
}; };
use bevy_time::{prelude::FixedTime, TimePlugin}; use bevy_time::{prelude::FixedTime, TimePlugin};
use derive_more::{Deref, DerefMut}; use derive_more::Deref;
use log::{debug, error}; use log::{debug, error};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc, time::Duration}; use std::{
collections::HashMap, fmt::Debug, io, net::SocketAddr, ops::Deref, sync::Arc, time::Duration,
};
use thiserror::Error; use thiserror::Error;
use tokio::{ use tokio::{
sync::{broadcast, mpsc}, sync::{broadcast, mpsc},
@ -71,7 +78,6 @@ use tokio::{
use uuid::Uuid; use uuid::Uuid;
/// `Client` has the things that a user interacting with the library will want. /// `Client` has the things that a user interacting with the library will want.
/// Things that a player in the world will want to know are in [`LocalPlayer`].
/// ///
/// To make a new client, use either [`azalea::ClientBuilder`] or /// To make a new client, use either [`azalea::ClientBuilder`] or
/// [`Client::join`]. /// [`Client::join`].
@ -105,62 +111,6 @@ pub struct Client {
pub run_schedule_sender: mpsc::UnboundedSender<()>, pub run_schedule_sender: mpsc::UnboundedSender<()>,
} }
/// A component that contains some of the "settings" for this client that are
/// sent to the server, such as render distance. This is only present on local
/// players.
pub type ClientInformation = ServerboundClientInformationPacket;
/// A component that contains the abilities the player has, like flying
/// or instantly breaking blocks. This is only present on local players.
#[derive(Clone, Debug, Component, Default)]
pub struct PlayerAbilities {
pub invulnerable: bool,
pub flying: bool,
pub can_fly: bool,
/// Whether the player can instantly break blocks and can duplicate blocks
/// in their inventory.
pub instant_break: bool,
pub flying_speed: f32,
/// Used for the fov
pub walking_speed: f32,
}
impl From<ClientboundPlayerAbilitiesPacket> for PlayerAbilities {
fn from(packet: ClientboundPlayerAbilitiesPacket) -> Self {
Self {
invulnerable: packet.flags.invulnerable,
flying: packet.flags.flying,
can_fly: packet.flags.can_fly,
instant_break: packet.flags.instant_break,
flying_speed: packet.flying_speed,
walking_speed: packet.walking_speed,
}
}
}
/// Level must be 0..=4
#[derive(Component, Clone, Default, Deref, DerefMut)]
pub struct PermissionLevel(pub u8);
/// A component and resource that contains a map of player UUIDs to their
/// information in the tab list.
///
/// This is a component on local players in case you want to get the tab list
/// that a certain client is seeing, and it's also a resource in case you know
/// that the server gives the same tab list to every player.
///
/// ```
/// # use azalea_client::TabList;
/// # fn example(client: &azalea_client::Client) {
/// let tab_list = client.component::<TabList>();
/// println!("Online players:");
/// for (uuid, player_info) in tab_list.iter() {
/// println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
/// }
/// # }
#[derive(Component, Resource, Clone, Debug, Deref, DerefMut, Default)]
pub struct TabList(HashMap<Uuid, PlayerInfo>);
/// An error that happened while joining the server. /// An error that happened while joining the server.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum JoinError { pub enum JoinError {
@ -261,71 +211,55 @@ impl Client {
let entity = ecs_lock.lock().spawn(account.to_owned()).id(); let entity = ecs_lock.lock().spawn(account.to_owned()).id();
let conn = Connection::new(resolved_address).await?; let conn = Connection::new(resolved_address).await?;
let (conn, game_profile) = Self::handshake(conn, account, address).await?; let (mut conn, game_profile) = Self::handshake(conn, account, address).await?;
{
// quickly send the brand here
let mut brand_data = Vec::new();
// they don't have to know :)
"vanilla".write_into(&mut brand_data).unwrap();
conn.write(
azalea_protocol::packets::configuration::serverbound_custom_payload_packet::ServerboundCustomPayloadPacket {
identifier: ResourceLocation::new("brand"),
data: brand_data.into(),
}
.get(),
).await?;
}
let (read_conn, write_conn) = conn.into_split(); let (read_conn, write_conn) = conn.into_split();
let (read_conn, write_conn) = (read_conn.raw, write_conn.raw);
// we did the handshake, so now we're connected to the server // we did the handshake, so now we're connected to the server
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
let (packet_writer_sender, packet_writer_receiver) = mpsc::unbounded_channel(); let mut ecs = ecs_lock.lock();
// start receiving packets
let packet_receiver = packet_handling::PacketReceiver {
packets: Arc::new(Mutex::new(Vec::new())),
run_schedule_sender: run_schedule_sender.clone(),
};
let read_packets_task = tokio::spawn(packet_receiver.clone().read_task(read_conn));
let write_packets_task = tokio::spawn(
packet_receiver
.clone()
.write_task(write_conn, packet_writer_receiver),
);
let local_player = crate::local_player::LocalPlayer::new(
entity,
packet_writer_sender,
// default to an empty world, it'll be set correctly later when we
// get the login packet
Arc::new(RwLock::new(Instance::default())),
read_packets_task,
write_packets_task,
);
ecs_lock
.lock()
.entity_mut(entity)
.insert(JoinedClientBundle {
local_player,
packet_receiver,
game_profile: GameProfileComponent(game_profile.clone()),
physics_state: PhysicsState::default(),
local_player_events: LocalPlayerEvents(tx),
inventory: InventoryComponent::default(),
client_information: ClientInformation::default(),
tab_list: TabList::default(),
current_sequence_number: CurrentSequenceNumber::default(),
last_sent_direction: LastSentLookDirection::default(),
abilities: PlayerAbilities::default(),
permission_level: PermissionLevel::default(),
hunger: Hunger::default(),
entity_id_index: EntityIdIndex::default(),
mining: mining::MineBundle::default(),
attack: attack::AttackBundle::default(),
_local: LocalEntity,
_loaded: Loaded,
});
// we got the ConfigurationConnection, so the client is now connected :)
let client = Client::new( let client = Client::new(
game_profile, game_profile.clone(),
entity, entity,
ecs_lock.clone(), ecs_lock.clone(),
run_schedule_sender.clone(), run_schedule_sender.clone(),
); );
ecs.entity_mut(entity).insert((
// these stay when we switch to the game state
LocalPlayerBundle {
raw_connection: RawConnection::new(
run_schedule_sender,
ConnectionProtocol::Configuration,
read_conn,
write_conn,
),
received_registries: ReceivedRegistries::default(),
local_player_events: LocalPlayerEvents(tx),
game_profile: GameProfileComponent(game_profile),
},
InConfigurationState,
));
Ok((client, rx)) Ok((client, rx))
} }
@ -340,7 +274,7 @@ impl Client {
address: &ServerAddress, address: &ServerAddress,
) -> Result< ) -> Result<
( (
Connection<ClientboundGamePacket, ServerboundGamePacket>, Connection<ClientboundConfigurationPacket, ServerboundConfigurationPacket>,
GameProfile, GameProfile,
), ),
JoinError, JoinError,
@ -362,7 +296,9 @@ impl Client {
conn.write( conn.write(
ServerboundHelloPacket { ServerboundHelloPacket {
name: account.username.clone(), name: account.username.clone(),
profile_id: account.uuid, // TODO: pretty sure this should generate an offline-mode uuid instead of just
// Uuid::default()
profile_id: account.uuid.unwrap_or_default(),
} }
.get(), .get(),
) )
@ -428,8 +364,13 @@ impl Client {
conn.set_compression_threshold(p.compression_threshold); conn.set_compression_threshold(p.compression_threshold);
} }
ClientboundLoginPacket::GameProfile(p) => { ClientboundLoginPacket::GameProfile(p) => {
debug!("Got profile {:?}", p.game_profile); debug!(
break (conn.game(), p.game_profile); "Got profile {:?}. handshake is finished and we're now switching to the configuration state",
p.game_profile
);
conn.write(ServerboundLoginAcknowledgedPacket {}.get())
.await?;
break (conn.configuration(), p.game_profile);
} }
ClientboundLoginPacket::LoginDisconnect(p) => { ClientboundLoginPacket::LoginDisconnect(p) => {
debug!("Got disconnect {:?}", p); debug!("Got disconnect {:?}", p);
@ -438,7 +379,7 @@ impl Client {
ClientboundLoginPacket::CustomQuery(p) => { ClientboundLoginPacket::CustomQuery(p) => {
debug!("Got custom query {:?}", p); debug!("Got custom query {:?}", p);
conn.write( conn.write(
ServerboundCustomQueryPacket { ServerboundCustomQueryAnswerPacket {
transaction_id: p.transaction_id, transaction_id: p.transaction_id,
data: None, data: None,
} }
@ -453,9 +394,12 @@ impl Client {
} }
/// Write a packet directly to the server. /// Write a packet directly to the server.
pub fn write_packet(&self, packet: ServerboundGamePacket) { pub fn write_packet(
self.local_player_mut(&mut self.ecs.lock()) &self,
.write_packet(packet); packet: ServerboundGamePacket,
) -> Result<(), crate::raw_connection::WritePacketError> {
self.raw_connection_mut(&mut self.ecs.lock())
.write_packet(packet)
} }
/// Disconnect this client from the server by ending all tasks. /// Disconnect this client from the server by ending all tasks.
@ -468,14 +412,24 @@ impl Client {
}); });
} }
pub fn local_player<'a>(&'a self, ecs: &'a mut World) -> &'a LocalPlayer { pub fn local_player<'a>(&'a self, ecs: &'a mut World) -> &'a InstanceHolder {
self.query::<&LocalPlayer>(ecs) self.query::<&InstanceHolder>(ecs)
} }
pub fn local_player_mut<'a>( pub fn local_player_mut<'a>(
&'a self, &'a self,
ecs: &'a mut World, ecs: &'a mut World,
) -> bevy_ecs::world::Mut<'a, LocalPlayer> { ) -> bevy_ecs::world::Mut<'a, InstanceHolder> {
self.query::<&mut LocalPlayer>(ecs) self.query::<&mut InstanceHolder>(ecs)
}
pub fn raw_connection<'a>(&'a self, ecs: &'a mut World) -> &'a RawConnection {
self.query::<&RawConnection>(ecs)
}
pub fn raw_connection_mut<'a>(
&'a self,
ecs: &'a mut World,
) -> bevy_ecs::world::Mut<'a, RawConnection> {
self.query::<&mut RawConnection>(ecs)
} }
/// Get a component from this client. This will clone the component and /// Get a component from this client. This will clone the component and
@ -538,8 +492,8 @@ impl Client {
/// ``` /// ```
pub async fn set_client_information( pub async fn set_client_information(
&self, &self,
client_information: ServerboundClientInformationPacket, client_information: ClientInformation,
) -> Result<(), std::io::Error> { ) -> Result<(), crate::raw_connection::WritePacketError> {
{ {
let mut ecs = self.ecs.lock(); let mut ecs = self.ecs.lock();
let mut client_information_mut = self.query::<&mut ClientInformation>(&mut ecs); let mut client_information_mut = self.query::<&mut ClientInformation>(&mut ecs);
@ -551,7 +505,7 @@ impl Client {
"Sending client information (already logged in): {:?}", "Sending client information (already logged in): {:?}",
client_information client_information
); );
self.write_packet(client_information.get()); self.write_packet(azalea_protocol::packets::game::serverbound_client_information_packet::ServerboundClientInformationPacket { information: client_information.clone() }.get())?;
} }
Ok(()) Ok(())
@ -606,21 +560,33 @@ impl Client {
/// Get a map of player UUIDs to their information in the tab list. /// Get a map of player UUIDs to their information in the tab list.
/// ///
/// This is a shortcut for `bot.component::<TabList>().0`. /// This is a shortcut for `*bot.component::<TabList>()`.
pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo> { pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo> {
self.component::<TabList>().0 self.component::<TabList>().deref().clone()
} }
} }
/// A bundle for the components that are present on a local player that received /// The bundle of components that's shared when we're either in the
/// a login packet. If you want to filter for this, just use [`LocalEntity`]. /// `configuration` or `game` state.
///
/// For the components that are only present in the `game` state, see
/// [`JoinedClientBundle`] and for the ones in the `configuration` state, see
/// [`ConfigurationClientBundle`].
#[derive(Bundle)]
pub struct LocalPlayerBundle {
pub raw_connection: RawConnection,
pub received_registries: ReceivedRegistries,
pub local_player_events: LocalPlayerEvents,
pub game_profile: GameProfileComponent,
}
/// A bundle for the components that are present on a local player that is
/// currently in the `game` protocol state. If you want to filter for this, just
/// use [`LocalEntity`].
#[derive(Bundle)] #[derive(Bundle)]
pub struct JoinedClientBundle { pub struct JoinedClientBundle {
pub local_player: LocalPlayer, pub instance_holder: InstanceHolder,
pub packet_receiver: PacketReceiver,
pub game_profile: GameProfileComponent,
pub physics_state: PhysicsState, pub physics_state: PhysicsState,
pub local_player_events: LocalPlayerEvents,
pub inventory: InventoryComponent, pub inventory: InventoryComponent,
pub client_information: ClientInformation, pub client_information: ClientInformation,
pub tab_list: TabList, pub tab_list: TabList,
@ -628,6 +594,7 @@ pub struct JoinedClientBundle {
pub last_sent_direction: LastSentLookDirection, pub last_sent_direction: LastSentLookDirection,
pub abilities: PlayerAbilities, pub abilities: PlayerAbilities,
pub permission_level: PermissionLevel, pub permission_level: PermissionLevel,
pub chunk_batch_info: ChunkBatchInfo,
pub hunger: Hunger, pub hunger: Hunger,
pub entity_id_index: EntityIdIndex, pub entity_id_index: EntityIdIndex,
@ -635,10 +602,15 @@ pub struct JoinedClientBundle {
pub mining: mining::MineBundle, pub mining: mining::MineBundle,
pub attack: attack::AttackBundle, pub attack: attack::AttackBundle,
pub _local: LocalEntity, pub _local_entity: LocalEntity,
pub _loaded: Loaded, pub _loaded: Loaded,
} }
/// A marker component for local players that are currently in the
/// `configuration` state.
#[derive(Component)]
pub struct InConfigurationState;
pub struct AzaleaPlugin; pub struct AzaleaPlugin;
impl Plugin for AzaleaPlugin { impl Plugin for AzaleaPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -790,6 +762,7 @@ impl PluginGroup for DefaultPlugins {
.add(RespawnPlugin) .add(RespawnPlugin)
.add(MinePlugin) .add(MinePlugin)
.add(AttackPlugin) .add(AttackPlugin)
.add(ChunkBatchingPlugin)
.add(TickBroadcastPlugin); .add(TickBroadcastPlugin);
#[cfg(feature = "log")] #[cfg(feature = "log")]
{ {

View file

@ -12,7 +12,7 @@ use bevy_ecs::{
}; };
use derive_more::Deref; use derive_more::Deref;
use crate::{client::JoinedClientBundle, LocalPlayer}; use crate::{client::JoinedClientBundle, raw_connection::RawConnection};
pub struct DisconnectPlugin; pub struct DisconnectPlugin;
impl Plugin for DisconnectPlugin { impl Plugin for DisconnectPlugin {
@ -21,7 +21,7 @@ impl Plugin for DisconnectPlugin {
PostUpdate, PostUpdate,
( (
update_read_packets_task_running_component, update_read_packets_task_running_component,
disconnect_on_read_packets_ended, disconnect_on_connection_dead,
remove_components_from_disconnected_players, remove_components_from_disconnected_players,
) )
.chain(), .chain(),
@ -47,25 +47,23 @@ pub fn remove_components_from_disconnected_players(
} }
#[derive(Component, Clone, Copy, Debug, Deref)] #[derive(Component, Clone, Copy, Debug, Deref)]
pub struct ReadPacketsTaskRunning(bool); pub struct IsConnectionAlive(bool);
fn update_read_packets_task_running_component( fn update_read_packets_task_running_component(
query: Query<(Entity, &RawConnection)>,
mut commands: Commands, mut commands: Commands,
local_player: Query<(Entity, &LocalPlayer)>,
) { ) {
for (entity, local_player) in &local_player { for (entity, raw_connection) in &query {
let running = !local_player.read_packets_task.is_finished(); let running = raw_connection.is_alive();
commands commands.entity(entity).insert(IsConnectionAlive(running));
.entity(entity)
.insert(ReadPacketsTaskRunning(running));
} }
} }
fn disconnect_on_read_packets_ended( fn disconnect_on_connection_dead(
local_player: Query<(Entity, &ReadPacketsTaskRunning), Changed<ReadPacketsTaskRunning>>, query: Query<(Entity, &IsConnectionAlive), Changed<IsConnectionAlive>>,
mut disconnect_events: EventWriter<DisconnectEvent>, mut disconnect_events: EventWriter<DisconnectEvent>,
) { ) {
for (entity, &read_packets_task_running) in &local_player { for (entity, &is_connection_alive) in &query {
if !*read_packets_task_running { if !*is_connection_alive {
disconnect_events.send(DisconnectEvent { entity }); disconnect_events.send(DisconnectEvent { entity });
} }
} }

View file

@ -20,7 +20,7 @@ use tokio::sync::mpsc;
use crate::{ use crate::{
chat::{ChatPacket, ChatReceivedEvent}, chat::{ChatPacket, ChatReceivedEvent},
packet_handling::{ packet_handling::game::{
AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketEvent, RemovePlayerEvent, AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketEvent, RemovePlayerEvent,
UpdatePlayerEvent, UpdatePlayerEvent,
}, },
@ -115,13 +115,13 @@ impl Plugin for EventPlugin {
add_player_listener, add_player_listener,
update_player_listener, update_player_listener,
remove_player_listener, remove_player_listener,
death_listener,
keepalive_listener, keepalive_listener,
death_listener,
), ),
) )
.add_systems( .add_systems(
PreUpdate, PreUpdate,
init_listener.before(crate::packet_handling::process_packet_events), init_listener.before(crate::packet_handling::game::process_packet_events),
) )
.add_systems(FixedUpdate, tick_listener); .add_systems(FixedUpdate, tick_listener);
} }
@ -145,7 +145,7 @@ fn chat_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<ChatR
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive chat events"); .expect("Non-local entities shouldn't be able to receive chat events");
local_player_events local_player_events
.send(Event::Chat(event.packet.clone())) .send(Event::Chat(event.packet.clone()))
.unwrap(); .unwrap();
@ -163,7 +163,7 @@ fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<Pac
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive add player events"); .expect("Non-local entities shouldn't be able to receive add player events");
local_player_events local_player_events
.send(Event::Packet(Arc::new(event.packet.clone()))) .send(Event::Packet(Arc::new(event.packet.clone())))
.unwrap(); .unwrap();
@ -174,7 +174,7 @@ fn add_player_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive add player events"); .expect("Non-local entities shouldn't be able to receive add player events");
local_player_events local_player_events
.send(Event::AddPlayer(event.info.clone())) .send(Event::AddPlayer(event.info.clone()))
.unwrap(); .unwrap();
@ -188,7 +188,7 @@ fn update_player_listener(
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive update player events"); .expect("Non-local entities shouldn't be able to receive update player events");
local_player_events local_player_events
.send(Event::UpdatePlayer(event.info.clone())) .send(Event::UpdatePlayer(event.info.clone()))
.unwrap(); .unwrap();
@ -202,7 +202,7 @@ fn remove_player_listener(
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive remove player events"); .expect("Non-local entities shouldn't be able to receive remove player events");
local_player_events local_player_events
.send(Event::RemovePlayer(event.info.clone())) .send(Event::RemovePlayer(event.info.clone()))
.unwrap(); .unwrap();
@ -223,7 +223,7 @@ fn keepalive_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<
for event in events.iter() { for event in events.iter() {
let local_player_events = query let local_player_events = query
.get(event.entity) .get(event.entity)
.expect("Non-localplayer entities shouldn't be able to receive keepalive events"); .expect("Non-local entities shouldn't be able to receive keepalive events");
local_player_events local_player_events
.send(Event::KeepAlive(event.id)) .send(Event::KeepAlive(event.id))
.unwrap(); .unwrap();

View file

@ -27,10 +27,13 @@ use derive_more::{Deref, DerefMut};
use log::warn; use log::warn;
use crate::{ use crate::{
client::{PermissionLevel, PlayerAbilities}, attack::handle_attack_event,
inventory::{InventoryComponent, InventorySet}, inventory::{InventoryComponent, InventorySet},
local_player::{handle_send_packet_event, LocalGameMode, SendPacketEvent}, local_player::{
Client, LocalPlayer, handle_send_packet_event, LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent,
},
respawn::perform_respawn,
Client,
}; };
/// A plugin that allows clients to interact with blocks in the world. /// A plugin that allows clients to interact with blocks in the world.
@ -48,6 +51,9 @@ impl Plugin for InteractPlugin {
handle_swing_arm_event, handle_swing_arm_event,
) )
.before(handle_send_packet_event) .before(handle_send_packet_event)
.after(InventorySet)
.after(perform_respawn)
.after(handle_attack_event)
.chain(), .chain(),
update_modifiers_for_held_item update_modifiers_for_held_item
.after(InventorySet) .after(InventorySet)
@ -100,16 +106,12 @@ pub struct HitResultComponent(BlockHitResult);
pub fn handle_block_interact_event( pub fn handle_block_interact_event(
mut events: EventReader<BlockInteractEvent>, mut events: EventReader<BlockInteractEvent>,
mut query: Query<( mut query: Query<(Entity, &mut CurrentSequenceNumber, &HitResultComponent)>,
&LocalPlayer, mut send_packet_events: EventWriter<SendPacketEvent>,
&mut CurrentSequenceNumber,
&HitResultComponent,
)>,
) { ) {
for event in events.iter() { for event in events.iter() {
let Ok((local_player, mut sequence_number, hit_result)) = query.get_mut(event.entity) let Ok((entity, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
else { warn!("Sent BlockInteractEvent for entity that doesn't have the required components");
warn!("Sent BlockInteractEvent for entity that isn't LocalPlayer");
continue; continue;
}; };
@ -141,14 +143,15 @@ pub fn handle_block_interact_event(
} }
}; };
local_player.write_packet( send_packet_events.send(SendPacketEvent {
ServerboundUseItemOnPacket { entity,
packet: ServerboundUseItemOnPacket {
hand: InteractionHand::MainHand, hand: InteractionHand::MainHand,
block_hit, block_hit,
sequence: sequence_number.0, sequence: sequence_number.0,
} }
.get(), .get(),
) })
} }
} }

View file

@ -25,7 +25,11 @@ use bevy_ecs::{
}; };
use log::warn; use log::warn;
use crate::{client::PlayerAbilities, local_player::handle_send_packet_event, Client, LocalPlayer}; use crate::{
local_player::{handle_send_packet_event, PlayerAbilities, SendPacketEvent},
respawn::perform_respawn,
Client,
};
pub struct InventoryPlugin; pub struct InventoryPlugin;
impl Plugin for InventoryPlugin { impl Plugin for InventoryPlugin {
@ -45,7 +49,8 @@ impl Plugin for InventoryPlugin {
handle_client_side_close_container_event, handle_client_side_close_container_event,
) )
.chain() .chain()
.in_set(InventorySet), .in_set(InventorySet)
.before(perform_respawn),
); );
} }
} }
@ -599,12 +604,13 @@ pub struct CloseContainerEvent {
pub id: u8, pub id: u8,
} }
fn handle_container_close_event( fn handle_container_close_event(
query: Query<(Entity, &InventoryComponent)>,
mut events: EventReader<CloseContainerEvent>, mut events: EventReader<CloseContainerEvent>,
mut client_side_events: EventWriter<ClientSideCloseContainerEvent>, mut client_side_events: EventWriter<ClientSideCloseContainerEvent>,
query: Query<(&LocalPlayer, &InventoryComponent)>, mut send_packet_events: EventWriter<SendPacketEvent>,
) { ) {
for event in events.iter() { for event in events.iter() {
let (local_player, inventory) = query.get(event.entity).unwrap(); let (entity, inventory) = query.get(event.entity).unwrap();
if event.id != inventory.id { if event.id != inventory.id {
warn!( warn!(
"Tried to close container with ID {}, but the current container ID is {}", "Tried to close container with ID {}, but the current container ID is {}",
@ -613,12 +619,13 @@ fn handle_container_close_event(
continue; continue;
} }
local_player.write_packet( send_packet_events.send(SendPacketEvent {
ServerboundContainerClosePacket { entity,
packet: ServerboundContainerClosePacket {
container_id: inventory.id, container_id: inventory.id,
} }
.get(), .get(),
); });
client_side_events.send(ClientSideCloseContainerEvent { client_side_events.send(ClientSideCloseContainerEvent {
entity: event.entity, entity: event.entity,
}); });
@ -650,11 +657,12 @@ pub struct ContainerClickEvent {
pub operation: ClickOperation, pub operation: ClickOperation,
} }
pub fn handle_container_click_event( pub fn handle_container_click_event(
mut query: Query<(Entity, &mut InventoryComponent)>,
mut events: EventReader<ContainerClickEvent>, mut events: EventReader<ContainerClickEvent>,
mut query: Query<(&mut InventoryComponent, &LocalPlayer)>, mut send_packet_events: EventWriter<SendPacketEvent>,
) { ) {
for event in events.iter() { for event in events.iter() {
let (mut inventory, local_player) = query.get_mut(event.entity).unwrap(); let (entity, mut inventory) = query.get_mut(event.entity).unwrap();
if inventory.id != event.window_id { if inventory.id != event.window_id {
warn!( warn!(
"Tried to click container with ID {}, but the current container ID is {}", "Tried to click container with ID {}, but the current container ID is {}",
@ -678,8 +686,9 @@ pub fn handle_container_click_event(
} }
} }
local_player.write_packet( send_packet_events.send(SendPacketEvent {
ServerboundContainerClickPacket { entity,
packet: ServerboundContainerClickPacket {
container_id: event.window_id, container_id: event.window_id,
state_id: inventory.state_id, state_id: inventory.state_id,
slot_num: event.operation.slot_num().map(|n| n as i16).unwrap_or(-999), slot_num: event.operation.slot_num().map(|n| n as i16).unwrap_or(-999),
@ -689,7 +698,7 @@ pub fn handle_container_click_event(
carried_item: inventory.carried.clone(), carried_item: inventory.carried.clone(),
} }
.get(), .get(),
) })
} }
} }

View file

@ -13,6 +13,7 @@
mod account; mod account;
pub mod attack; pub mod attack;
pub mod chat; pub mod chat;
pub mod chunk_batching;
mod client; mod client;
pub mod disconnect; pub mod disconnect;
mod entity_query; mod entity_query;
@ -26,18 +27,20 @@ pub mod movement;
pub mod packet_handling; pub mod packet_handling;
pub mod ping; pub mod ping;
mod player; mod player;
pub mod raw_connection;
pub mod received_registries; pub mod received_registries;
pub mod respawn; pub mod respawn;
pub mod task_pool; pub mod task_pool;
pub use account::{Account, AccountOpts}; pub use account::{Account, AccountOpts};
pub use azalea_protocol::packets::configuration::serverbound_client_information_packet::ClientInformation;
pub use client::{ pub use client::{
start_ecs_runner, Client, ClientInformation, DefaultPlugins, JoinError, JoinedClientBundle, start_ecs_runner, Client, DefaultPlugins, JoinError, JoinedClientBundle, TickBroadcast,
TabList, TickBroadcast,
}; };
pub use events::Event; pub use events::Event;
pub use local_player::{GameProfileComponent, LocalPlayer, SendPacketEvent}; pub use local_player::{GameProfileComponent, InstanceHolder, SendPacketEvent, TabList};
pub use movement::{ pub use movement::{
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
}; };
pub use player::PlayerInfo; pub use player::PlayerInfo;
pub use received_registries::ReceivedRegistries;

View file

@ -1,51 +1,40 @@
use std::{io, sync::Arc}; use std::{collections::HashMap, io, sync::Arc};
use azalea_auth::game_profile::GameProfile; use azalea_auth::game_profile::GameProfile;
use azalea_core::GameMode; use azalea_core::GameMode;
use azalea_entity::Dead; use azalea_entity::Dead;
use azalea_protocol::packets::game::ServerboundGamePacket; use azalea_protocol::packets::game::{
clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, ServerboundGamePacket,
};
use azalea_world::{Instance, PartialInstance}; use azalea_world::{Instance, PartialInstance};
use bevy_ecs::{ use bevy_ecs::{
component::Component, entity::Entity, event::EventReader, prelude::Event, query::Added, component::Component, entity::Entity, event::EventReader, prelude::*, query::Added,
system::Query, system::Query,
}; };
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use log::error;
use parking_lot::RwLock; use parking_lot::RwLock;
use thiserror::Error; use thiserror::Error;
use tokio::{sync::mpsc, task::JoinHandle}; use tokio::sync::mpsc;
use uuid::Uuid;
use crate::{ use crate::{
events::{Event as AzaleaEvent, LocalPlayerEvents}, events::{Event as AzaleaEvent, LocalPlayerEvents},
ClientInformation, raw_connection::RawConnection,
ClientInformation, PlayerInfo,
}; };
/// This is a component for our local player entities that are probably in a /// A component that keeps strong references to our [`PartialInstance`] and
/// world. If you have access to a [`Client`], you probably don't need to care /// [`Instance`] for local players.
/// about this since `Client` gives you access to everything here.
///
/// You can also use the [`LocalEntity`] marker component for queries if you're
/// only checking for a local player and don't need the contents of this
/// component.
///
/// [`LocalEntity`]: azalea_entity::LocalEntity
/// [`Client`]: crate::Client
#[derive(Component)] #[derive(Component)]
pub struct LocalPlayer { pub struct InstanceHolder {
pub packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
/// The partial instance is the world this client currently has loaded. It /// The partial instance is the world this client currently has loaded. It
/// has a limited render distance. /// has a limited render distance.
pub partial_instance: Arc<RwLock<PartialInstance>>, pub partial_instance: Arc<RwLock<PartialInstance>>,
/// The world is the combined [`PartialInstance`]s of all clients in the /// The world is the combined [`PartialInstance`]s of all clients in the
/// same world. (Only relevant if you're using a shared world, i.e. a /// same world. (Only relevant if you're using a shared world, i.e. a
/// swarm) /// swarm)
pub world: Arc<RwLock<Instance>>, pub instance: Arc<RwLock<Instance>>,
/// A task that reads packets from the server. The client is disconnected
/// when this task ends.
pub(crate) read_packets_task: JoinHandle<()>,
/// A task that writes packets from the server.
pub(crate) write_packets_task: JoinHandle<()>,
} }
/// A component only present in players that contains the [`GameProfile`] (which /// A component only present in players that contains the [`GameProfile`] (which
@ -64,6 +53,53 @@ pub struct LocalGameMode {
pub previous: Option<GameMode>, pub previous: Option<GameMode>,
} }
/// A component that contains the abilities the player has, like flying
/// or instantly breaking blocks. This is only present on local players.
#[derive(Clone, Debug, Component, Default)]
pub struct PlayerAbilities {
pub invulnerable: bool,
pub flying: bool,
pub can_fly: bool,
/// Whether the player can instantly break blocks and can duplicate blocks
/// in their inventory.
pub instant_break: bool,
pub flying_speed: f32,
/// Used for the fov
pub walking_speed: f32,
}
impl From<ClientboundPlayerAbilitiesPacket> for PlayerAbilities {
fn from(packet: ClientboundPlayerAbilitiesPacket) -> Self {
Self {
invulnerable: packet.flags.invulnerable,
flying: packet.flags.flying,
can_fly: packet.flags.can_fly,
instant_break: packet.flags.instant_break,
flying_speed: packet.flying_speed,
walking_speed: packet.walking_speed,
}
}
}
/// Level must be 0..=4
#[derive(Component, Clone, Default, Deref, DerefMut)]
pub struct PermissionLevel(pub u8);
/// A component that contains a map of player UUIDs to their information in the
/// tab list.
///
/// ```
/// # use azalea_client::TabList;
/// # fn example(client: &azalea_client::Client) {
/// let tab_list = client.component::<TabList>();
/// println!("Online players:");
/// for (uuid, player_info) in tab_list.iter() {
/// println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
/// }
/// # }
#[derive(Component, Resource, Clone, Debug, Deref, DerefMut, Default)]
pub struct TabList(HashMap<Uuid, PlayerInfo>);
#[derive(Component, Clone)] #[derive(Component, Clone)]
pub struct Hunger { pub struct Hunger {
/// The main hunger bar. Goes from 0 to 20. /// The main hunger bar. Goes from 0 to 20.
@ -83,50 +119,24 @@ impl Default for Hunger {
} }
} }
impl LocalPlayer { impl InstanceHolder {
/// Create a new `LocalPlayer`. /// Create a new `InstanceHolder`.
pub fn new( pub fn new(entity: Entity, world: Arc<RwLock<Instance>>) -> Self {
entity: Entity,
packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
world: Arc<RwLock<Instance>>,
read_packets_task: JoinHandle<()>,
write_packets_task: JoinHandle<()>,
) -> Self {
let client_information = ClientInformation::default(); let client_information = ClientInformation::default();
LocalPlayer { InstanceHolder {
packet_writer, instance: world,
world,
partial_instance: Arc::new(RwLock::new(PartialInstance::new( partial_instance: Arc::new(RwLock::new(PartialInstance::new(
azalea_world::calculate_chunk_storage_range( azalea_world::calculate_chunk_storage_range(
client_information.view_distance.into(), client_information.view_distance.into(),
), ),
Some(entity), Some(entity),
))), ))),
read_packets_task,
write_packets_task,
} }
} }
/// Write a packet directly to the server.
pub fn write_packet(&self, packet: ServerboundGamePacket) {
self.packet_writer
.send(packet)
.expect("write_packet shouldn't be able to be called if the connection is closed");
}
} }
impl Drop for LocalPlayer { /// Send the "Death" event for [`LocalEntity`]s that died with no reason.
/// Stop every active task when the `LocalPlayer` is dropped.
fn drop(&mut self) {
self.read_packets_task.abort();
self.write_packets_task.abort();
}
}
/// Send the "Death" event for [`LocalPlayer`]s that died with no reason.
pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) { pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) {
for local_player_events in &query { for local_player_events in &query {
local_player_events.send(AzaleaEvent::Death(None)).unwrap(); local_player_events.send(AzaleaEvent::Death(None)).unwrap();
@ -160,11 +170,14 @@ pub struct SendPacketEvent {
pub fn handle_send_packet_event( pub fn handle_send_packet_event(
mut send_packet_events: EventReader<SendPacketEvent>, mut send_packet_events: EventReader<SendPacketEvent>,
mut query: Query<&mut LocalPlayer>, mut query: Query<&mut RawConnection>,
) { ) {
for event in send_packet_events.iter() { for event in send_packet_events.iter() {
if let Ok(local_player) = query.get_mut(event.entity) { if let Ok(raw_connection) = query.get_mut(event.entity) {
local_player.write_packet(event.packet.clone()); // debug!("Sending packet: {:?}", event.packet);
if let Err(e) = raw_connection.write_packet(event.packet.clone()) {
error!("Failed to send packet: {e}");
}
} }
} }
} }

View file

@ -12,13 +12,12 @@ use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use crate::{ use crate::{
client::{PermissionLevel, PlayerAbilities},
interact::{ interact::{
can_use_game_master_blocks, check_is_interaction_restricted, CurrentSequenceNumber, can_use_game_master_blocks, check_is_interaction_restricted, CurrentSequenceNumber,
HitResultComponent, SwingArmEvent, HitResultComponent, SwingArmEvent,
}, },
inventory::{InventoryComponent, InventorySet}, inventory::{InventoryComponent, InventorySet},
local_player::{LocalGameMode, SendPacketEvent}, local_player::{LocalGameMode, PermissionLevel, PlayerAbilities, SendPacketEvent},
Client, Client,
}; };

View file

@ -1,8 +1,8 @@
use crate::client::Client; use crate::client::Client;
use crate::local_player::LocalPlayer; use crate::local_player::SendPacketEvent;
use azalea_entity::{metadata::Sprinting, Attributes, Jumping}; use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position}; use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
use azalea_physics::PhysicsSet; use azalea_physics::{ai_step, PhysicsSet};
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket; use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
use azalea_protocol::packets::game::{ use azalea_protocol::packets::game::{
serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
@ -12,7 +12,7 @@ use azalea_protocol::packets::game::{
}; };
use azalea_world::{MinecraftEntityId, MoveEntityError}; use azalea_world::{MinecraftEntityId, MoveEntityError};
use bevy_app::{App, FixedUpdate, Plugin, Update}; use bevy_app::{App, FixedUpdate, Plugin, Update};
use bevy_ecs::prelude::Event; use bevy_ecs::prelude::{Event, EventWriter};
use bevy_ecs::{ use bevy_ecs::{
component::Component, entity::Entity, event::EventReader, query::With, component::Component, entity::Entity, event::EventReader, query::With,
schedule::IntoSystemConfigs, system::Query, schedule::IntoSystemConfigs, system::Query,
@ -48,9 +48,11 @@ impl Plugin for PlayerMovePlugin {
.add_systems( .add_systems(
FixedUpdate, FixedUpdate,
( (
local_player_ai_step (tick_controls, local_player_ai_step)
.chain()
.in_set(PhysicsSet) .in_set(PhysicsSet)
.before(azalea_physics::ai_step), .before(ai_step),
send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
send_position.after(PhysicsSet), send_position.after(PhysicsSet),
) )
.chain(), .chain(),
@ -118,33 +120,28 @@ pub struct PhysicsState {
pub fn send_position( pub fn send_position(
mut query: Query< mut query: Query<
( (
&MinecraftEntityId, Entity,
&mut LocalPlayer,
&mut PhysicsState,
&Position, &Position,
&LookDirection,
&mut PhysicsState,
&mut LastSentPosition, &mut LastSentPosition,
&mut Physics, &mut Physics,
&LookDirection,
&mut LastSentLookDirection, &mut LastSentLookDirection,
&Sprinting,
), ),
With<InLoadedChunk>, With<InLoadedChunk>,
>, >,
mut send_packet_events: EventWriter<SendPacketEvent>,
) { ) {
for ( for (
id, entity,
mut local_player,
mut physics_state,
position, position,
direction,
mut physics_state,
mut last_sent_position, mut last_sent_position,
mut physics, mut physics,
direction,
mut last_direction, mut last_direction,
sprinting,
) in query.iter_mut() ) in query.iter_mut()
{ {
local_player.send_sprinting_if_needed(id, sprinting, &mut physics_state);
let packet = { let packet = {
// TODO: the camera being able to be controlled by other entities isn't // TODO: the camera being able to be controlled by other entities isn't
// implemented yet if !self.is_controlled_camera() { return }; // implemented yet if !self.is_controlled_camera() { return };
@ -225,18 +222,16 @@ pub fn send_position(
}; };
if let Some(packet) = packet { if let Some(packet) = packet {
local_player.write_packet(packet); send_packet_events.send(SendPacketEvent { entity, packet });
} }
} }
} }
impl LocalPlayer { fn send_sprinting_if_needed(
fn send_sprinting_if_needed( mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
&mut self, mut send_packet_events: EventWriter<SendPacketEvent>,
id: &MinecraftEntityId, ) {
sprinting: &Sprinting, for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
physics_state: &mut PhysicsState,
) {
let was_sprinting = physics_state.was_sprinting; let was_sprinting = physics_state.was_sprinting;
if **sprinting != was_sprinting { if **sprinting != was_sprinting {
let sprinting_action = if **sprinting { let sprinting_action = if **sprinting {
@ -244,21 +239,26 @@ impl LocalPlayer {
} else { } else {
azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting
}; };
self.write_packet( send_packet_events.send(SendPacketEvent {
ServerboundPlayerCommandPacket { entity,
id: **id, packet: ServerboundPlayerCommandPacket {
id: **minecraft_entity_id,
action: sprinting_action, action: sprinting_action,
data: 0, data: 0,
} }
.get(), .get(),
); });
physics_state.was_sprinting = **sprinting; physics_state.was_sprinting = **sprinting;
} }
} }
}
/// Update the impulse from self.move_direction. The multipler is used for
/// sneaking.
pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
for mut physics_state in query.iter_mut() {
let multiplier: Option<f32> = None;
/// Update the impulse from self.move_direction. The multipler is used for
/// sneaking.
pub(crate) fn tick_controls(multiplier: Option<f32>, physics_state: &mut PhysicsState) {
let mut forward_impulse: f32 = 0.; let mut forward_impulse: f32 = 0.;
let mut left_impulse: f32 = 0.; let mut left_impulse: f32 = 0.;
let move_direction = physics_state.move_direction; let move_direction = physics_state.move_direction;
@ -296,18 +296,11 @@ impl LocalPlayer {
/// automatically by the client. /// automatically by the client.
pub fn local_player_ai_step( pub fn local_player_ai_step(
mut query: Query< mut query: Query<
( (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
&mut PhysicsState,
&mut Physics,
&mut Sprinting,
&mut Attributes,
),
With<InLoadedChunk>, With<InLoadedChunk>,
>, >,
) { ) {
for (mut physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() { for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
LocalPlayer::tick_controls(None, &mut physics_state);
// server ai step // server ai step
physics.xxa = physics_state.left_impulse; physics.xxa = physics_state.left_impulse;
physics.zza = physics_state.forward_impulse; physics.zza = physics_state.forward_impulse;
@ -325,7 +318,7 @@ pub fn local_player_ai_step(
&& ( && (
// !self.is_in_water() // !self.is_in_water()
// || self.is_underwater() && // || self.is_underwater() &&
has_enough_impulse_to_start_sprinting(&physics_state) has_enough_impulse_to_start_sprinting(physics_state)
&& has_enough_food_to_sprint && has_enough_food_to_sprint
// && !self.using_item() // && !self.using_item()
// && !self.has_effect(MobEffects.BLINDNESS) // && !self.has_effect(MobEffects.BLINDNESS)

View file

@ -0,0 +1,204 @@
use std::io::Cursor;
use std::sync::Arc;
use azalea_entity::indexing::{EntityIdIndex, Loaded};
use azalea_protocol::packets::configuration::serverbound_finish_configuration_packet::ServerboundFinishConfigurationPacket;
use azalea_protocol::packets::configuration::serverbound_keep_alive_packet::ServerboundKeepAlivePacket;
use azalea_protocol::packets::configuration::serverbound_pong_packet::ServerboundPongPacket;
use azalea_protocol::packets::configuration::serverbound_resource_pack_packet::ServerboundResourcePackPacket;
use azalea_protocol::packets::configuration::ClientboundConfigurationPacket;
use azalea_protocol::packets::ConnectionProtocol;
use azalea_protocol::read::deserialize_packet;
use azalea_world::Instance;
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemState;
use log::{debug, error, warn};
use parking_lot::RwLock;
use crate::client::InConfigurationState;
use crate::disconnect::DisconnectEvent;
use crate::local_player::Hunger;
use crate::packet_handling::game::KeepAliveEvent;
use crate::raw_connection::RawConnection;
use crate::ReceivedRegistries;
#[derive(Event, Debug, Clone)]
pub struct PacketEvent {
/// The client entity that received the packet.
pub entity: Entity,
/// The packet that was actually received.
pub packet: ClientboundConfigurationPacket,
}
pub fn send_packet_events(
query: Query<(Entity, &RawConnection), With<InConfigurationState>>,
mut packet_events: ResMut<Events<PacketEvent>>,
) {
// 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_connection) in &query {
let packets_lock = raw_connection.incoming_packet_queue();
let mut packets = packets_lock.lock();
if !packets.is_empty() {
for raw_packet in packets.iter() {
let packet = match deserialize_packet::<ClientboundConfigurationPacket>(
&mut Cursor::new(raw_packet),
) {
Ok(packet) => packet,
Err(err) => {
error!("failed to read packet: {:?}", err);
continue;
}
};
packet_events.send(PacketEvent {
entity: player_entity,
packet: packet.clone(),
});
}
// 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<PacketEvent>> = SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);
for PacketEvent {
entity: player_entity,
packet,
} in events.iter()
{
// 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 {
ClientboundConfigurationPacket::RegistryData(p) => {
let mut system_state: SystemState<Query<&mut ReceivedRegistries>> =
SystemState::new(ecs);
let mut query = system_state.get_mut(ecs);
let mut received_registries = query.get_mut(player_entity).unwrap();
let new_received_registries = p.registry_holder.registries;
// override the old registries with the new ones
// but if a registry wasn't sent, keep the old one
for (registry_name, registry) in new_received_registries {
received_registries
.registries
.insert(registry_name, registry);
}
}
ClientboundConfigurationPacket::CustomPayload(p) => {
debug!("Got custom payload packet {p:?}");
}
ClientboundConfigurationPacket::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,
});
}
ClientboundConfigurationPacket::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_connection = query.get_mut(player_entity).unwrap();
let instance_holder = crate::local_player::InstanceHolder::new(
player_entity,
// default to an empty world, it'll be set correctly later when we
// get the login packet
Arc::new(RwLock::new(Instance::default())),
);
raw_connection
.write_packet(ServerboundFinishConfigurationPacket {}.get())
.expect(
"we should be in the right state and encoding this packet shouldn't fail",
);
raw_connection.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::<InConfigurationState>()
.insert(crate::JoinedClientBundle {
instance_holder,
physics_state: crate::PhysicsState::default(),
inventory: crate::inventory::InventoryComponent::default(),
client_information: crate::ClientInformation::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::chunk_batching::ChunkBatchInfo::default(),
entity_id_index: EntityIdIndex::default(),
mining: crate::mining::MineBundle::default(),
attack: crate::attack::AttackBundle::default(),
_local_entity: azalea_entity::LocalEntity,
_loaded: Loaded,
});
}
ClientboundConfigurationPacket::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_connection = query.get(player_entity).unwrap();
keepalive_events.send(KeepAliveEvent {
entity: player_entity,
id: p.id,
});
raw_connection
.write_packet(ServerboundKeepAlivePacket { id: p.id }.get())
.unwrap();
}
ClientboundConfigurationPacket::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_connection = query.get_mut(player_entity).unwrap();
raw_connection
.write_packet(ServerboundPongPacket { id: p.id }.get())
.unwrap();
}
ClientboundConfigurationPacket::ResourcePack(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_connection = query.get_mut(player_entity).unwrap();
// always accept resource pack
raw_connection.write_packet(
ServerboundResourcePackPacket { action: azalea_protocol::packets::configuration::serverbound_resource_pack_packet::Action::Accepted }.get()
).unwrap();
}
ClientboundConfigurationPacket::UpdateEnabledFeatures(p) => {
debug!("Got update enabled features packet {p:?}");
}
ClientboundConfigurationPacket::UpdateTags(_p) => {
debug!("Got update tags packet");
}
}
}
}

View file

@ -0,0 +1,59 @@
use azalea_entity::{metadata::Health, EntityUpdateSet};
use bevy_app::{App, First, Plugin, PreUpdate, Update};
use bevy_ecs::prelude::*;
use crate::{chat::ChatReceivedEvent, events::death_listener};
use self::game::{
AddPlayerEvent, DeathEvent, InstanceLoadedEvent, KeepAliveEvent, RemovePlayerEvent,
ResourcePackEvent, UpdatePlayerEvent,
};
pub mod configuration;
pub mod game;
pub struct PacketHandlerPlugin;
pub fn death_event_on_0_health(
query: Query<(Entity, &Health), Changed<Health>>,
mut death_events: EventWriter<DeathEvent>,
) {
for (entity, health) in query.iter() {
if **health == 0. {
death_events.send(DeathEvent {
entity,
packet: None,
});
}
}
}
impl Plugin for PacketHandlerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
First,
(game::send_packet_events, configuration::send_packet_events),
)
.add_systems(
PreUpdate,
(
game::process_packet_events,
configuration::process_packet_events,
)
// we want to index and deindex right after
.before(EntityUpdateSet::Deindex),
)
.add_systems(Update, death_event_on_0_health.before(death_listener))
// we do this instead of add_event so we can handle the events ourselves
.init_resource::<Events<game::PacketEvent>>()
.init_resource::<Events<configuration::PacketEvent>>()
.add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>()
.add_event::<ChatReceivedEvent>()
.add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>()
.add_event::<ResourcePackEvent>()
.add_event::<InstanceLoadedEvent>();
}
}

View file

@ -3,7 +3,7 @@
use azalea_protocol::{ use azalea_protocol::{
connect::{Connection, ConnectionError}, connect::{Connection, ConnectionError},
packets::{ packets::{
handshake::client_intention_packet::ClientIntentionPacket, handshaking::client_intention_packet::ClientIntentionPacket,
status::{ status::{
clientbound_status_response_packet::ClientboundStatusResponsePacket, clientbound_status_response_packet::ClientboundStatusResponsePacket,
serverbound_status_request_packet::ServerboundStatusRequestPacket, serverbound_status_request_packet::ServerboundStatusRequestPacket,

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
}; };
use uuid::Uuid; use uuid::Uuid;
use crate::{packet_handling::AddPlayerEvent, GameProfileComponent}; use crate::{packet_handling::game::AddPlayerEvent, GameProfileComponent};
/// A player in the tab list. /// A player in the tab list.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -0,0 +1,174 @@
use std::fmt::Debug;
use std::sync::Arc;
use azalea_protocol::{
connect::{RawReadConnection, RawWriteConnection},
packets::{ConnectionProtocol, ProtocolPacket},
read::ReadPacketError,
write::serialize_packet,
};
use bevy_ecs::prelude::*;
use log::error;
use parking_lot::Mutex;
use thiserror::Error;
use tokio::sync::mpsc;
/// A component for clients that can read and write packets to the server. This
/// works with raw bytes, so you'll have to serialize/deserialize packets
/// yourself. It will do the compression and encryption for you though.
#[derive(Component)]
pub struct RawConnection {
reader: RawConnectionReader,
writer: RawConnectionWriter,
/// Packets sent to this will be sent to the server.
/// A task that reads packets from the server. The client is disconnected
/// when this task ends.
read_packets_task: tokio::task::JoinHandle<()>,
/// A task that writes packets from the server.
write_packets_task: tokio::task::JoinHandle<()>,
connection_protocol: ConnectionProtocol,
}
#[derive(Clone)]
struct RawConnectionReader {
pub incoming_packet_queue: Arc<Mutex<Vec<Vec<u8>>>>,
pub run_schedule_sender: mpsc::UnboundedSender<()>,
}
#[derive(Clone)]
struct RawConnectionWriter {
pub outgoing_packets_sender: mpsc::UnboundedSender<Vec<u8>>,
}
#[derive(Error, Debug)]
pub enum WritePacketError {
#[error("Wrong protocol state: expected {expected:?}, got {got:?}")]
WrongState {
expected: ConnectionProtocol,
got: ConnectionProtocol,
},
#[error(transparent)]
Encoding(#[from] azalea_protocol::write::PacketEncodeError),
}
impl RawConnection {
pub fn new(
run_schedule_sender: mpsc::UnboundedSender<()>,
connection_protocol: ConnectionProtocol,
raw_read_connection: RawReadConnection,
raw_write_connection: RawWriteConnection,
) -> Self {
let (outgoing_packets_sender, outgoing_packets_receiver) = mpsc::unbounded_channel();
let incoming_packet_queue = Arc::new(Mutex::new(Vec::new()));
let reader = RawConnectionReader {
incoming_packet_queue: incoming_packet_queue.clone(),
run_schedule_sender,
};
let writer = RawConnectionWriter {
outgoing_packets_sender,
};
let read_packets_task = tokio::spawn(reader.clone().read_task(raw_read_connection));
let write_packets_task = tokio::spawn(
writer
.clone()
.write_task(raw_write_connection, outgoing_packets_receiver),
);
Self {
reader,
writer,
read_packets_task,
write_packets_task,
connection_protocol,
}
}
pub fn write_raw_packet(&self, raw_packet: Vec<u8>) {
self.writer
.outgoing_packets_sender
.send(raw_packet)
.unwrap();
}
/// Write the packet with the given state to the server.
///
/// # Errors
///
/// Returns an error if the packet is not valid for the current state, or if
/// encoding it failed somehow (like it's too big or something).
pub fn write_packet<P: ProtocolPacket + Debug>(
&self,
packet: P,
) -> Result<(), WritePacketError> {
let raw_packet = serialize_packet(&packet)?;
self.write_raw_packet(raw_packet);
Ok(())
}
/// Returns whether the connection is still alive.
pub fn is_alive(&self) -> bool {
!self.read_packets_task.is_finished()
}
pub fn incoming_packet_queue(&self) -> Arc<Mutex<Vec<Vec<u8>>>> {
self.reader.incoming_packet_queue.clone()
}
pub fn set_state(&mut self, connection_protocol: ConnectionProtocol) {
self.connection_protocol = connection_protocol;
}
}
impl RawConnectionReader {
/// Loop that reads from the connection and adds the packets to the queue +
/// runs the schedule.
pub async fn read_task(self, mut read_conn: RawReadConnection) {
loop {
match read_conn.read().await {
Ok(raw_packet) => {
self.incoming_packet_queue.lock().push(raw_packet);
// tell the client to run all the systems
self.run_schedule_sender.send(()).unwrap();
}
Err(error) => {
if !matches!(*error, ReadPacketError::ConnectionClosed) {
error!("Error reading packet from Client: {error:?}");
}
break;
}
}
}
}
}
impl RawConnectionWriter {
/// Consume the [`ServerboundGamePacket`] queue and actually write the
/// packets to the server. It's like this so writing packets doesn't need to
/// be awaited.
pub async fn write_task(
self,
mut write_conn: RawWriteConnection,
mut outgoing_packets_receiver: mpsc::UnboundedReceiver<Vec<u8>>,
) {
while let Some(raw_packet) = outgoing_packets_receiver.recv().await {
if let Err(err) = write_conn.write(&raw_packet).await {
error!("Disconnecting because we couldn't write a packet: {err}.");
break;
};
}
// receiver is automatically closed when it's dropped
}
}
impl Drop for RawConnection {
/// Stop every active task when this `RawConnection` is dropped.
fn drop(&mut self) {
self.read_packets_task.abort();
self.write_packets_task.abort();
}
}

View file

@ -1,7 +1,28 @@
use azalea_protocol::packets::game::clientbound_login_packet::registry::RegistryRoot; use std::collections::HashMap;
use bevy_ecs::component::Component;
use derive_more::Deref;
/// The registries that the server sent us on login. use azalea_core::ResourceLocation;
#[derive(Clone, Debug, Component, Deref)] use azalea_nbt::Nbt;
pub struct ReceivedRegistries(pub RegistryRoot); use azalea_protocol::packets::configuration::clientbound_registry_data_packet::registry::{
DimensionTypeElement, RegistryType,
};
use bevy_ecs::prelude::*;
use serde::de::DeserializeOwned;
/// The registries that were sent to us during the configuration state.
#[derive(Default, Component, Clone)]
pub struct ReceivedRegistries {
pub registries: HashMap<ResourceLocation, Nbt>,
}
impl ReceivedRegistries {
fn get<T: DeserializeOwned>(&self, name: &ResourceLocation) -> Option<T> {
let nbt = self.registries.get(name)?;
serde_json::from_value(serde_json::to_value(nbt).ok()?).ok()
}
/// Get the dimension type registry, or `None` if it doesn't exist. You
/// should do some type of error handling if this returns `None`.
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
self.get(&ResourceLocation::new("minecraft:dimension_type"))
}
}

View file

@ -353,6 +353,8 @@ pub struct PlayerBundle {
/// A marker component that signifies that this entity is "local" and shouldn't /// A marker component that signifies that this entity is "local" and shouldn't
/// be updated by other clients. /// be updated by other clients.
///
/// If this is for a client then all of our clients will have this.
#[derive(Component)] #[derive(Component)]
pub struct LocalEntity; pub struct LocalEntity;

View file

@ -761,9 +761,11 @@ impl Default for BlazeMetadataBundle {
} }
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct BlockDisplayInterpolationStartDeltaTicks(pub i32); pub struct BlockDisplayTransformationInterpolationStartDeltaTicks(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct BlockDisplayInterpolationDuration(pub i32); pub struct BlockDisplayTransformationInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)]
pub struct BlockDisplayPosRotInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct BlockDisplayTranslation(pub Vec3); pub struct BlockDisplayTranslation(pub Vec3);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
@ -800,50 +802,55 @@ impl BlockDisplay {
match d.index { match d.index {
0..=7 => AbstractEntity::apply_metadata(entity, d)?, 0..=7 => AbstractEntity::apply_metadata(entity, d)?,
8 => { 8 => {
entity.insert(BlockDisplayInterpolationStartDeltaTicks( entity.insert(BlockDisplayTransformationInterpolationStartDeltaTicks(
d.value.into_int()?, d.value.into_int()?,
)); ));
} }
9 => { 9 => {
entity.insert(BlockDisplayInterpolationDuration(d.value.into_int()?)); entity.insert(BlockDisplayTransformationInterpolationDuration(
d.value.into_int()?,
));
} }
10 => { 10 => {
entity.insert(BlockDisplayTranslation(d.value.into_vector3()?)); entity.insert(BlockDisplayPosRotInterpolationDuration(d.value.into_int()?));
} }
11 => { 11 => {
entity.insert(BlockDisplayScale(d.value.into_vector3()?)); entity.insert(BlockDisplayTranslation(d.value.into_vector3()?));
} }
12 => { 12 => {
entity.insert(BlockDisplayLeftRotation(d.value.into_quaternion()?)); entity.insert(BlockDisplayScale(d.value.into_vector3()?));
} }
13 => { 13 => {
entity.insert(BlockDisplayRightRotation(d.value.into_quaternion()?)); entity.insert(BlockDisplayLeftRotation(d.value.into_quaternion()?));
} }
14 => { 14 => {
entity.insert(BlockDisplayBillboardRenderConstraints(d.value.into_byte()?)); entity.insert(BlockDisplayRightRotation(d.value.into_quaternion()?));
} }
15 => { 15 => {
entity.insert(BlockDisplayBrightnessOverride(d.value.into_int()?)); entity.insert(BlockDisplayBillboardRenderConstraints(d.value.into_byte()?));
} }
16 => { 16 => {
entity.insert(BlockDisplayViewRange(d.value.into_float()?)); entity.insert(BlockDisplayBrightnessOverride(d.value.into_int()?));
} }
17 => { 17 => {
entity.insert(BlockDisplayShadowRadius(d.value.into_float()?)); entity.insert(BlockDisplayViewRange(d.value.into_float()?));
} }
18 => { 18 => {
entity.insert(BlockDisplayShadowStrength(d.value.into_float()?)); entity.insert(BlockDisplayShadowRadius(d.value.into_float()?));
} }
19 => { 19 => {
entity.insert(BlockDisplayWidth(d.value.into_float()?)); entity.insert(BlockDisplayShadowStrength(d.value.into_float()?));
} }
20 => { 20 => {
entity.insert(BlockDisplayHeight(d.value.into_float()?)); entity.insert(BlockDisplayWidth(d.value.into_float()?));
} }
21 => { 21 => {
entity.insert(BlockDisplayGlowColorOverride(d.value.into_int()?)); entity.insert(BlockDisplayHeight(d.value.into_float()?));
} }
22 => { 22 => {
entity.insert(BlockDisplayGlowColorOverride(d.value.into_int()?));
}
23 => {
entity.insert(BlockState(d.value.into_block_state()?)); entity.insert(BlockState(d.value.into_block_state()?));
} }
_ => {} _ => {}
@ -856,8 +863,11 @@ impl BlockDisplay {
pub struct BlockDisplayMetadataBundle { pub struct BlockDisplayMetadataBundle {
_marker: BlockDisplay, _marker: BlockDisplay,
parent: AbstractEntityMetadataBundle, parent: AbstractEntityMetadataBundle,
block_display_interpolation_start_delta_ticks: BlockDisplayInterpolationStartDeltaTicks, block_display_transformation_interpolation_start_delta_ticks:
block_display_interpolation_duration: BlockDisplayInterpolationDuration, BlockDisplayTransformationInterpolationStartDeltaTicks,
block_display_transformation_interpolation_duration:
BlockDisplayTransformationInterpolationDuration,
block_display_pos_rot_interpolation_duration: BlockDisplayPosRotInterpolationDuration,
block_display_translation: BlockDisplayTranslation, block_display_translation: BlockDisplayTranslation,
block_display_scale: BlockDisplayScale, block_display_scale: BlockDisplayScale,
block_display_left_rotation: BlockDisplayLeftRotation, block_display_left_rotation: BlockDisplayLeftRotation,
@ -893,10 +903,13 @@ impl Default for BlockDisplayMetadataBundle {
pose: Pose::default(), pose: Pose::default(),
ticks_frozen: TicksFrozen(0), ticks_frozen: TicksFrozen(0),
}, },
block_display_interpolation_start_delta_ticks: BlockDisplayInterpolationStartDeltaTicks( block_display_transformation_interpolation_start_delta_ticks:
BlockDisplayTransformationInterpolationStartDeltaTicks(0),
block_display_transformation_interpolation_duration:
BlockDisplayTransformationInterpolationDuration(0),
block_display_pos_rot_interpolation_duration: BlockDisplayPosRotInterpolationDuration(
0, 0,
), ),
block_display_interpolation_duration: BlockDisplayInterpolationDuration(0),
block_display_translation: BlockDisplayTranslation(Vec3 { block_display_translation: BlockDisplayTranslation(Vec3 {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
@ -1901,7 +1914,7 @@ impl Default for DolphinMetadataBundle {
aggressive: Aggressive(false), aggressive: Aggressive(false),
}, },
}, },
treasure_pos: TreasurePos(BlockPos::new(0, 0, 0)), treasure_pos: TreasurePos(Default::default()),
got_fish: GotFish(false), got_fish: GotFish(false),
moistness_level: MoistnessLevel(2400), moistness_level: MoistnessLevel(2400),
} }
@ -2929,7 +2942,7 @@ impl Default for FallingBlockMetadataBundle {
pose: Pose::default(), pose: Pose::default(),
ticks_frozen: TicksFrozen(0), ticks_frozen: TicksFrozen(0),
}, },
start_pos: StartPos(BlockPos::new(0, 0, 0)), start_pos: StartPos(Default::default()),
} }
} }
} }
@ -4409,9 +4422,11 @@ impl Default for ItemMetadataBundle {
} }
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct ItemDisplayInterpolationStartDeltaTicks(pub i32); pub struct ItemDisplayTransformationInterpolationStartDeltaTicks(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct ItemDisplayInterpolationDuration(pub i32); pub struct ItemDisplayTransformationInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)]
pub struct ItemDisplayPosRotInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct ItemDisplayTranslation(pub Vec3); pub struct ItemDisplayTranslation(pub Vec3);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
@ -4450,51 +4465,58 @@ impl ItemDisplay {
match d.index { match d.index {
0..=7 => AbstractEntity::apply_metadata(entity, d)?, 0..=7 => AbstractEntity::apply_metadata(entity, d)?,
8 => { 8 => {
entity.insert(ItemDisplayInterpolationStartDeltaTicks(d.value.into_int()?)); entity.insert(ItemDisplayTransformationInterpolationStartDeltaTicks(
d.value.into_int()?,
));
} }
9 => { 9 => {
entity.insert(ItemDisplayInterpolationDuration(d.value.into_int()?)); entity.insert(ItemDisplayTransformationInterpolationDuration(
d.value.into_int()?,
));
} }
10 => { 10 => {
entity.insert(ItemDisplayTranslation(d.value.into_vector3()?)); entity.insert(ItemDisplayPosRotInterpolationDuration(d.value.into_int()?));
} }
11 => { 11 => {
entity.insert(ItemDisplayScale(d.value.into_vector3()?)); entity.insert(ItemDisplayTranslation(d.value.into_vector3()?));
} }
12 => { 12 => {
entity.insert(ItemDisplayLeftRotation(d.value.into_quaternion()?)); entity.insert(ItemDisplayScale(d.value.into_vector3()?));
} }
13 => { 13 => {
entity.insert(ItemDisplayRightRotation(d.value.into_quaternion()?)); entity.insert(ItemDisplayLeftRotation(d.value.into_quaternion()?));
} }
14 => { 14 => {
entity.insert(ItemDisplayBillboardRenderConstraints(d.value.into_byte()?)); entity.insert(ItemDisplayRightRotation(d.value.into_quaternion()?));
} }
15 => { 15 => {
entity.insert(ItemDisplayBrightnessOverride(d.value.into_int()?)); entity.insert(ItemDisplayBillboardRenderConstraints(d.value.into_byte()?));
} }
16 => { 16 => {
entity.insert(ItemDisplayViewRange(d.value.into_float()?)); entity.insert(ItemDisplayBrightnessOverride(d.value.into_int()?));
} }
17 => { 17 => {
entity.insert(ItemDisplayShadowRadius(d.value.into_float()?)); entity.insert(ItemDisplayViewRange(d.value.into_float()?));
} }
18 => { 18 => {
entity.insert(ItemDisplayShadowStrength(d.value.into_float()?)); entity.insert(ItemDisplayShadowRadius(d.value.into_float()?));
} }
19 => { 19 => {
entity.insert(ItemDisplayWidth(d.value.into_float()?)); entity.insert(ItemDisplayShadowStrength(d.value.into_float()?));
} }
20 => { 20 => {
entity.insert(ItemDisplayHeight(d.value.into_float()?)); entity.insert(ItemDisplayWidth(d.value.into_float()?));
} }
21 => { 21 => {
entity.insert(ItemDisplayGlowColorOverride(d.value.into_int()?)); entity.insert(ItemDisplayHeight(d.value.into_float()?));
} }
22 => { 22 => {
entity.insert(ItemDisplayItemStack(d.value.into_item_stack()?)); entity.insert(ItemDisplayGlowColorOverride(d.value.into_int()?));
} }
23 => { 23 => {
entity.insert(ItemDisplayItemStack(d.value.into_item_stack()?));
}
24 => {
entity.insert(ItemDisplayItemDisplay(d.value.into_byte()?)); entity.insert(ItemDisplayItemDisplay(d.value.into_byte()?));
} }
_ => {} _ => {}
@ -4507,8 +4529,11 @@ impl ItemDisplay {
pub struct ItemDisplayMetadataBundle { pub struct ItemDisplayMetadataBundle {
_marker: ItemDisplay, _marker: ItemDisplay,
parent: AbstractEntityMetadataBundle, parent: AbstractEntityMetadataBundle,
item_display_interpolation_start_delta_ticks: ItemDisplayInterpolationStartDeltaTicks, item_display_transformation_interpolation_start_delta_ticks:
item_display_interpolation_duration: ItemDisplayInterpolationDuration, ItemDisplayTransformationInterpolationStartDeltaTicks,
item_display_transformation_interpolation_duration:
ItemDisplayTransformationInterpolationDuration,
item_display_pos_rot_interpolation_duration: ItemDisplayPosRotInterpolationDuration,
item_display_translation: ItemDisplayTranslation, item_display_translation: ItemDisplayTranslation,
item_display_scale: ItemDisplayScale, item_display_scale: ItemDisplayScale,
item_display_left_rotation: ItemDisplayLeftRotation, item_display_left_rotation: ItemDisplayLeftRotation,
@ -4545,10 +4570,11 @@ impl Default for ItemDisplayMetadataBundle {
pose: Pose::default(), pose: Pose::default(),
ticks_frozen: TicksFrozen(0), ticks_frozen: TicksFrozen(0),
}, },
item_display_interpolation_start_delta_ticks: ItemDisplayInterpolationStartDeltaTicks( item_display_transformation_interpolation_start_delta_ticks:
0, ItemDisplayTransformationInterpolationStartDeltaTicks(0),
), item_display_transformation_interpolation_duration:
item_display_interpolation_duration: ItemDisplayInterpolationDuration(0), ItemDisplayTransformationInterpolationDuration(0),
item_display_pos_rot_interpolation_duration: ItemDisplayPosRotInterpolationDuration(0),
item_display_translation: ItemDisplayTranslation(Vec3 { item_display_translation: ItemDisplayTranslation(Vec3 {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
@ -6106,7 +6132,7 @@ impl Default for PlayerMetadataBundle {
player_absorption: PlayerAbsorption(0.0), player_absorption: PlayerAbsorption(0.0),
score: Score(0), score: Score(0),
player_mode_customisation: PlayerModeCustomisation(0), player_mode_customisation: PlayerModeCustomisation(0),
player_main_hand: PlayerMainHand(1), player_main_hand: PlayerMainHand(Default::default()),
shoulder_left: ShoulderLeft(azalea_nbt::Nbt::Compound(Default::default())), shoulder_left: ShoulderLeft(azalea_nbt::Nbt::Compound(Default::default())),
shoulder_right: ShoulderRight(azalea_nbt::Nbt::Compound(Default::default())), shoulder_right: ShoulderRight(azalea_nbt::Nbt::Compound(Default::default())),
} }
@ -7830,9 +7856,11 @@ impl Default for TadpoleMetadataBundle {
} }
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct TextDisplayInterpolationStartDeltaTicks(pub i32); pub struct TextDisplayTransformationInterpolationStartDeltaTicks(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct TextDisplayInterpolationDuration(pub i32); pub struct TextDisplayTransformationInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)]
pub struct TextDisplayPosRotInterpolationDuration(pub i32);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
pub struct TextDisplayTranslation(pub Vec3); pub struct TextDisplayTranslation(pub Vec3);
#[derive(Component, Deref, DerefMut, Clone)] #[derive(Component, Deref, DerefMut, Clone)]
@ -7877,60 +7905,67 @@ impl TextDisplay {
match d.index { match d.index {
0..=7 => AbstractEntity::apply_metadata(entity, d)?, 0..=7 => AbstractEntity::apply_metadata(entity, d)?,
8 => { 8 => {
entity.insert(TextDisplayInterpolationStartDeltaTicks(d.value.into_int()?)); entity.insert(TextDisplayTransformationInterpolationStartDeltaTicks(
d.value.into_int()?,
));
} }
9 => { 9 => {
entity.insert(TextDisplayInterpolationDuration(d.value.into_int()?)); entity.insert(TextDisplayTransformationInterpolationDuration(
d.value.into_int()?,
));
} }
10 => { 10 => {
entity.insert(TextDisplayTranslation(d.value.into_vector3()?)); entity.insert(TextDisplayPosRotInterpolationDuration(d.value.into_int()?));
} }
11 => { 11 => {
entity.insert(TextDisplayScale(d.value.into_vector3()?)); entity.insert(TextDisplayTranslation(d.value.into_vector3()?));
} }
12 => { 12 => {
entity.insert(TextDisplayLeftRotation(d.value.into_quaternion()?)); entity.insert(TextDisplayScale(d.value.into_vector3()?));
} }
13 => { 13 => {
entity.insert(TextDisplayRightRotation(d.value.into_quaternion()?)); entity.insert(TextDisplayLeftRotation(d.value.into_quaternion()?));
} }
14 => { 14 => {
entity.insert(TextDisplayBillboardRenderConstraints(d.value.into_byte()?)); entity.insert(TextDisplayRightRotation(d.value.into_quaternion()?));
} }
15 => { 15 => {
entity.insert(TextDisplayBrightnessOverride(d.value.into_int()?)); entity.insert(TextDisplayBillboardRenderConstraints(d.value.into_byte()?));
} }
16 => { 16 => {
entity.insert(TextDisplayViewRange(d.value.into_float()?)); entity.insert(TextDisplayBrightnessOverride(d.value.into_int()?));
} }
17 => { 17 => {
entity.insert(TextDisplayShadowRadius(d.value.into_float()?)); entity.insert(TextDisplayViewRange(d.value.into_float()?));
} }
18 => { 18 => {
entity.insert(TextDisplayShadowStrength(d.value.into_float()?)); entity.insert(TextDisplayShadowRadius(d.value.into_float()?));
} }
19 => { 19 => {
entity.insert(TextDisplayWidth(d.value.into_float()?)); entity.insert(TextDisplayShadowStrength(d.value.into_float()?));
} }
20 => { 20 => {
entity.insert(TextDisplayHeight(d.value.into_float()?)); entity.insert(TextDisplayWidth(d.value.into_float()?));
} }
21 => { 21 => {
entity.insert(TextDisplayGlowColorOverride(d.value.into_int()?)); entity.insert(TextDisplayHeight(d.value.into_float()?));
} }
22 => { 22 => {
entity.insert(Text(d.value.into_formatted_text()?)); entity.insert(TextDisplayGlowColorOverride(d.value.into_int()?));
} }
23 => { 23 => {
entity.insert(LineWidth(d.value.into_int()?)); entity.insert(Text(d.value.into_formatted_text()?));
} }
24 => { 24 => {
entity.insert(BackgroundColor(d.value.into_int()?)); entity.insert(LineWidth(d.value.into_int()?));
} }
25 => { 25 => {
entity.insert(TextOpacity(d.value.into_byte()?)); entity.insert(BackgroundColor(d.value.into_int()?));
} }
26 => { 26 => {
entity.insert(TextOpacity(d.value.into_byte()?));
}
27 => {
entity.insert(StyleFlags(d.value.into_byte()?)); entity.insert(StyleFlags(d.value.into_byte()?));
} }
_ => {} _ => {}
@ -7943,8 +7978,11 @@ impl TextDisplay {
pub struct TextDisplayMetadataBundle { pub struct TextDisplayMetadataBundle {
_marker: TextDisplay, _marker: TextDisplay,
parent: AbstractEntityMetadataBundle, parent: AbstractEntityMetadataBundle,
text_display_interpolation_start_delta_ticks: TextDisplayInterpolationStartDeltaTicks, text_display_transformation_interpolation_start_delta_ticks:
text_display_interpolation_duration: TextDisplayInterpolationDuration, TextDisplayTransformationInterpolationStartDeltaTicks,
text_display_transformation_interpolation_duration:
TextDisplayTransformationInterpolationDuration,
text_display_pos_rot_interpolation_duration: TextDisplayPosRotInterpolationDuration,
text_display_translation: TextDisplayTranslation, text_display_translation: TextDisplayTranslation,
text_display_scale: TextDisplayScale, text_display_scale: TextDisplayScale,
text_display_left_rotation: TextDisplayLeftRotation, text_display_left_rotation: TextDisplayLeftRotation,
@ -7984,10 +8022,11 @@ impl Default for TextDisplayMetadataBundle {
pose: Pose::default(), pose: Pose::default(),
ticks_frozen: TicksFrozen(0), ticks_frozen: TicksFrozen(0),
}, },
text_display_interpolation_start_delta_ticks: TextDisplayInterpolationStartDeltaTicks( text_display_transformation_interpolation_start_delta_ticks:
0, TextDisplayTransformationInterpolationStartDeltaTicks(0),
), text_display_transformation_interpolation_duration:
text_display_interpolation_duration: TextDisplayInterpolationDuration(0), TextDisplayTransformationInterpolationDuration(0),
text_display_pos_rot_interpolation_duration: TextDisplayPosRotInterpolationDuration(0),
text_display_translation: TextDisplayTranslation(Vec3 { text_display_translation: TextDisplayTranslation(Vec3 {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
@ -8486,10 +8525,10 @@ impl Default for TurtleMetadataBundle {
abstract_ageable_baby: AbstractAgeableBaby(false), abstract_ageable_baby: AbstractAgeableBaby(false),
}, },
}, },
home_pos: HomePos(BlockPos::new(0, 0, 0)), home_pos: HomePos(Default::default()),
has_egg: HasEgg(false), has_egg: HasEgg(false),
laying_egg: LayingEgg(false), laying_egg: LayingEgg(false),
travel_pos: TravelPos(BlockPos::new(0, 0, 0)), travel_pos: TravelPos(Default::default()),
going_home: GoingHome(false), going_home: GoingHome(false),
travelling: Travelling(false), travelling: Travelling(false),
} }

View file

@ -51,7 +51,7 @@ pub struct RelativeEntityUpdate {
/// This is used for making sure two clients don't do the same relative update /// This is used for making sure two clients don't do the same relative update
/// on an entity. /// on an entity.
/// ///
/// If an entity is local (i.e. it's a client/localplayer), this component /// If an entity is local (i.e. it's a client/LocalEntity), this component
/// should NOT be present in the entity. /// should NOT be present in the entity.
#[derive(Component, Debug, Deref, DerefMut)] #[derive(Component, Debug, Deref, DerefMut)]
pub struct UpdatesReceived(u32); pub struct UpdatesReceived(u32);

View file

@ -200,6 +200,7 @@
"advancements.nether.uneasy_alliance.title": "Uneasy Alliance", "advancements.nether.uneasy_alliance.title": "Uneasy Alliance",
"advancements.nether.use_lodestone.description": "Use a Compass on a Lodestone", "advancements.nether.use_lodestone.description": "Use a Compass on a Lodestone",
"advancements.nether.use_lodestone.title": "Country Lode, Take Me Home", "advancements.nether.use_lodestone.title": "Country Lode, Take Me Home",
"advancements.progress": "%s/%s",
"advancements.sad_label": ":(", "advancements.sad_label": ":(",
"advancements.story.cure_zombie_villager.description": "Weaken and then cure a Zombie Villager", "advancements.story.cure_zombie_villager.description": "Weaken and then cure a Zombie Villager",
"advancements.story.cure_zombie_villager.title": "Zombie Doctor", "advancements.story.cure_zombie_villager.title": "Zombie Doctor",
@ -393,6 +394,7 @@
"attribute.name.generic.follow_range": "Mob Follow Range", "attribute.name.generic.follow_range": "Mob Follow Range",
"attribute.name.generic.knockback_resistance": "Knockback Resistance", "attribute.name.generic.knockback_resistance": "Knockback Resistance",
"attribute.name.generic.luck": "Luck", "attribute.name.generic.luck": "Luck",
"attribute.name.generic.max_absorption": "Max Absorption",
"attribute.name.generic.max_health": "Max Health", "attribute.name.generic.max_health": "Max Health",
"attribute.name.generic.movement_speed": "Speed", "attribute.name.generic.movement_speed": "Speed",
"attribute.name.horse.jump_strength": "Horse Jump Strength", "attribute.name.horse.jump_strength": "Horse Jump Strength",
@ -2154,6 +2156,7 @@
"chat.link.warning": "Never open links from people that you don't trust!", "chat.link.warning": "Never open links from people that you don't trust!",
"chat.queue": "[+%s pending lines]", "chat.queue": "[+%s pending lines]",
"chat.square_brackets": "[%s]", "chat.square_brackets": "[%s]",
"chat.tag.error": "Server sent invalid message.",
"chat.tag.modified": "Message modified by the server. Original:", "chat.tag.modified": "Message modified by the server. Original:",
"chat.tag.not_secure": "Unverified message. Cannot be reported.", "chat.tag.not_secure": "Unverified message. Cannot be reported.",
"chat.tag.system": "Server message. Cannot be reported.", "chat.tag.system": "Server message. Cannot be reported.",
@ -2169,6 +2172,7 @@
"chat.type.team.text": "%s <%s> %s", "chat.type.team.text": "%s <%s> %s",
"chat.type.text": "<%s> %s", "chat.type.text": "<%s> %s",
"chat.type.text.narrate": "%s says %s", "chat.type.text.narrate": "%s says %s",
"chat.validation_error": "Chat validation error",
"clear.failed.multiple": "No items were found on %s players", "clear.failed.multiple": "No items were found on %s players",
"clear.failed.single": "No items were found on player %s", "clear.failed.single": "No items were found on player %s",
"color.minecraft.black": "Black", "color.minecraft.black": "Black",
@ -2237,6 +2241,7 @@
"commands.banip.invalid": "Invalid IP address or unknown player", "commands.banip.invalid": "Invalid IP address or unknown player",
"commands.banip.success": "Banned IP %s: %s", "commands.banip.success": "Banned IP %s: %s",
"commands.banlist.entry": "%s was banned by %s: %s", "commands.banlist.entry": "%s was banned by %s: %s",
"commands.banlist.entry.unknown": "(Unknown)",
"commands.banlist.list": "There are %s ban(s):", "commands.banlist.list": "There are %s ban(s):",
"commands.banlist.none": "There are no bans", "commands.banlist.none": "There are no bans",
"commands.bossbar.create.failed": "A bossbar already exists with the ID '%s'", "commands.bossbar.create.failed": "A bossbar already exists with the ID '%s'",
@ -2379,6 +2384,10 @@
"commands.forceload.removed.multiple": "Unmarked %s chunks in %s from %s to %s for force loading", "commands.forceload.removed.multiple": "Unmarked %s chunks in %s from %s to %s for force loading",
"commands.forceload.removed.single": "Unmarked chunk %s in %s for force loading", "commands.forceload.removed.single": "Unmarked chunk %s in %s for force loading",
"commands.forceload.toobig": "Too many chunks in the specified area (maximum %s, specified %s)", "commands.forceload.toobig": "Too many chunks in the specified area (maximum %s, specified %s)",
"commands.function.error.argument_not_compound": "Invalid argument type: %s, expected Compound",
"commands.function.error.missing_argument": "Missing argument %2$s to function %1$s",
"commands.function.error.missing_arguments": "Missing arguments to function %s",
"commands.function.error.parse": "While instantiating macro %s: Command '%s' caused error: %s",
"commands.function.success.multiple": "Executed %s command(s) from %s functions", "commands.function.success.multiple": "Executed %s command(s) from %s functions",
"commands.function.success.multiple.result": "Executed %s functions", "commands.function.success.multiple.result": "Executed %s functions",
"commands.function.success.single": "Executed %s command(s) from function '%s'", "commands.function.success.single": "Executed %s command(s) from function '%s'",
@ -2452,6 +2461,12 @@
"commands.publish.failed": "Unable to host local game", "commands.publish.failed": "Unable to host local game",
"commands.publish.started": "Local game hosted on port %s", "commands.publish.started": "Local game hosted on port %s",
"commands.publish.success": "Multiplayer game is now hosted on port %s", "commands.publish.success": "Multiplayer game is now hosted on port %s",
"commands.random.error.range_too_large": "The range of the random value must be at most 2147483646",
"commands.random.error.range_too_small": "The range of the random value must be at least 2",
"commands.random.reset.all.success": "Reset %s random sequence(s)",
"commands.random.reset.success": "Reset random sequence %s",
"commands.random.roll": "%s rolled %s (from %s to %s)",
"commands.random.sample.success": "Randomized value: %s",
"commands.recipe.give.failed": "No new recipes were learned", "commands.recipe.give.failed": "No new recipes were learned",
"commands.recipe.give.success.multiple": "Unlocked %s recipes for %s players", "commands.recipe.give.success.multiple": "Unlocked %s recipes for %s players",
"commands.recipe.give.success.single": "Unlocked %s recipes for %s", "commands.recipe.give.success.single": "Unlocked %s recipes for %s",
@ -2649,6 +2664,8 @@
"connect.failed": "Failed to connect to the server", "connect.failed": "Failed to connect to the server",
"connect.joining": "Joining world...", "connect.joining": "Joining world...",
"connect.negotiating": "Negotiating...", "connect.negotiating": "Negotiating...",
"connect.reconfiging": "Reconfiguring...",
"connect.reconfiguring": "Reconfiguring...",
"container.barrel": "Barrel", "container.barrel": "Barrel",
"container.beacon": "Beacon", "container.beacon": "Beacon",
"container.blast_furnace": "Blast Furnace", "container.blast_furnace": "Blast Furnace",
@ -2679,7 +2696,9 @@
"container.repair.cost": "Enchantment Cost: %1$s", "container.repair.cost": "Enchantment Cost: %1$s",
"container.repair.expensive": "Too Expensive!", "container.repair.expensive": "Too Expensive!",
"container.shulkerBox": "Shulker Box", "container.shulkerBox": "Shulker Box",
"container.shulkerBox.itemCount": "%s x%s",
"container.shulkerBox.more": "and %s more...", "container.shulkerBox.more": "and %s more...",
"container.shulkerBox.unknownContents": "???????",
"container.smoker": "Smoker", "container.smoker": "Smoker",
"container.spectatorCantOpen": "Unable to open. Loot not generated yet.", "container.spectatorCantOpen": "Unable to open. Loot not generated yet.",
"container.stonecutter": "Stonecutter", "container.stonecutter": "Stonecutter",
@ -2779,6 +2798,8 @@
"dataPack.bundle.description": "Enables experimental Bundle item", "dataPack.bundle.description": "Enables experimental Bundle item",
"dataPack.bundle.name": "Bundles", "dataPack.bundle.name": "Bundles",
"dataPack.title": "Select Data Packs", "dataPack.title": "Select Data Packs",
"dataPack.trade_rebalance.description": "Updated trades for Villagers",
"dataPack.trade_rebalance.name": "Villager Trade Rebalance",
"dataPack.update_1_20.description": "New features and content for Minecraft 1.20", "dataPack.update_1_20.description": "New features and content for Minecraft 1.20",
"dataPack.update_1_20.name": "Update 1.20", "dataPack.update_1_20.name": "Update 1.20",
"dataPack.validation.back": "Go Back", "dataPack.validation.back": "Go Back",
@ -2792,90 +2813,90 @@
"datapackFailure.safeMode.failed.title": "Failed to load world in Safe Mode.", "datapackFailure.safeMode.failed.title": "Failed to load world in Safe Mode.",
"datapackFailure.title": "Errors in currently selected data packs prevented the world from loading.\nYou can either try to load it with only the vanilla data pack (\"safe mode\"), or go back to the title screen and fix it manually.", "datapackFailure.title": "Errors in currently selected data packs prevented the world from loading.\nYou can either try to load it with only the vanilla data pack (\"safe mode\"), or go back to the title screen and fix it manually.",
"death.attack.anvil": "%1$s was squashed by a falling anvil", "death.attack.anvil": "%1$s was squashed by a falling anvil",
"death.attack.anvil.player": "%1$s was squashed by a falling anvil whilst fighting %2$s", "death.attack.anvil.player": "%1$s was squashed by a falling anvil while fighting %2$s",
"death.attack.arrow": "%1$s was shot by %2$s", "death.attack.arrow": "%1$s was shot by %2$s",
"death.attack.arrow.item": "%1$s was shot by %2$s using %3$s", "death.attack.arrow.item": "%1$s was shot by %2$s using %3$s",
"death.attack.badRespawnPoint.link": "Intentional Game Design", "death.attack.badRespawnPoint.link": "Intentional Game Design",
"death.attack.badRespawnPoint.message": "%1$s was killed by %2$s", "death.attack.badRespawnPoint.message": "%1$s was killed by %2$s",
"death.attack.cactus": "%1$s was pricked to death", "death.attack.cactus": "%1$s was pricked to death",
"death.attack.cactus.player": "%1$s walked into a cactus whilst trying to escape %2$s", "death.attack.cactus.player": "%1$s walked into a cactus while trying to escape %2$s",
"death.attack.cramming": "%1$s was squished too much", "death.attack.cramming": "%1$s was squished too much",
"death.attack.cramming.player": "%1$s was squashed by %2$s", "death.attack.cramming.player": "%1$s was squashed by %2$s",
"death.attack.dragonBreath": "%1$s was roasted in dragon's breath", "death.attack.dragonBreath": "%1$s was roasted in dragon's breath",
"death.attack.dragonBreath.player": "%1$s was roasted in dragon's breath by %2$s", "death.attack.dragonBreath.player": "%1$s was roasted in dragon's breath by %2$s",
"death.attack.drown": "%1$s drowned", "death.attack.drown": "%1$s drowned",
"death.attack.drown.player": "%1$s drowned whilst trying to escape %2$s", "death.attack.drown.player": "%1$s drowned while trying to escape %2$s",
"death.attack.dryout": "%1$s died from dehydration", "death.attack.dryout": "%1$s died from dehydration",
"death.attack.dryout.player": "%1$s died from dehydration whilst trying to escape %2$s", "death.attack.dryout.player": "%1$s died from dehydration while trying to escape %2$s",
"death.attack.even_more_magic": "%1$s was killed by even more magic", "death.attack.even_more_magic": "%1$s was killed by even more magic",
"death.attack.explosion": "%1$s blew up", "death.attack.explosion": "%1$s blew up",
"death.attack.explosion.player": "%1$s was blown up by %2$s", "death.attack.explosion.player": "%1$s was blown up by %2$s",
"death.attack.explosion.player.item": "%1$s was blown up by %2$s using %3$s", "death.attack.explosion.player.item": "%1$s was blown up by %2$s using %3$s",
"death.attack.fall": "%1$s hit the ground too hard", "death.attack.fall": "%1$s hit the ground too hard",
"death.attack.fall.player": "%1$s hit the ground too hard whilst trying to escape %2$s", "death.attack.fall.player": "%1$s hit the ground too hard while trying to escape %2$s",
"death.attack.fallingBlock": "%1$s was squashed by a falling block", "death.attack.fallingBlock": "%1$s was squashed by a falling block",
"death.attack.fallingBlock.player": "%1$s was squashed by a falling block whilst fighting %2$s", "death.attack.fallingBlock.player": "%1$s was squashed by a falling block while fighting %2$s",
"death.attack.fallingStalactite": "%1$s was skewered by a falling stalactite", "death.attack.fallingStalactite": "%1$s was skewered by a falling stalactite",
"death.attack.fallingStalactite.player": "%1$s was skewered by a falling stalactite whilst fighting %2$s", "death.attack.fallingStalactite.player": "%1$s was skewered by a falling stalactite while fighting %2$s",
"death.attack.fireball": "%1$s was fireballed by %2$s", "death.attack.fireball": "%1$s was fireballed by %2$s",
"death.attack.fireball.item": "%1$s was fireballed by %2$s using %3$s", "death.attack.fireball.item": "%1$s was fireballed by %2$s using %3$s",
"death.attack.fireworks": "%1$s went off with a bang", "death.attack.fireworks": "%1$s went off with a bang",
"death.attack.fireworks.item": "%1$s went off with a bang due to a firework fired from %3$s by %2$s", "death.attack.fireworks.item": "%1$s went off with a bang due to a firework fired from %3$s by %2$s",
"death.attack.fireworks.player": "%1$s went off with a bang whilst fighting %2$s", "death.attack.fireworks.player": "%1$s went off with a bang while fighting %2$s",
"death.attack.flyIntoWall": "%1$s experienced kinetic energy", "death.attack.flyIntoWall": "%1$s experienced kinetic energy",
"death.attack.flyIntoWall.player": "%1$s experienced kinetic energy whilst trying to escape %2$s", "death.attack.flyIntoWall.player": "%1$s experienced kinetic energy while trying to escape %2$s",
"death.attack.freeze": "%1$s froze to death", "death.attack.freeze": "%1$s froze to death",
"death.attack.freeze.player": "%1$s was frozen to death by %2$s", "death.attack.freeze.player": "%1$s was frozen to death by %2$s",
"death.attack.generic": "%1$s died", "death.attack.generic": "%1$s died",
"death.attack.genericKill": "%1$s was killed",
"death.attack.genericKill.player": "%1$s was killed whilst fighting %2$s",
"death.attack.generic.player": "%1$s died because of %2$s", "death.attack.generic.player": "%1$s died because of %2$s",
"death.attack.genericKill": "%1$s was killed",
"death.attack.genericKill.player": "%1$s was killed while fighting %2$s",
"death.attack.hotFloor": "%1$s discovered the floor was lava", "death.attack.hotFloor": "%1$s discovered the floor was lava",
"death.attack.hotFloor.player": "%1$s walked into the danger zone due to %2$s", "death.attack.hotFloor.player": "%1$s walked into the danger zone due to %2$s",
"death.attack.indirectMagic": "%1$s was killed by %2$s using magic", "death.attack.indirectMagic": "%1$s was killed by %2$s using magic",
"death.attack.indirectMagic.item": "%1$s was killed by %2$s using %3$s", "death.attack.indirectMagic.item": "%1$s was killed by %2$s using %3$s",
"death.attack.inFire": "%1$s went up in flames", "death.attack.inFire": "%1$s went up in flames",
"death.attack.inFire.player": "%1$s walked into fire whilst fighting %2$s", "death.attack.inFire.player": "%1$s walked into fire while fighting %2$s",
"death.attack.inWall": "%1$s suffocated in a wall", "death.attack.inWall": "%1$s suffocated in a wall",
"death.attack.inWall.player": "%1$s suffocated in a wall whilst fighting %2$s", "death.attack.inWall.player": "%1$s suffocated in a wall while fighting %2$s",
"death.attack.lava": "%1$s tried to swim in lava", "death.attack.lava": "%1$s tried to swim in lava",
"death.attack.lava.player": "%1$s tried to swim in lava to escape %2$s", "death.attack.lava.player": "%1$s tried to swim in lava to escape %2$s",
"death.attack.lightningBolt": "%1$s was struck by lightning", "death.attack.lightningBolt": "%1$s was struck by lightning",
"death.attack.lightningBolt.player": "%1$s was struck by lightning whilst fighting %2$s", "death.attack.lightningBolt.player": "%1$s was struck by lightning while fighting %2$s",
"death.attack.magic": "%1$s was killed by magic", "death.attack.magic": "%1$s was killed by magic",
"death.attack.magic.player": "%1$s was killed by magic whilst trying to escape %2$s", "death.attack.magic.player": "%1$s was killed by magic while trying to escape %2$s",
"death.attack.message_too_long": "Actually, the message was too long to deliver fully. Sorry! Here's stripped version: %s", "death.attack.message_too_long": "Actually, the message was too long to deliver fully. Sorry! Here's a stripped version: %s",
"death.attack.mob": "%1$s was slain by %2$s", "death.attack.mob": "%1$s was slain by %2$s",
"death.attack.mob.item": "%1$s was slain by %2$s using %3$s", "death.attack.mob.item": "%1$s was slain by %2$s using %3$s",
"death.attack.onFire": "%1$s burned to death", "death.attack.onFire": "%1$s burned to death",
"death.attack.onFire.item": "%1$s was burnt to a crisp whilst fighting %2$s wielding %3$s", "death.attack.onFire.item": "%1$s was burned to a crisp while fighting %2$s wielding %3$s",
"death.attack.onFire.player": "%1$s was burnt to a crisp whilst fighting %2$s", "death.attack.onFire.player": "%1$s was burned to a crisp while fighting %2$s",
"death.attack.outsideBorder": "%1$s left the confines of this world",
"death.attack.outsideBorder.player": "%1$s left the confines of this world whilst fighting %2$s",
"death.attack.outOfWorld": "%1$s fell out of the world", "death.attack.outOfWorld": "%1$s fell out of the world",
"death.attack.outOfWorld.player": "%1$s didn't want to live in the same world as %2$s", "death.attack.outOfWorld.player": "%1$s didn't want to live in the same world as %2$s",
"death.attack.outsideBorder": "%1$s left the confines of this world",
"death.attack.outsideBorder.player": "%1$s left the confines of this world while fighting %2$s",
"death.attack.player": "%1$s was slain by %2$s", "death.attack.player": "%1$s was slain by %2$s",
"death.attack.player.item": "%1$s was slain by %2$s using %3$s", "death.attack.player.item": "%1$s was slain by %2$s using %3$s",
"death.attack.sonic_boom": "%1$s was obliterated by a sonically-charged shriek", "death.attack.sonic_boom": "%1$s was obliterated by a sonically-charged shriek",
"death.attack.sonic_boom.item": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s wielding %3$s", "death.attack.sonic_boom.item": "%1$s was obliterated by a sonically-charged shriek while trying to escape %2$s wielding %3$s",
"death.attack.sonic_boom.player": "%1$s was obliterated by a sonically-charged shriek whilst trying to escape %2$s", "death.attack.sonic_boom.player": "%1$s was obliterated by a sonically-charged shriek while trying to escape %2$s",
"death.attack.stalagmite": "%1$s was impaled on a stalagmite", "death.attack.stalagmite": "%1$s was impaled on a stalagmite",
"death.attack.stalagmite.player": "%1$s was impaled on a stalagmite whilst fighting %2$s", "death.attack.stalagmite.player": "%1$s was impaled on a stalagmite while fighting %2$s",
"death.attack.starve": "%1$s starved to death", "death.attack.starve": "%1$s starved to death",
"death.attack.starve.player": "%1$s starved to death whilst fighting %2$s", "death.attack.starve.player": "%1$s starved to death while fighting %2$s",
"death.attack.sting": "%1$s was stung to death", "death.attack.sting": "%1$s was stung to death",
"death.attack.sting.item": "%1$s was stung to death by %2$s using %3$s", "death.attack.sting.item": "%1$s was stung to death by %2$s using %3$s",
"death.attack.sting.player": "%1$s was stung to death by %2$s", "death.attack.sting.player": "%1$s was stung to death by %2$s",
"death.attack.sweetBerryBush": "%1$s was poked to death by a sweet berry bush", "death.attack.sweetBerryBush": "%1$s was poked to death by a sweet berry bush",
"death.attack.sweetBerryBush.player": "%1$s was poked to death by a sweet berry bush whilst trying to escape %2$s", "death.attack.sweetBerryBush.player": "%1$s was poked to death by a sweet berry bush while trying to escape %2$s",
"death.attack.thorns": "%1$s was killed trying to hurt %2$s", "death.attack.thorns": "%1$s was killed while trying to hurt %2$s",
"death.attack.thorns.item": "%1$s was killed by %3$s trying to hurt %2$s", "death.attack.thorns.item": "%1$s was killed by %3$s while trying to hurt %2$s",
"death.attack.thrown": "%1$s was pummeled by %2$s", "death.attack.thrown": "%1$s was pummeled by %2$s",
"death.attack.thrown.item": "%1$s was pummeled by %2$s using %3$s", "death.attack.thrown.item": "%1$s was pummeled by %2$s using %3$s",
"death.attack.trident": "%1$s was impaled by %2$s", "death.attack.trident": "%1$s was impaled by %2$s",
"death.attack.trident.item": "%1$s was impaled by %2$s with %3$s", "death.attack.trident.item": "%1$s was impaled by %2$s with %3$s",
"death.attack.wither": "%1$s withered away", "death.attack.wither": "%1$s withered away",
"death.attack.wither.player": "%1$s withered away whilst fighting %2$s", "death.attack.wither.player": "%1$s withered away while fighting %2$s",
"death.attack.witherSkull": "%1$s was shot by a skull from %2$s", "death.attack.witherSkull": "%1$s was shot by a skull from %2$s",
"death.attack.witherSkull.item": "%1$s was shot by a skull from %2$s using %3$s", "death.attack.witherSkull.item": "%1$s was shot by a skull from %2$s using %3$s",
"death.fell.accident.generic": "%1$s fell from a high place", "death.fell.accident.generic": "%1$s fell from a high place",
@ -2893,6 +2914,7 @@
"deathScreen.quit.confirm": "Are you sure you want to quit?", "deathScreen.quit.confirm": "Are you sure you want to quit?",
"deathScreen.respawn": "Respawn", "deathScreen.respawn": "Respawn",
"deathScreen.score": "Score", "deathScreen.score": "Score",
"deathScreen.score.value": "Score: %s",
"deathScreen.spectate": "Spectate World", "deathScreen.spectate": "Spectate World",
"deathScreen.title": "You Died!", "deathScreen.title": "You Died!",
"deathScreen.title.hardcore": "Game Over!", "deathScreen.title.hardcore": "Game Over!",
@ -3242,9 +3264,13 @@
"entity.not_summonable": "Can't summon entity of type %s", "entity.not_summonable": "Can't summon entity of type %s",
"event.minecraft.raid": "Raid", "event.minecraft.raid": "Raid",
"event.minecraft.raid.defeat": "Defeat", "event.minecraft.raid.defeat": "Defeat",
"event.minecraft.raid.defeat.full": "Raid - Defeat",
"event.minecraft.raid.raiders_remaining": "Raiders Remaining: %s", "event.minecraft.raid.raiders_remaining": "Raiders Remaining: %s",
"event.minecraft.raid.victory": "Victory", "event.minecraft.raid.victory": "Victory",
"event.minecraft.raid.victory.full": "Raid - Victory",
"filled_map.buried_treasure": "Buried Treasure Map", "filled_map.buried_treasure": "Buried Treasure Map",
"filled_map.explorer_jungle": "Jungle Explorer Map",
"filled_map.explorer_swamp": "Swamp Explorer Map",
"filled_map.id": "Id #%s", "filled_map.id": "Id #%s",
"filled_map.level": "(Level %s/%s)", "filled_map.level": "(Level %s/%s)",
"filled_map.locked": "Locked", "filled_map.locked": "Locked",
@ -3252,6 +3278,11 @@
"filled_map.monument": "Ocean Explorer Map", "filled_map.monument": "Ocean Explorer Map",
"filled_map.scale": "Scaling at 1:%s", "filled_map.scale": "Scaling at 1:%s",
"filled_map.unknown": "Unknown Map", "filled_map.unknown": "Unknown Map",
"filled_map.village_desert": "Desert Village Map",
"filled_map.village_plains": "Plains Village Map",
"filled_map.village_savanna": "Savanna Village Map",
"filled_map.village_snowy": "Snowy Village Map",
"filled_map.village_taiga": "Taiga Village Map",
"flat_world_preset.minecraft.bottomless_pit": "Bottomless Pit", "flat_world_preset.minecraft.bottomless_pit": "Bottomless Pit",
"flat_world_preset.minecraft.classic_flat": "Classic Flat", "flat_world_preset.minecraft.classic_flat": "Classic Flat",
"flat_world_preset.minecraft.desert": "Desert", "flat_world_preset.minecraft.desert": "Desert",
@ -3304,6 +3335,8 @@
"gamerule.doWardenSpawning": "Spawn Wardens", "gamerule.doWardenSpawning": "Spawn Wardens",
"gamerule.doWeatherCycle": "Update weather", "gamerule.doWeatherCycle": "Update weather",
"gamerule.drowningDamage": "Deal drowning damage", "gamerule.drowningDamage": "Deal drowning damage",
"gamerule.enderPearlsVanishOnDeath": "Thrown ender pearls vanish on death",
"gamerule.enderPearlsVanishOnDeath.description": "Whether ender pearls thrown by a player vanish when that player dies.",
"gamerule.fallDamage": "Deal fall damage", "gamerule.fallDamage": "Deal fall damage",
"gamerule.fireDamage": "Deal fire damage", "gamerule.fireDamage": "Deal fire damage",
"gamerule.forgiveDeadPlayers": "Forgive dead players", "gamerule.forgiveDeadPlayers": "Forgive dead players",
@ -3350,41 +3383,71 @@
"generator.minecraft.single_biome_surface": "Single Biome", "generator.minecraft.single_biome_surface": "Single Biome",
"generator.single_biome_caves": "Caves", "generator.single_biome_caves": "Caves",
"generator.single_biome_floating_islands": "Floating Islands", "generator.single_biome_floating_islands": "Floating Islands",
"gui.abuseReport.comments": "Comments",
"gui.abuseReport.describe": "Sharing details will help us make a well-informed decision.",
"gui.abuseReport.discard.content": "If you leave, you'll lose this report and your comments.\nAre you sure you want to leave?",
"gui.abuseReport.discard.discard": "Leave and Discard Report",
"gui.abuseReport.discard.draft": "Save as Draft",
"gui.abuseReport.discard.return": "Continue Editing",
"gui.abuseReport.discard.title": "Discard report and comments?",
"gui.abuseReport.draft.content": "Would you like to continue editing the existing report or discard it and create a new one?",
"gui.abuseReport.draft.discard": "Discard",
"gui.abuseReport.draft.edit": "Continue Editing",
"gui.abuseReport.draft.quittotitle.content": "Would you like to continue editing it or discard it?",
"gui.abuseReport.draft.quittotitle.title": "You have a draft chat report that will be lost if you quit",
"gui.abuseReport.draft.title": "Edit draft chat report?",
"gui.abuseReport.error.title": "Problem sending your report", "gui.abuseReport.error.title": "Problem sending your report",
"gui.abuseReport.message": "Where did you observe the bad behavior?\nThis will help us in researching your case.",
"gui.abuseReport.more_comments": "Please describe what happened:",
"gui.abuseReport.name.reporting": "You are reporting \"%s\".",
"gui.abuseReport.name.title": "Report Player Name",
"gui.abuseReport.observed_what": "Why are you reporting this?",
"gui.abuseReport.read_info": "Learn About Reporting",
"gui.abuseReport.reason.alcohol_tobacco_drugs": "Drugs or alcohol", "gui.abuseReport.reason.alcohol_tobacco_drugs": "Drugs or alcohol",
"gui.abuseReport.reason.alcohol_tobacco_drugs.description": "Someone is encouraging others to partake in illegal drug related activities or encouraging underage drinking.", "gui.abuseReport.reason.alcohol_tobacco_drugs.description": "Someone is encouraging others to partake in illegal drug related activities or encouraging underage drinking.",
"gui.abuseReport.reason.child_sexual_exploitation_or_abuse": "Child sexual exploitation or abuse", "gui.abuseReport.reason.child_sexual_exploitation_or_abuse": "Child sexual exploitation or abuse",
"gui.abuseReport.reason.child_sexual_exploitation_or_abuse.description": "Someone is talking about or otherwise promoting indecent behavior involving children.", "gui.abuseReport.reason.child_sexual_exploitation_or_abuse.description": "Someone is talking about or otherwise promoting indecent behavior involving children.",
"gui.abuseReport.reason.defamation_impersonation_false_information": "Defamation, impersonation, or false information", "gui.abuseReport.reason.defamation_impersonation_false_information": "Defamation",
"gui.abuseReport.reason.defamation_impersonation_false_information.description": "Someone is damaging someone else's reputation, pretending to be someone they're not, or sharing false information with the aim to exploit or mislead others.", "gui.abuseReport.reason.defamation_impersonation_false_information.description": "Someone is damaging your or someone else's reputation, for example sharing false information with the aim to exploit or mislead others.",
"gui.abuseReport.reason.description": "Description:", "gui.abuseReport.reason.description": "Description:",
"gui.abuseReport.reason.false_reporting": "False Reporting", "gui.abuseReport.reason.false_reporting": "False Reporting",
"gui.abuseReport.reason.generic": "I want to report them",
"gui.abuseReport.reason.generic.description": "I'm annoyed with them / they have done something I do not like.",
"gui.abuseReport.reason.harassment_or_bullying": "Harassment or bullying", "gui.abuseReport.reason.harassment_or_bullying": "Harassment or bullying",
"gui.abuseReport.reason.harassment_or_bullying.description": "Someone is shaming, attacking, or bullying you or someone else. This includes when someone is repeatedly trying to contact you or someone else without consent or posting private personal information about you or someone else without consent (\"doxing\").", "gui.abuseReport.reason.harassment_or_bullying.description": "Someone is shaming, attacking, or bullying you or someone else. This includes when someone is repeatedly trying to contact you or someone else without consent or posting private personal information about you or someone else without consent (\"doxing\").",
"gui.abuseReport.reason.hate_speech": "Hate speech", "gui.abuseReport.reason.hate_speech": "Hate speech",
"gui.abuseReport.reason.hate_speech.description": "Someone is attacking you or another player based on characteristics of their identity, like religion, race, or sexuality.", "gui.abuseReport.reason.hate_speech.description": "Someone is attacking you or another player based on characteristics of their identity, like religion, race, or sexuality.",
"gui.abuseReport.reason.imminent_harm": "Imminent harm - Threat to harm others", "gui.abuseReport.reason.imminent_harm": "Threat of harm to others",
"gui.abuseReport.reason.imminent_harm.description": "Someone is threatening to harm you or someone else in real life.", "gui.abuseReport.reason.imminent_harm.description": "Someone is threatening to harm you or someone else in real life.",
"gui.abuseReport.reason.narration": "%s: %s", "gui.abuseReport.reason.narration": "%s: %s",
"gui.abuseReport.reason.non_consensual_intimate_imagery": "Non-consensual intimate imagery", "gui.abuseReport.reason.non_consensual_intimate_imagery": "Non-consensual intimate imagery",
"gui.abuseReport.reason.non_consensual_intimate_imagery.description": "Someone is talking about, sharing, or otherwise promoting private and intimate images.", "gui.abuseReport.reason.non_consensual_intimate_imagery.description": "Someone is talking about, sharing, or otherwise promoting private and intimate images.",
"gui.abuseReport.reason.self_harm_or_suicide": "Imminent harm - Self-harm or suicide", "gui.abuseReport.reason.self_harm_or_suicide": "Self-harm or suicide",
"gui.abuseReport.reason.self_harm_or_suicide.description": "Someone is threatening to harm themselves in real life or talking about harming themselves in real life.", "gui.abuseReport.reason.self_harm_or_suicide.description": "Someone is threatening to harm themselves in real life or talking about harming themselves in real life.",
"gui.abuseReport.reason.terrorism_or_violent_extremism": "Terrorism or violent extremism", "gui.abuseReport.reason.terrorism_or_violent_extremism": "Terrorism or violent extremism",
"gui.abuseReport.reason.terrorism_or_violent_extremism.description": "Someone is talking about, promoting, or threatening to commit acts of terrorism or violent extremism for political, religious, ideological, or other reasons.", "gui.abuseReport.reason.terrorism_or_violent_extremism.description": "Someone is talking about, promoting, or threatening to commit acts of terrorism or violent extremism for political, religious, ideological, or other reasons.",
"gui.abuseReport.reason.title": "Select Report Category", "gui.abuseReport.reason.title": "Select Report Category",
"gui.abuseReport.report_sent_msg": "We\u2019ve successfully received your report. Thank you!\n\nOur team will review it as soon as possible.",
"gui.abuseReport.select_reason": "Select Report Category",
"gui.abuseReport.send": "Send Report",
"gui.abuseReport.send.comment_too_long": "Please shorten the comment",
"gui.abuseReport.send.error_message": "An error was returned while sending your report:\n'%s'", "gui.abuseReport.send.error_message": "An error was returned while sending your report:\n'%s'",
"gui.abuseReport.send.generic_error": "Encountered an unexpected error while sending your report.", "gui.abuseReport.send.generic_error": "Encountered an unexpected error while sending your report.",
"gui.abuseReport.send.http_error": "An unexpected HTTP error occurred while sending your report.", "gui.abuseReport.send.http_error": "An unexpected HTTP error occurred while sending your report.",
"gui.abuseReport.send.json_error": "Encountered malformed payload while sending your report.", "gui.abuseReport.send.json_error": "Encountered malformed payload while sending your report.",
"gui.abuseReport.send.no_reason": "Please select a report category",
"gui.abuseReport.send.service_unavailable": "Unable to reach the Abuse Reporting service. Please make sure you are connected to the internet and try again.", "gui.abuseReport.send.service_unavailable": "Unable to reach the Abuse Reporting service. Please make sure you are connected to the internet and try again.",
"gui.abuseReport.sending.title": "Sending your report...", "gui.abuseReport.sending.title": "Sending your report...",
"gui.abuseReport.sent.title": "Report sent", "gui.abuseReport.sent.title": "Report sent",
"gui.abuseReport.skin.title": "Report Player Skin",
"gui.abuseReport.title": "Report Player",
"gui.abuseReport.type.chat": "Chat Messages",
"gui.abuseReport.type.name": "Player Name",
"gui.abuseReport.type.skin": "Player Skin",
"gui.acknowledge": "Acknowledge", "gui.acknowledge": "Acknowledge",
"gui.advancements": "Advancements", "gui.advancements": "Advancements",
"gui.all": "All", "gui.all": "All",
"gui.back": "Back", "gui.back": "Back",
"gui.copy_link_to_clipboard": "Copy Link to Clipboard",
"gui.banned.description": "%s\n\n%s\n\nLearn more at the following link: %s", "gui.banned.description": "%s\n\n%s\n\nLearn more at the following link: %s",
"gui.banned.description.permanent": "Your account is permanently banned, which means you can\u2019t play online or join Realms.", "gui.banned.description.permanent": "Your account is permanently banned, which means you can\u2019t play online or join Realms.",
"gui.banned.description.reason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified it as %s, which goes against the Minecraft Community Standards.", "gui.banned.description.reason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified it as %s, which goes against the Minecraft Community Standards.",
@ -3393,6 +3456,8 @@
"gui.banned.description.temporary": "%s Until then, you can\u2019t play online or join Realms.", "gui.banned.description.temporary": "%s Until then, you can\u2019t play online or join Realms.",
"gui.banned.description.temporary.duration": "Your account is temporarily suspended and will be reactivated in %s.", "gui.banned.description.temporary.duration": "Your account is temporarily suspended and will be reactivated in %s.",
"gui.banned.description.unknownreason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified that it goes against the Minecraft Community Standards.", "gui.banned.description.unknownreason": "We recently received a report for bad behavior by your account. Our moderators have now reviewed your case and identified that it goes against the Minecraft Community Standards.",
"gui.banned.name.description": "Your current name - \"%s\" - violates our Community Standards. You can play singleplayer, but will need to change your name to play online.\n\nLearn more or submit a case review at the following link: %s",
"gui.banned.name.title": "Name Not Allowed in Multiplayer",
"gui.banned.reason.defamation_impersonation_false_information": "Impersonation or sharing information to exploit or mislead others", "gui.banned.reason.defamation_impersonation_false_information": "Impersonation or sharing information to exploit or mislead others",
"gui.banned.reason.drugs": "References to illegal drugs", "gui.banned.reason.drugs": "References to illegal drugs",
"gui.banned.reason.extreme_violence_or_gore": "Depictions of real-life excessive violence or gore", "gui.banned.reason.extreme_violence_or_gore": "Depictions of real-life excessive violence or gore",
@ -3406,6 +3471,8 @@
"gui.banned.reason.nudity_or_pornography": "Displaying lewd or pornographic material", "gui.banned.reason.nudity_or_pornography": "Displaying lewd or pornographic material",
"gui.banned.reason.sexually_inappropriate": "Topics or content of a sexual nature", "gui.banned.reason.sexually_inappropriate": "Topics or content of a sexual nature",
"gui.banned.reason.spam_or_advertising": "Spam or advertising", "gui.banned.reason.spam_or_advertising": "Spam or advertising",
"gui.banned.skin.description": "Your current skin violates our Community Standards. You can still play with a default skin, or select a new one.\n\nLearn more or submit a case review at the following link: %s",
"gui.banned.skin.title": "Skin Not Allowed",
"gui.banned.title.permanent": "Account permanently banned", "gui.banned.title.permanent": "Account permanently banned",
"gui.banned.title.temporary": "Account temporarily suspended", "gui.banned.title.temporary": "Account temporarily suspended",
"gui.cancel": "Cancel", "gui.cancel": "Cancel",
@ -3434,7 +3501,7 @@
"gui.chatReport.send.no_reason": "Please select a report category", "gui.chatReport.send.no_reason": "Please select a report category",
"gui.chatReport.send.no_reported_messages": "Please select at least one chat message to report", "gui.chatReport.send.no_reported_messages": "Please select at least one chat message to report",
"gui.chatReport.send.too_many_messages": "Trying to include too many messages in the report", "gui.chatReport.send.too_many_messages": "Trying to include too many messages in the report",
"gui.chatReport.title": "Report Player", "gui.chatReport.title": "Report Player Chat",
"gui.chatSelection.context": "Messages surrounding this selection will be included to provide additional context", "gui.chatSelection.context": "Messages surrounding this selection will be included to provide additional context",
"gui.chatSelection.fold": "%s message(s) hidden", "gui.chatSelection.fold": "%s message(s) hidden",
"gui.chatSelection.heading": "%s %s", "gui.chatSelection.heading": "%s %s",
@ -3443,6 +3510,7 @@
"gui.chatSelection.selected": "%s/%s message(s) selected", "gui.chatSelection.selected": "%s/%s message(s) selected",
"gui.chatSelection.title": "Select Chat Messages to Report", "gui.chatSelection.title": "Select Chat Messages to Report",
"gui.continue": "Continue", "gui.continue": "Continue",
"gui.copy_link_to_clipboard": "Copy Link to Clipboard",
"gui.days": "%s day(s)", "gui.days": "%s day(s)",
"gui.done": "Done", "gui.done": "Done",
"gui.down": "Down", "gui.down": "Down",
@ -3459,6 +3527,7 @@
"gui.ok": "Ok", "gui.ok": "Ok",
"gui.proceed": "Proceed", "gui.proceed": "Proceed",
"gui.recipebook.moreRecipes": "Right Click for More", "gui.recipebook.moreRecipes": "Right Click for More",
"gui.recipebook.page": "%s/%s",
"gui.recipebook.search_hint": "Search...", "gui.recipebook.search_hint": "Search...",
"gui.recipebook.toggleRecipes.all": "Showing All", "gui.recipebook.toggleRecipes.all": "Showing All",
"gui.recipebook.toggleRecipes.blastable": "Showing Blastable", "gui.recipebook.toggleRecipes.blastable": "Showing Blastable",
@ -3591,6 +3660,7 @@
"item.minecraft.clay_ball": "Clay Ball", "item.minecraft.clay_ball": "Clay Ball",
"item.minecraft.clock": "Clock", "item.minecraft.clock": "Clock",
"item.minecraft.coal": "Coal", "item.minecraft.coal": "Coal",
"item.minecraft.coast_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.cocoa_beans": "Cocoa Beans", "item.minecraft.cocoa_beans": "Cocoa Beans",
"item.minecraft.cod": "Raw Cod", "item.minecraft.cod": "Raw Cod",
"item.minecraft.cod_bucket": "Bucket of Cod", "item.minecraft.cod_bucket": "Bucket of Cod",
@ -3639,6 +3709,7 @@
"item.minecraft.dragon_breath": "Dragon's Breath", "item.minecraft.dragon_breath": "Dragon's Breath",
"item.minecraft.dried_kelp": "Dried Kelp", "item.minecraft.dried_kelp": "Dried Kelp",
"item.minecraft.drowned_spawn_egg": "Drowned Spawn Egg", "item.minecraft.drowned_spawn_egg": "Drowned Spawn Egg",
"item.minecraft.dune_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.echo_shard": "Echo Shard", "item.minecraft.echo_shard": "Echo Shard",
"item.minecraft.egg": "Egg", "item.minecraft.egg": "Egg",
"item.minecraft.elder_guardian_spawn_egg": "Elder Guardian Spawn Egg", "item.minecraft.elder_guardian_spawn_egg": "Elder Guardian Spawn Egg",
@ -3656,6 +3727,7 @@
"item.minecraft.experience_bottle": "Bottle o' Enchanting", "item.minecraft.experience_bottle": "Bottle o' Enchanting",
"item.minecraft.explorer_pottery_shard": "Explorer Pottery Shard", "item.minecraft.explorer_pottery_shard": "Explorer Pottery Shard",
"item.minecraft.explorer_pottery_sherd": "Explorer Pottery Sherd", "item.minecraft.explorer_pottery_sherd": "Explorer Pottery Sherd",
"item.minecraft.eye_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.feather": "Feather", "item.minecraft.feather": "Feather",
"item.minecraft.fermented_spider_eye": "Fermented Spider Eye", "item.minecraft.fermented_spider_eye": "Fermented Spider Eye",
"item.minecraft.filled_map": "Map", "item.minecraft.filled_map": "Map",
@ -3741,6 +3813,7 @@
"item.minecraft.honeycomb": "Honeycomb", "item.minecraft.honeycomb": "Honeycomb",
"item.minecraft.hopper_minecart": "Minecart with Hopper", "item.minecraft.hopper_minecart": "Minecart with Hopper",
"item.minecraft.horse_spawn_egg": "Horse Spawn Egg", "item.minecraft.horse_spawn_egg": "Horse Spawn Egg",
"item.minecraft.host_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.howl_pottery_shard": "Howl Pottery Shard", "item.minecraft.howl_pottery_shard": "Howl Pottery Shard",
"item.minecraft.howl_pottery_sherd": "Howl Pottery Sherd", "item.minecraft.howl_pottery_sherd": "Howl Pottery Sherd",
"item.minecraft.husk_spawn_egg": "Husk Spawn Egg", "item.minecraft.husk_spawn_egg": "Husk Spawn Egg",
@ -3867,6 +3940,7 @@
"item.minecraft.netherite_scrap": "Netherite Scrap", "item.minecraft.netherite_scrap": "Netherite Scrap",
"item.minecraft.netherite_shovel": "Netherite Shovel", "item.minecraft.netherite_shovel": "Netherite Shovel",
"item.minecraft.netherite_sword": "Netherite Sword", "item.minecraft.netherite_sword": "Netherite Sword",
"item.minecraft.netherite_upgrade_smithing_template": "Smithing Template",
"item.minecraft.oak_boat": "Oak Boat", "item.minecraft.oak_boat": "Oak Boat",
"item.minecraft.oak_chest_boat": "Oak Boat with Chest", "item.minecraft.oak_chest_boat": "Oak Boat with Chest",
"item.minecraft.ocelot_spawn_egg": "Ocelot Spawn Egg", "item.minecraft.ocelot_spawn_egg": "Ocelot Spawn Egg",
@ -3937,6 +4011,7 @@
"item.minecraft.rabbit_hide": "Rabbit Hide", "item.minecraft.rabbit_hide": "Rabbit Hide",
"item.minecraft.rabbit_spawn_egg": "Rabbit Spawn Egg", "item.minecraft.rabbit_spawn_egg": "Rabbit Spawn Egg",
"item.minecraft.rabbit_stew": "Rabbit Stew", "item.minecraft.rabbit_stew": "Rabbit Stew",
"item.minecraft.raiser_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.ravager_spawn_egg": "Ravager Spawn Egg", "item.minecraft.ravager_spawn_egg": "Ravager Spawn Egg",
"item.minecraft.raw_copper": "Raw Copper", "item.minecraft.raw_copper": "Raw Copper",
"item.minecraft.raw_gold": "Raw Gold", "item.minecraft.raw_gold": "Raw Gold",
@ -3944,12 +4019,15 @@
"item.minecraft.recovery_compass": "Recovery Compass", "item.minecraft.recovery_compass": "Recovery Compass",
"item.minecraft.red_dye": "Red Dye", "item.minecraft.red_dye": "Red Dye",
"item.minecraft.redstone": "Redstone Dust", "item.minecraft.redstone": "Redstone Dust",
"item.minecraft.rib_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.rotten_flesh": "Rotten Flesh", "item.minecraft.rotten_flesh": "Rotten Flesh",
"item.minecraft.saddle": "Saddle", "item.minecraft.saddle": "Saddle",
"item.minecraft.salmon": "Raw Salmon", "item.minecraft.salmon": "Raw Salmon",
"item.minecraft.salmon_bucket": "Bucket of Salmon", "item.minecraft.salmon_bucket": "Bucket of Salmon",
"item.minecraft.salmon_spawn_egg": "Salmon Spawn Egg", "item.minecraft.salmon_spawn_egg": "Salmon Spawn Egg",
"item.minecraft.scute": "Scute", "item.minecraft.scute": "Scute",
"item.minecraft.sentry_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.shaper_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.sheaf_pottery_shard": "Sheaf Pottery Shard", "item.minecraft.sheaf_pottery_shard": "Sheaf Pottery Shard",
"item.minecraft.sheaf_pottery_sherd": "Sheaf Pottery Sherd", "item.minecraft.sheaf_pottery_sherd": "Sheaf Pottery Sherd",
"item.minecraft.shears": "Shears", "item.minecraft.shears": "Shears",
@ -3976,6 +4054,7 @@
"item.minecraft.shulker_shell": "Shulker Shell", "item.minecraft.shulker_shell": "Shulker Shell",
"item.minecraft.shulker_spawn_egg": "Shulker Spawn Egg", "item.minecraft.shulker_spawn_egg": "Shulker Spawn Egg",
"item.minecraft.sign": "Sign", "item.minecraft.sign": "Sign",
"item.minecraft.silence_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.silverfish_spawn_egg": "Silverfish Spawn Egg", "item.minecraft.silverfish_spawn_egg": "Silverfish Spawn Egg",
"item.minecraft.skeleton_horse_spawn_egg": "Skeleton Horse Spawn Egg", "item.minecraft.skeleton_horse_spawn_egg": "Skeleton Horse Spawn Egg",
"item.minecraft.skeleton_spawn_egg": "Skeleton Spawn Egg", "item.minecraft.skeleton_spawn_egg": "Skeleton Spawn Egg",
@ -4000,11 +4079,13 @@
"item.minecraft.sniffer_spawn_egg": "Sniffer Spawn Egg", "item.minecraft.sniffer_spawn_egg": "Sniffer Spawn Egg",
"item.minecraft.snort_pottery_shard": "Snort Pottery Shard", "item.minecraft.snort_pottery_shard": "Snort Pottery Shard",
"item.minecraft.snort_pottery_sherd": "Snort Pottery Sherd", "item.minecraft.snort_pottery_sherd": "Snort Pottery Sherd",
"item.minecraft.snout_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.snow_golem_spawn_egg": "Snow Golem Spawn Egg", "item.minecraft.snow_golem_spawn_egg": "Snow Golem Spawn Egg",
"item.minecraft.snowball": "Snowball", "item.minecraft.snowball": "Snowball",
"item.minecraft.spectral_arrow": "Spectral Arrow", "item.minecraft.spectral_arrow": "Spectral Arrow",
"item.minecraft.spider_eye": "Spider Eye", "item.minecraft.spider_eye": "Spider Eye",
"item.minecraft.spider_spawn_egg": "Spider Spawn Egg", "item.minecraft.spider_spawn_egg": "Spider Spawn Egg",
"item.minecraft.spire_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.splash_potion": "Splash Potion", "item.minecraft.splash_potion": "Splash Potion",
"item.minecraft.splash_potion.effect.awkward": "Awkward Splash Potion", "item.minecraft.splash_potion.effect.awkward": "Awkward Splash Potion",
"item.minecraft.splash_potion.effect.empty": "Splash Uncraftable Potion", "item.minecraft.splash_potion.effect.empty": "Splash Uncraftable Potion",
@ -4046,6 +4127,7 @@
"item.minecraft.sweet_berries": "Sweet Berries", "item.minecraft.sweet_berries": "Sweet Berries",
"item.minecraft.tadpole_bucket": "Bucket of Tadpole", "item.minecraft.tadpole_bucket": "Bucket of Tadpole",
"item.minecraft.tadpole_spawn_egg": "Tadpole Spawn Egg", "item.minecraft.tadpole_spawn_egg": "Tadpole Spawn Egg",
"item.minecraft.tide_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.tipped_arrow": "Tipped Arrow", "item.minecraft.tipped_arrow": "Tipped Arrow",
"item.minecraft.tipped_arrow.effect.awkward": "Tipped Arrow", "item.minecraft.tipped_arrow.effect.awkward": "Tipped Arrow",
"item.minecraft.tipped_arrow.effect.empty": "Uncraftable Tipped Arrow", "item.minecraft.tipped_arrow.effect.empty": "Uncraftable Tipped Arrow",
@ -4079,16 +4161,20 @@
"item.minecraft.tropical_fish_spawn_egg": "Tropical Fish Spawn Egg", "item.minecraft.tropical_fish_spawn_egg": "Tropical Fish Spawn Egg",
"item.minecraft.turtle_helmet": "Turtle Shell", "item.minecraft.turtle_helmet": "Turtle Shell",
"item.minecraft.turtle_spawn_egg": "Turtle Spawn Egg", "item.minecraft.turtle_spawn_egg": "Turtle Spawn Egg",
"item.minecraft.vex_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.vex_spawn_egg": "Vex Spawn Egg", "item.minecraft.vex_spawn_egg": "Vex Spawn Egg",
"item.minecraft.villager_spawn_egg": "Villager Spawn Egg", "item.minecraft.villager_spawn_egg": "Villager Spawn Egg",
"item.minecraft.vindicator_spawn_egg": "Vindicator Spawn Egg", "item.minecraft.vindicator_spawn_egg": "Vindicator Spawn Egg",
"item.minecraft.wandering_trader_spawn_egg": "Wandering Trader Spawn Egg", "item.minecraft.wandering_trader_spawn_egg": "Wandering Trader Spawn Egg",
"item.minecraft.ward_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.warden_spawn_egg": "Warden Spawn Egg", "item.minecraft.warden_spawn_egg": "Warden Spawn Egg",
"item.minecraft.warped_fungus_on_a_stick": "Warped Fungus on a Stick", "item.minecraft.warped_fungus_on_a_stick": "Warped Fungus on a Stick",
"item.minecraft.water_bucket": "Water Bucket", "item.minecraft.water_bucket": "Water Bucket",
"item.minecraft.wayfinder_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.wheat": "Wheat", "item.minecraft.wheat": "Wheat",
"item.minecraft.wheat_seeds": "Wheat Seeds", "item.minecraft.wheat_seeds": "Wheat Seeds",
"item.minecraft.white_dye": "White Dye", "item.minecraft.white_dye": "White Dye",
"item.minecraft.wild_armor_trim_smithing_template": "Smithing Template",
"item.minecraft.witch_spawn_egg": "Witch Spawn Egg", "item.minecraft.witch_spawn_egg": "Witch Spawn Egg",
"item.minecraft.wither_skeleton_spawn_egg": "Wither Skeleton Spawn Egg", "item.minecraft.wither_skeleton_spawn_egg": "Wither Skeleton Spawn Egg",
"item.minecraft.wither_spawn_egg": "Wither Spawn Egg", "item.minecraft.wither_spawn_egg": "Wither Spawn Egg",
@ -4283,6 +4369,7 @@
"lanServer.start": "Start LAN World", "lanServer.start": "Start LAN World",
"lanServer.title": "LAN World", "lanServer.title": "LAN World",
"lectern.take_book": "Take Book", "lectern.take_book": "Take Book",
"loading.progress": "%s%%",
"mco.account.privacy.info": "Read more about Mojang and privacy laws", "mco.account.privacy.info": "Read more about Mojang and privacy laws",
"mco.account.privacyinfo": "Mojang implements certain procedures to help protect children and their privacy including complying with the Children\u2019s Online Privacy Protection Act (COPPA) and General Data Protection Regulation (GDPR).\n\nYou may need to obtain parental consent before accessing your Realms account.\n\nIf you have an older Minecraft account (you log in with your username), you need to migrate the account to a Mojang account in order to access Realms.", "mco.account.privacyinfo": "Mojang implements certain procedures to help protect children and their privacy including complying with the Children\u2019s Online Privacy Protection Act (COPPA) and General Data Protection Regulation (GDPR).\n\nYou may need to obtain parental consent before accessing your Realms account.\n\nIf you have an older Minecraft account (you log in with your username), you need to migrate the account to a Mojang account in order to access Realms.",
"mco.account.update": "Update account", "mco.account.update": "Update account",
@ -4395,9 +4482,6 @@
"mco.configure.world.spawnNPCs": "Spawn NPCs", "mco.configure.world.spawnNPCs": "Spawn NPCs",
"mco.configure.world.spawnProtection": "Spawn protection", "mco.configure.world.spawnProtection": "Spawn protection",
"mco.configure.world.status": "Status", "mco.configure.world.status": "Status",
"mco.configure.world.subscription.remaining.months.days": "%1$s month(s), %2$s day(s)",
"mco.configure.world.subscription.remaining.months": "%1$s month(s)",
"mco.configure.world.subscription.remaining.days": "%1$s day(s)",
"mco.configure.world.subscription.day": "day", "mco.configure.world.subscription.day": "day",
"mco.configure.world.subscription.days": "days", "mco.configure.world.subscription.days": "days",
"mco.configure.world.subscription.expired": "Expired", "mco.configure.world.subscription.expired": "Expired",
@ -4407,6 +4491,9 @@
"mco.configure.world.subscription.months": "months", "mco.configure.world.subscription.months": "months",
"mco.configure.world.subscription.recurring.daysleft": "Renewed automatically in", "mco.configure.world.subscription.recurring.daysleft": "Renewed automatically in",
"mco.configure.world.subscription.recurring.info": "Changes made to your Realms subscription such as stacking time or turning off recurring billing will not be reflected until your next bill date.", "mco.configure.world.subscription.recurring.info": "Changes made to your Realms subscription such as stacking time or turning off recurring billing will not be reflected until your next bill date.",
"mco.configure.world.subscription.remaining.days": "%1$s day(s)",
"mco.configure.world.subscription.remaining.months": "%1$s month(s)",
"mco.configure.world.subscription.remaining.months.days": "%1$s month(s), %2$s day(s)",
"mco.configure.world.subscription.start": "Start date", "mco.configure.world.subscription.start": "Start date",
"mco.configure.world.subscription.timeleft": "Time left", "mco.configure.world.subscription.timeleft": "Time left",
"mco.configure.world.subscription.title": "Your subscription", "mco.configure.world.subscription.title": "Your subscription",
@ -4414,8 +4501,8 @@
"mco.configure.world.switch.slot": "Create world", "mco.configure.world.switch.slot": "Create world",
"mco.configure.world.switch.slot.subtitle": "This world is empty, choose how to create your world", "mco.configure.world.switch.slot.subtitle": "This world is empty, choose how to create your world",
"mco.configure.world.title": "Configure realm:", "mco.configure.world.title": "Configure realm:",
"mco.configure.world.uninvite.question": "Are you sure that you want to uninvite",
"mco.configure.world.uninvite.player": "Are you sure that you want to uninvite '%s'?", "mco.configure.world.uninvite.player": "Are you sure that you want to uninvite '%s'?",
"mco.configure.world.uninvite.question": "Are you sure that you want to uninvite",
"mco.configure.worlds.title": "Worlds", "mco.configure.worlds.title": "Worlds",
"mco.connect.authorizing": "Logging in...", "mco.connect.authorizing": "Logging in...",
"mco.connect.connecting": "Connecting to the realm...", "mco.connect.connecting": "Connecting to the realm...",
@ -4438,6 +4525,7 @@
"mco.download.preparing": "Preparing download", "mco.download.preparing": "Preparing download",
"mco.download.resourcePack.fail": "Failed to download resource pack!", "mco.download.resourcePack.fail": "Failed to download resource pack!",
"mco.download.speed": "(%s/s)", "mco.download.speed": "(%s/s)",
"mco.download.speed.narration": "%s/s",
"mco.download.title": "Downloading latest world", "mco.download.title": "Downloading latest world",
"mco.error.invalid.session.message": "Please try restarting Minecraft", "mco.error.invalid.session.message": "Please try restarting Minecraft",
"mco.error.invalid.session.title": "Invalid session", "mco.error.invalid.session.title": "Invalid session",
@ -4452,8 +4540,12 @@
"mco.errorMessage.6009": "Invalid Realm description", "mco.errorMessage.6009": "Invalid Realm description",
"mco.errorMessage.connectionFailure": "An error occurred, please try again later.", "mco.errorMessage.connectionFailure": "An error occurred, please try again later.",
"mco.errorMessage.generic": "An error occurred: ", "mco.errorMessage.generic": "An error occurred: ",
"mco.errorMessage.noDetails": "No error details provided",
"mco.errorMessage.realmsService": "An error occurred (%s):", "mco.errorMessage.realmsService": "An error occurred (%s):",
"mco.errorMessage.realmsService.connectivity": "Could not connect to Realms: %s",
"mco.errorMessage.realmsService.realmsError": "Realms (%s):", "mco.errorMessage.realmsService.realmsError": "Realms (%s):",
"mco.errorMessage.realmsService.unknownCompatibility": "Could not check compatible version, got response: %s",
"mco.errorMessage.retry": "Retry operation",
"mco.errorMessage.serviceBusy": "Realms is busy at the moment.\nPlease try connecting to your Realm again in a couple of minutes.", "mco.errorMessage.serviceBusy": "Realms is busy at the moment.\nPlease try connecting to your Realm again in a couple of minutes.",
"mco.gui.button": "Button", "mco.gui.button": "Button",
"mco.gui.ok": "Ok", "mco.gui.ok": "Ok",
@ -4500,6 +4592,7 @@
"mco.selectServer.configure": "Configure", "mco.selectServer.configure": "Configure",
"mco.selectServer.configureRealm": "Configure realm", "mco.selectServer.configureRealm": "Configure realm",
"mco.selectServer.create": "Create realm", "mco.selectServer.create": "Create realm",
"mco.selectServer.create.subtitle": "Select what world to put on your new realm",
"mco.selectServer.expired": "Expired realm", "mco.selectServer.expired": "Expired realm",
"mco.selectServer.expiredList": "Your subscription has expired", "mco.selectServer.expiredList": "Your subscription has expired",
"mco.selectServer.expiredRenew": "Renew", "mco.selectServer.expiredRenew": "Renew",
@ -4509,14 +4602,16 @@
"mco.selectServer.expires.days": "Expires in %s days", "mco.selectServer.expires.days": "Expires in %s days",
"mco.selectServer.expires.soon": "Expires soon", "mco.selectServer.expires.soon": "Expires soon",
"mco.selectServer.leave": "Leave realm", "mco.selectServer.leave": "Leave realm",
"mco.selectServer.loading": "Loading Realms List",
"mco.selectServer.mapOnlySupportedForVersion": "This map is unsupported in %s", "mco.selectServer.mapOnlySupportedForVersion": "This map is unsupported in %s",
"mco.selectServer.minigame": "Minigame:", "mco.selectServer.minigame": "Minigame:",
"mco.selectServer.minigameNotSupportedInVersion": "Can't play this minigame in %s", "mco.selectServer.minigameNotSupportedInVersion": "Can't play this minigame in %s",
"mco.selectServer.noRealms": "You don't seem to have a Realm. Add a Realm to play together with your friends.",
"mco.selectServer.note": "Note:", "mco.selectServer.note": "Note:",
"mco.selectServer.open": "Open realm", "mco.selectServer.open": "Open realm",
"mco.selectServer.openserver": "Open realm", "mco.selectServer.openserver": "Open realm",
"mco.selectServer.play": "Play", "mco.selectServer.play": "Play",
"mco.selectServer.popup": "Realms is a safe, simple way to enjoy an online Minecraft world with up to ten friends at a time. It supports loads of minigames and plenty of custom worlds! Only the owner of the realm needs to pay.", "mco.selectServer.popup": "Realms is a safe, simple way to enjoy an online Minecraft world with up to ten friends at a time. It supports loads of minigames and plenty of custom worlds! Only the owner of the realm needs to pay.",
"mco.selectServer.purchase": "Add Realm", "mco.selectServer.purchase": "Add Realm",
"mco.selectServer.trial": "Get a trial!", "mco.selectServer.trial": "Get a trial!",
"mco.selectServer.uninitialized": "Click to start your new realm!", "mco.selectServer.uninitialized": "Click to start your new realm!",
@ -4539,16 +4634,22 @@
"mco.terms.sentence.1": "I agree to the Minecraft Realms", "mco.terms.sentence.1": "I agree to the Minecraft Realms",
"mco.terms.sentence.2": "Terms of Service", "mco.terms.sentence.2": "Terms of Service",
"mco.terms.title": "Realms Terms of Service", "mco.terms.title": "Realms Terms of Service",
"mco.time.daysAgo": "%1$s day(s) ago",
"mco.time.hoursAgo": "%1$s hour(s) ago",
"mco.time.minutesAgo": "%1$s minute(s) ago",
"mco.time.now": "right now",
"mco.time.secondsAgo": "%1$s second(s) ago",
"mco.trial.message.line1": "Want to get your own realm?", "mco.trial.message.line1": "Want to get your own realm?",
"mco.trial.message.line2": "Click here for more info!", "mco.trial.message.line2": "Click here for more info!",
"mco.upload.button.name": "Upload", "mco.upload.button.name": "Upload",
"mco.upload.cancelled": "Upload cancelled", "mco.upload.cancelled": "Upload cancelled",
"mco.upload.close.failure": "Could not close your realm, please try again later", "mco.upload.close.failure": "Could not close your realm, please try again later",
"mco.upload.done": "Upload done", "mco.upload.done": "Upload done",
"mco.upload.entry.id": "%1$s (%2$s)",
"mco.upload.entry.cheats": "%1$s, %2$s", "mco.upload.entry.cheats": "%1$s, %2$s",
"mco.upload.entry.id": "%1$s (%2$s)",
"mco.upload.failed": "Upload failed! (%s)", "mco.upload.failed": "Upload failed! (%s)",
"mco.upload.hardcore": "Hardcore worlds can't be uploaded!", "mco.upload.hardcore": "Hardcore worlds can't be uploaded!",
"mco.upload.percent": "%s %%",
"mco.upload.preparing": "Preparing your world", "mco.upload.preparing": "Preparing your world",
"mco.upload.select.world.none": "No singleplayer worlds found!", "mco.upload.select.world.none": "No singleplayer worlds found!",
"mco.upload.select.world.subtitle": "Please select a singleplayer world to upload", "mco.upload.select.world.subtitle": "Please select a singleplayer world to upload",
@ -4557,11 +4658,6 @@
"mco.upload.size.failure.line2": "It is %s. The maximum allowed size is %s.", "mco.upload.size.failure.line2": "It is %s. The maximum allowed size is %s.",
"mco.upload.uploading": "Uploading '%s'", "mco.upload.uploading": "Uploading '%s'",
"mco.upload.verifying": "Verifying your world", "mco.upload.verifying": "Verifying your world",
"mco.time.now": "right now",
"mco.time.secondsAgo": "%1$s second(s) ago",
"mco.time.minutesAgo": "%1$s minute(s) ago",
"mco.time.hoursAgo": "%1$s hour(s) ago",
"mco.time.daysAgo": "%1$s day(s) ago",
"mco.warning": "Warning!", "mco.warning": "Warning!",
"mco.worldSlot.minigame": "Minigame", "mco.worldSlot.minigame": "Minigame",
"menu.convertingLevel": "Converting world", "menu.convertingLevel": "Converting world",
@ -4599,6 +4695,7 @@
"merchant.level.4": "Expert", "merchant.level.4": "Expert",
"merchant.level.5": "Master", "merchant.level.5": "Master",
"merchant.next_level": "Trader's next level", "merchant.next_level": "Trader's next level",
"merchant.title": "%s - %s",
"merchant.trades": "Trades", "merchant.trades": "Trades",
"mirror.front_back": "\u2191 \u2193", "mirror.front_back": "\u2191 \u2193",
"mirror.left_right": "\u2190 \u2192", "mirror.left_right": "\u2190 \u2192",
@ -4648,6 +4745,7 @@
"multiplayer.player.joined": "%s joined the game", "multiplayer.player.joined": "%s joined the game",
"multiplayer.player.joined.renamed": "%s (formerly known as %s) joined the game", "multiplayer.player.joined.renamed": "%s (formerly known as %s) joined the game",
"multiplayer.player.left": "%s left the game", "multiplayer.player.left": "%s left the game",
"multiplayer.player.list.hp": "%shp",
"multiplayer.player.list.narration": "Online players: %s", "multiplayer.player.list.narration": "Online players: %s",
"multiplayer.requiredTexturePrompt.disconnect": "Server requires a custom resource pack", "multiplayer.requiredTexturePrompt.disconnect": "Server requires a custom resource pack",
"multiplayer.requiredTexturePrompt.line1": "This server requires the use of a custom resource pack.", "multiplayer.requiredTexturePrompt.line1": "This server requires the use of a custom resource pack.",
@ -4666,6 +4764,7 @@
"multiplayer.status.ping": "%s ms", "multiplayer.status.ping": "%s ms",
"multiplayer.status.ping.narration": "Ping %s milliseconds", "multiplayer.status.ping.narration": "Ping %s milliseconds",
"multiplayer.status.pinging": "Pinging...", "multiplayer.status.pinging": "Pinging...",
"multiplayer.status.player_count": "%s/%s",
"multiplayer.status.player_count.narration": "%s out of %s players online", "multiplayer.status.player_count.narration": "%s out of %s players online",
"multiplayer.status.quitting": "Quitting", "multiplayer.status.quitting": "Quitting",
"multiplayer.status.request_handled": "Status request has been handled", "multiplayer.status.request_handled": "Status request has been handled",
@ -4702,6 +4801,10 @@
"narration.slider.usage.hovered": "Drag slider to change value", "narration.slider.usage.hovered": "Drag slider to change value",
"narration.suggestion": "Selected suggestion %d out of %d: %s", "narration.suggestion": "Selected suggestion %d out of %d: %s",
"narration.suggestion.tooltip": "Selected suggestion %d out of %d: %s (%s)", "narration.suggestion.tooltip": "Selected suggestion %d out of %d: %s (%s)",
"narration.suggestion.usage.cycle.fixed": "Press Tab to cycle to the next suggestion",
"narration.suggestion.usage.cycle.hidable": "Press Tab to cycle to the next suggestion, or Escape to leave suggestions",
"narration.suggestion.usage.fill.fixed": "Press Tab to use suggestion",
"narration.suggestion.usage.fill.hidable": "Press Tab to use suggestion, or Escape to leave suggestions",
"narration.tab_navigation.usage": "Press Ctrl and Tab to switch between tabs", "narration.tab_navigation.usage": "Press Ctrl and Tab to switch between tabs",
"narrator.button.accessibility": "Accessibility", "narrator.button.accessibility": "Accessibility",
"narrator.button.difficulty_lock": "Difficulty lock", "narrator.button.difficulty_lock": "Difficulty lock",
@ -4731,21 +4834,26 @@
"optimizeWorld.info.converted": "Upgraded chunks: %s", "optimizeWorld.info.converted": "Upgraded chunks: %s",
"optimizeWorld.info.skipped": "Skipped chunks: %s", "optimizeWorld.info.skipped": "Skipped chunks: %s",
"optimizeWorld.info.total": "Total chunks: %s", "optimizeWorld.info.total": "Total chunks: %s",
"optimizeWorld.progress.counter": "%s / %s",
"optimizeWorld.progress.percentage": "%s%%",
"optimizeWorld.stage.counting": "Counting chunks...", "optimizeWorld.stage.counting": "Counting chunks...",
"optimizeWorld.stage.failed": "Failed! :(", "optimizeWorld.stage.failed": "Failed! :(",
"optimizeWorld.stage.finished": "Finishing up...", "optimizeWorld.stage.finished": "Finishing up...",
"optimizeWorld.stage.upgrading": "Upgrading all chunks...", "optimizeWorld.stage.upgrading": "Upgrading all chunks...",
"optimizeWorld.title": "Optimizing World '%s'", "optimizeWorld.title": "Optimizing World '%s'",
"options.accessibility": "Accessibility Settings...",
"options.accessibility.high_contrast": "High Contrast", "options.accessibility.high_contrast": "High Contrast",
"options.accessibility.high_contrast.error.tooltip": "High Contrast resource pack is not available", "options.accessibility.high_contrast.error.tooltip": "High Contrast resource pack is not available",
"options.accessibility.high_contrast.tooltip": "Enhances the contrast of UI elements", "options.accessibility.high_contrast.tooltip": "Enhances the contrast of UI elements",
"options.accessibility.link": "Accessibility Guide", "options.accessibility.link": "Accessibility Guide",
"options.accessibility.narrator_hotkey": "Narrator Hotkey",
"options.accessibility.narrator_hotkey.tooltip": "Allows the Narrator to be toggled on and off with 'Ctrl+B'",
"options.accessibility.panorama_speed": "Panorama Scroll Speed", "options.accessibility.panorama_speed": "Panorama Scroll Speed",
"options.accessibility.text_background": "Text Background", "options.accessibility.text_background": "Text Background",
"options.accessibility.text_background_opacity": "Text Background Opacity", "options.accessibility.text_background_opacity": "Text Background Opacity",
"options.accessibility.text_background.chat": "Chat", "options.accessibility.text_background.chat": "Chat",
"options.accessibility.text_background.everywhere": "Everywhere", "options.accessibility.text_background.everywhere": "Everywhere",
"options.accessibility.title": "Accessibility Settings...", "options.accessibility.title": "Accessibility Settings",
"options.allowServerListing": "Allow Server Listings", "options.allowServerListing": "Allow Server Listings",
"options.allowServerListing.tooltip": "Servers may list online players as part of their public status.\nWith this option off your name will not show up in such lists.", "options.allowServerListing.tooltip": "Servers may list online players as part of their public status.\nWith this option off your name will not show up in such lists.",
"options.ao": "Smooth Lighting", "options.ao": "Smooth Lighting",
@ -4769,6 +4877,7 @@
"options.biomeBlendRadius.11": "11x11 (Extreme)", "options.biomeBlendRadius.11": "11x11 (Extreme)",
"options.biomeBlendRadius.13": "13x13 (Showoff)", "options.biomeBlendRadius.13": "13x13 (Showoff)",
"options.biomeBlendRadius.15": "15x15 (Maximum)", "options.biomeBlendRadius.15": "15x15 (Maximum)",
"options.chat": "Chat Settings...",
"options.chat.color": "Colors", "options.chat.color": "Colors",
"options.chat.delay": "Chat Delay: %s seconds", "options.chat.delay": "Chat Delay: %s seconds",
"options.chat.delay_none": "Chat Delay: None", "options.chat.delay_none": "Chat Delay: None",
@ -4779,7 +4888,7 @@
"options.chat.links.prompt": "Prompt on Links", "options.chat.links.prompt": "Prompt on Links",
"options.chat.opacity": "Chat Text Opacity", "options.chat.opacity": "Chat Text Opacity",
"options.chat.scale": "Chat Text Size", "options.chat.scale": "Chat Text Size",
"options.chat.title": "Chat Settings...", "options.chat.title": "Chat Settings",
"options.chat.visibility": "Chat", "options.chat.visibility": "Chat",
"options.chat.visibility.full": "Shown", "options.chat.visibility.full": "Shown",
"options.chat.visibility.hidden": "Hidden", "options.chat.visibility.hidden": "Hidden",
@ -4825,6 +4934,7 @@
"options.framerateLimit.max": "Unlimited", "options.framerateLimit.max": "Unlimited",
"options.fullscreen": "Fullscreen", "options.fullscreen": "Fullscreen",
"options.fullscreen.current": "Current", "options.fullscreen.current": "Current",
"options.fullscreen.entry": "%sx%s@%s (%sbit)",
"options.fullscreen.resolution": "Fullscreen Resolution", "options.fullscreen.resolution": "Fullscreen Resolution",
"options.fullscreen.unavailable": "Setting unavailable", "options.fullscreen.unavailable": "Setting unavailable",
"options.gamma": "Brightness", "options.gamma": "Brightness",
@ -4861,6 +4971,8 @@
"options.key.hold": "Hold", "options.key.hold": "Hold",
"options.key.toggle": "Toggle", "options.key.toggle": "Toggle",
"options.language": "Language...", "options.language": "Language...",
"options.language.title": "Language",
"options.languageAccuracyWarning": "(Language translations may not be 100%% accurate)",
"options.languageWarning": "Language translations may not be 100%% accurate", "options.languageWarning": "Language translations may not be 100%% accurate",
"options.mainHand": "Main Hand", "options.mainHand": "Main Hand",
"options.mainHand.left": "Left", "options.mainHand.left": "Left",
@ -4939,12 +5051,14 @@
"options.viewBobbing": "View Bobbing", "options.viewBobbing": "View Bobbing",
"options.visible": "Shown", "options.visible": "Shown",
"options.vsync": "VSync", "options.vsync": "VSync",
"outOfMemory.message": "Minecraft has run out of memory.\n\nThis could be caused by a bug in the game or by the Java Virtual Machine not being allocated enough memory.\n\nTo prevent level corruption, the current game has quit. We've tried to free up enough memory to let you go back to the main menu and back to playing, but this may not have worked.\n\nPlease restart the game if you see this message again.", "outOfMemory.message": "Minecraft has run out of memory.\n\nThis could be caused by a bug in the game or by the Java Virtual Machine not being allocated enough memory.\n\nTo prevent world corruption, the current game has quit. We've tried to free up enough memory to let you go back to the main menu and back to playing, but this may not have worked.\n\nPlease restart the game if you see this message again.",
"outOfMemory.title": "Out of memory!", "outOfMemory.title": "Out of memory!",
"pack.available.title": "Available", "pack.available.title": "Available",
"pack.copyFailure": "Failed to copy packs", "pack.copyFailure": "Failed to copy packs",
"pack.dropConfirm": "Do you want to add the following packs to Minecraft?", "pack.dropConfirm": "Do you want to add the following packs to Minecraft?",
"pack.dropInfo": "Drag and drop files into this window to add packs", "pack.dropInfo": "Drag and drop files into this window to add packs",
"pack.dropRejected.message": "The following entries were not valid packs and were not copied:\n %s",
"pack.dropRejected.title": "Non-pack entries",
"pack.folderInfo": "(Place pack files here)", "pack.folderInfo": "(Place pack files here)",
"pack.incompatible": "Incompatible", "pack.incompatible": "Incompatible",
"pack.incompatible.confirm.new": "This pack was made for a newer version of Minecraft and may not work correctly.", "pack.incompatible.confirm.new": "This pack was made for a newer version of Minecraft and may not work correctly.",
@ -5165,7 +5279,7 @@
"selectWorld.import_worldgen_settings.select_file": "Select settings file (.json)", "selectWorld.import_worldgen_settings.select_file": "Select settings file (.json)",
"selectWorld.incompatible_series": "Created by an incompatible version", "selectWorld.incompatible_series": "Created by an incompatible version",
"selectWorld.load_folder_access": "Unable to read or access folder where game worlds are saved!", "selectWorld.load_folder_access": "Unable to read or access folder where game worlds are saved!",
"selectWorld.loading_list": "Loading world list", "selectWorld.loading_list": "Loading World List",
"selectWorld.locked": "Locked by another running instance of Minecraft", "selectWorld.locked": "Locked by another running instance of Minecraft",
"selectWorld.mapFeatures": "Generate Structures", "selectWorld.mapFeatures": "Generate Structures",
"selectWorld.mapFeatures.info": "Villages, Shipwrecks, etc.", "selectWorld.mapFeatures.info": "Villages, Shipwrecks, etc.",
@ -5313,6 +5427,7 @@
"stat.minecraft.walk_one_cm": "Distance Walked", "stat.minecraft.walk_one_cm": "Distance Walked",
"stat.minecraft.walk_under_water_one_cm": "Distance Walked under Water", "stat.minecraft.walk_under_water_one_cm": "Distance Walked under Water",
"stat.mobsButton": "Mobs", "stat.mobsButton": "Mobs",
"stats.none": "-",
"stats.tooltip.type.statistic": "Statistic", "stats.tooltip.type.statistic": "Statistic",
"structure_block.button.detect_size": "DETECT", "structure_block.button.detect_size": "DETECT",
"structure_block.button.load": "LOAD", "structure_block.button.load": "LOAD",
@ -5454,6 +5569,7 @@
"subtitles.block.sniffer_egg.crack": "Sniffer Egg cracks", "subtitles.block.sniffer_egg.crack": "Sniffer Egg cracks",
"subtitles.block.sniffer_egg.hatch": "Sniffer Egg hatches", "subtitles.block.sniffer_egg.hatch": "Sniffer Egg hatches",
"subtitles.block.sniffer_egg.plop": "Sniffer plops", "subtitles.block.sniffer_egg.plop": "Sniffer plops",
"subtitles.block.sponge.absorb": "Sponge sucks",
"subtitles.block.sweet_berry_bush.pick_berries": "Berries pop", "subtitles.block.sweet_berry_bush.pick_berries": "Berries pop",
"subtitles.block.trapdoor.toggle": "Trapdoor creaks", "subtitles.block.trapdoor.toggle": "Trapdoor creaks",
"subtitles.block.tripwire.attach": "Tripwire attaches", "subtitles.block.tripwire.attach": "Tripwire attaches",
@ -6094,8 +6210,12 @@
"subtitles.ui.loom.take_result": "Loom used", "subtitles.ui.loom.take_result": "Loom used",
"subtitles.ui.stonecutter.take_result": "Stonecutter used", "subtitles.ui.stonecutter.take_result": "Stonecutter used",
"subtitles.weather.rain": "Rain falls", "subtitles.weather.rain": "Rain falls",
"symlink_warning.title": "World folder contains symbolic links",
"symlink_warning.message": "Loading worlds from folders with symbolic links can be unsafe if you don't know exactly what you are doing. Please visit %s to learn more.", "symlink_warning.message": "Loading worlds from folders with symbolic links can be unsafe if you don't know exactly what you are doing. Please visit %s to learn more.",
"symlink_warning.message.pack": "Loading packs with symbolic links can be unsafe if you don't know exactly what you are doing. Please visit %s to learn more.",
"symlink_warning.message.world": "Loading worlds from folders with symbolic links can be unsafe if you don't know exactly what you are doing. Please visit %s to learn more.",
"symlink_warning.title": "World folder contains symbolic links",
"symlink_warning.title.pack": "Added pack(s) contain(s) symbolic links",
"symlink_warning.title.world": "The world folder contains symbolic links",
"team.collision.always": "Always", "team.collision.always": "Always",
"team.collision.never": "Never", "team.collision.never": "Never",
"team.collision.pushOtherTeams": "Push other teams", "team.collision.pushOtherTeams": "Push other teams",
@ -6106,6 +6226,7 @@
"team.visibility.hideForOwnTeam": "Hide for own team", "team.visibility.hideForOwnTeam": "Hide for own team",
"team.visibility.never": "Never", "team.visibility.never": "Never",
"telemetry_info.button.give_feedback": "Give Feedback", "telemetry_info.button.give_feedback": "Give Feedback",
"telemetry_info.button.privacy_statement": "Privacy Statement",
"telemetry_info.button.show_data": "Open My Data", "telemetry_info.button.show_data": "Open My Data",
"telemetry_info.property_title": "Included Data", "telemetry_info.property_title": "Included Data",
"telemetry_info.screen.description": "Collecting this data helps us improve Minecraft by guiding us in directions that are relevant to our players.\nYou can also send in additional feedback to help us keep improving Minecraft.", "telemetry_info.screen.description": "Collecting this data helps us improve Minecraft by guiding us in directions that are relevant to our players.\nYou can also send in additional feedback to help us keep improving Minecraft.",
@ -6159,7 +6280,9 @@
"title.32bit.deprecation.realms": "Minecraft will soon require a 64-bit system, which will prevent you from playing or using Realms on this device. You will need to manually cancel any Realms subscription.", "title.32bit.deprecation.realms": "Minecraft will soon require a 64-bit system, which will prevent you from playing or using Realms on this device. You will need to manually cancel any Realms subscription.",
"title.32bit.deprecation.realms.check": "Do not show this screen again", "title.32bit.deprecation.realms.check": "Do not show this screen again",
"title.32bit.deprecation.realms.header": "32-bit system detected", "title.32bit.deprecation.realms.header": "32-bit system detected",
"title.credits": "Copyright Mojang AB. Do not distribute!",
"title.multiplayer.disabled": "Multiplayer is disabled. Please check your Microsoft account settings.", "title.multiplayer.disabled": "Multiplayer is disabled. Please check your Microsoft account settings.",
"title.multiplayer.disabled.banned.name": "You must change your name before you can play online",
"title.multiplayer.disabled.banned.permanent": "Your account is permanently suspended from online play", "title.multiplayer.disabled.banned.permanent": "Your account is permanently suspended from online play",
"title.multiplayer.disabled.banned.temporary": "Your account is temporarily suspended from online play", "title.multiplayer.disabled.banned.temporary": "Your account is temporarily suspended from online play",
"title.multiplayer.lan": "Multiplayer (LAN)", "title.multiplayer.lan": "Multiplayer (LAN)",

View file

@ -255,6 +255,9 @@ impl Nbt {
} }
/// Read the NBT data. This will return a compound tag with a single item. /// Read the NBT data. This will return a compound tag with a single item.
///
/// Minecraft usually uses this function when reading from files.
/// [`Nbt::read_any_tag`] is used when reading from the network.
pub fn read(stream: &mut Cursor<&[u8]>) -> Result<Nbt, Error> { pub fn read(stream: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
// default to compound tag // default to compound tag
@ -271,6 +274,17 @@ impl Nbt {
Ok(Nbt::Compound(map)) Ok(Nbt::Compound(map))
} }
/// Read the NBT data. There is no guarantee that the tag will be a compound
/// with a single item.
///
/// The Minecraft protocol uses this function when reading from the network.
/// [`Nbt::read`] is usually used when reading from files.
pub fn read_any_tag(stream: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
let tag_id = stream.read_u8().unwrap_or(0);
let tag = Nbt::read_known(stream, tag_id)?;
Ok(tag)
}
/// Read the NBT data compressed wtih zlib. /// Read the NBT data compressed wtih zlib.
pub fn read_zlib(stream: &mut impl BufRead) -> Result<Nbt, Error> { pub fn read_zlib(stream: &mut impl BufRead) -> Result<Nbt, Error> {
let mut gz = ZlibDecoder::new(stream); let mut gz = ZlibDecoder::new(stream);
@ -290,7 +304,7 @@ impl Nbt {
impl McBufReadable for Nbt { impl McBufReadable for Nbt {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
Ok(Nbt::read(buf)?) Ok(Nbt::read_any_tag(buf)?)
} }
} }
impl From<Error> for BufReadError { impl From<Error> for BufReadError {

View file

@ -16,51 +16,55 @@ fn write_compound(writer: &mut impl Write, value: &NbtCompound, end_tag: bool) {
for (key, tag) in value.iter() { for (key, tag) in value.iter() {
writer.write_u8(tag.id()).unwrap(); writer.write_u8(tag.id()).unwrap();
write_string(writer, key); write_string(writer, key);
match tag { write_known(writer, tag);
Nbt::End => {}
Nbt::Byte(value) => {
writer.write_i8(*value).unwrap();
}
Nbt::Short(value) => {
writer.write_i16::<BE>(*value).unwrap();
}
Nbt::Int(value) => {
writer.write_i32::<BE>(*value).unwrap();
}
Nbt::Long(value) => {
writer.write_i64::<BE>(*value).unwrap();
}
Nbt::Float(value) => {
writer.write_f32::<BE>(*value).unwrap();
}
Nbt::Double(value) => {
writer.write_f64::<BE>(*value).unwrap();
}
Nbt::ByteArray(value) => {
write_byte_array(writer, value);
}
Nbt::String(value) => {
write_string(writer, value);
}
Nbt::List(value) => {
write_list(writer, value);
}
Nbt::Compound(value) => {
write_compound(writer, value, true);
}
Nbt::IntArray(value) => {
write_int_array(writer, value);
}
Nbt::LongArray(value) => {
write_long_array(writer, value);
}
}
} }
if end_tag { if end_tag {
writer.write_u8(END_ID).unwrap(); writer.write_u8(END_ID).unwrap();
} }
} }
fn write_known(writer: &mut impl Write, tag: &Nbt) {
match tag {
Nbt::End => {}
Nbt::Byte(value) => {
writer.write_i8(*value).unwrap();
}
Nbt::Short(value) => {
writer.write_i16::<BE>(*value).unwrap();
}
Nbt::Int(value) => {
writer.write_i32::<BE>(*value).unwrap();
}
Nbt::Long(value) => {
writer.write_i64::<BE>(*value).unwrap();
}
Nbt::Float(value) => {
writer.write_f32::<BE>(*value).unwrap();
}
Nbt::Double(value) => {
writer.write_f64::<BE>(*value).unwrap();
}
Nbt::ByteArray(value) => {
write_byte_array(writer, value);
}
Nbt::String(value) => {
write_string(writer, value);
}
Nbt::List(value) => {
write_list(writer, value);
}
Nbt::Compound(value) => {
write_compound(writer, value, true);
}
Nbt::IntArray(value) => {
write_int_array(writer, value);
}
Nbt::LongArray(value) => {
write_long_array(writer, value);
}
}
}
#[inline] #[inline]
fn write_list(writer: &mut impl Write, value: &NbtList) { fn write_list(writer: &mut impl Write, value: &NbtList) {
writer.write_u8(value.id()).unwrap(); writer.write_u8(value.id()).unwrap();
@ -256,6 +260,13 @@ impl Nbt {
} }
} }
/// Write any tag as NBT data. This is used by Minecraft when writing to the
/// network, otherwise [`Nbt::write`] is usually used instead.
pub fn write_any(&self, writer: &mut impl Write) {
writer.write_u8(self.id()).unwrap();
write_known(writer, self);
}
/// Write the compound tag as NBT data compressed wtih zlib. /// Write the compound tag as NBT data compressed wtih zlib.
/// ///
/// # Errors /// # Errors
@ -279,7 +290,7 @@ impl Nbt {
impl McBufWritable for Nbt { impl McBufWritable for Nbt {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.write(buf); self.write_any(buf);
Ok(()) Ok(())
} }
} }

View file

@ -226,6 +226,16 @@ impl NbtCompound {
self.inner.len() >= 32 self.inner.len() >= 32
} }
} }
impl IntoIterator for NbtCompound {
type Item = (NbtString, Nbt);
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl Serialize for NbtCompound { impl Serialize for NbtCompound {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {

View file

@ -47,7 +47,7 @@ pub fn derive_serverbound_game_packet(input: TokenStream) -> TokenStream {
pub fn derive_serverbound_handshake_packet(input: TokenStream) -> TokenStream { pub fn derive_serverbound_handshake_packet(input: TokenStream) -> TokenStream {
as_packet_derive( as_packet_derive(
input, input,
quote! {crate::packets::handshake::ServerboundHandshakePacket}, quote! {crate::packets::handshaking::ServerboundHandshakePacket},
) )
} }
#[proc_macro_derive(ServerboundLoginPacket, attributes(var))] #[proc_macro_derive(ServerboundLoginPacket, attributes(var))]
@ -64,6 +64,13 @@ pub fn derive_serverbound_status_packet(input: TokenStream) -> TokenStream {
quote! {crate::packets::status::ServerboundStatusPacket}, quote! {crate::packets::status::ServerboundStatusPacket},
) )
} }
#[proc_macro_derive(ServerboundConfigurationPacket, attributes(var))]
pub fn derive_serverbound_configuration_packet(input: TokenStream) -> TokenStream {
as_packet_derive(
input,
quote! {crate::packets::configuration::ServerboundConfigurationPacket},
)
}
#[proc_macro_derive(ClientboundGamePacket, attributes(var))] #[proc_macro_derive(ClientboundGamePacket, attributes(var))]
pub fn derive_clientbound_game_packet(input: TokenStream) -> TokenStream { pub fn derive_clientbound_game_packet(input: TokenStream) -> TokenStream {
@ -73,7 +80,7 @@ pub fn derive_clientbound_game_packet(input: TokenStream) -> TokenStream {
pub fn derive_clientbound_handshake_packet(input: TokenStream) -> TokenStream { pub fn derive_clientbound_handshake_packet(input: TokenStream) -> TokenStream {
as_packet_derive( as_packet_derive(
input, input,
quote! {crate::packets::handshake::ClientboundHandshakePacket}, quote! {crate::packets::handshaking::ClientboundHandshakePacket},
) )
} }
#[proc_macro_derive(ClientboundLoginPacket, attributes(var))] #[proc_macro_derive(ClientboundLoginPacket, attributes(var))]
@ -90,6 +97,13 @@ pub fn derive_clientbound_status_packet(input: TokenStream) -> TokenStream {
quote! {crate::packets::status::ClientboundStatusPacket}, quote! {crate::packets::status::ClientboundStatusPacket},
) )
} }
#[proc_macro_derive(ClientboundConfigurationPacket, attributes(var))]
pub fn derive_clientbound_configuration_packet(input: TokenStream) -> TokenStream {
as_packet_derive(
input,
quote! {crate::packets::configuration::ClientboundConfigurationPacket},
)
}
#[derive(Debug)] #[derive(Debug)]
struct PacketIdPair { struct PacketIdPair {

View file

@ -4,7 +4,7 @@
use azalea_protocol::{ use azalea_protocol::{
connect::Connection, connect::Connection,
packets::{ packets::{
handshake::{ handshaking::{
client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket, client_intention_packet::ClientIntentionPacket, ClientboundHandshakePacket,
ServerboundHandshakePacket, ServerboundHandshakePacket,
}, },
@ -145,11 +145,7 @@ async fn handle_connection(stream: TcpStream) -> anyhow::Result<()> {
"Player \'{0}\' from {1} logging in with uuid: {2}", "Player \'{0}\' from {1} logging in with uuid: {2}",
hello.name, hello.name,
ip.ip(), ip.ip(),
if let Some(id) = hello.profile_id { hello.profile_id.to_string()
id.to_string()
} else {
String::new()
}
); );
tokio::spawn(transfer(conn.unwrap()?, intent, hello).map(|r| { tokio::spawn(transfer(conn.unwrap()?, intent, hello).map(|r| {

View file

@ -1,19 +1,23 @@
//! Connect to remote servers/clients. //! Connect to remote servers/clients.
use crate::packets::configuration::{
ClientboundConfigurationPacket, ServerboundConfigurationPacket,
};
use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket}; use crate::packets::game::{ClientboundGamePacket, ServerboundGamePacket};
use crate::packets::handshake::{ClientboundHandshakePacket, ServerboundHandshakePacket}; use crate::packets::handshaking::{ClientboundHandshakePacket, ServerboundHandshakePacket};
use crate::packets::login::clientbound_hello_packet::ClientboundHelloPacket; use crate::packets::login::clientbound_hello_packet::ClientboundHelloPacket;
use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket}; use crate::packets::login::{ClientboundLoginPacket, ServerboundLoginPacket};
use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket}; use crate::packets::status::{ClientboundStatusPacket, ServerboundStatusPacket};
use crate::packets::ProtocolPacket; use crate::packets::ProtocolPacket;
use crate::read::{read_packet, try_read_packet, ReadPacketError}; use crate::read::{deserialize_packet, read_raw_packet, try_read_raw_packet, ReadPacketError};
use crate::write::write_packet; use crate::write::{serialize_packet, write_raw_packet};
use azalea_auth::game_profile::GameProfile; use azalea_auth::game_profile::GameProfile;
use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError}; use azalea_auth::sessionserver::{ClientSessionServerError, ServerSessionServerError};
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc}; use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
use bytes::BytesMut; use bytes::BytesMut;
use log::{error, info}; use log::{error, info};
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Cursor;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use thiserror::Error; use thiserror::Error;
@ -22,20 +26,28 @@ use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReuniteError};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use uuid::Uuid; use uuid::Uuid;
/// The read half of a connection. pub struct RawReadConnection {
pub struct ReadConnection<R: ProtocolPacket> {
pub read_stream: OwnedReadHalf, pub read_stream: OwnedReadHalf,
pub buffer: BytesMut, pub buffer: BytesMut,
pub compression_threshold: Option<u32>, pub compression_threshold: Option<u32>,
pub dec_cipher: Option<Aes128CfbDec>, pub dec_cipher: Option<Aes128CfbDec>,
}
pub struct RawWriteConnection {
pub write_stream: OwnedWriteHalf,
pub compression_threshold: Option<u32>,
pub enc_cipher: Option<Aes128CfbEnc>,
}
/// The read half of a connection.
pub struct ReadConnection<R: ProtocolPacket> {
pub raw: RawReadConnection,
_reading: PhantomData<R>, _reading: PhantomData<R>,
} }
/// The write half of a connection. /// The write half of a connection.
pub struct WriteConnection<W: ProtocolPacket> { pub struct WriteConnection<W: ProtocolPacket> {
pub write_stream: OwnedWriteHalf, pub raw: RawWriteConnection,
pub compression_threshold: Option<u32>,
pub enc_cipher: Option<Aes128CfbEnc>,
_writing: PhantomData<W>, _writing: PhantomData<W>,
} }
@ -55,7 +67,7 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// serverbound_hello_packet::ServerboundHelloPacket, /// serverbound_hello_packet::ServerboundHelloPacket,
/// serverbound_key_packet::ServerboundKeyPacket /// serverbound_key_packet::ServerboundKeyPacket
/// }, /// },
/// handshake::client_intention_packet::ClientIntentionPacket /// handshaking::client_intention_packet::ClientIntentionPacket
/// } /// }
/// }; /// };
/// ///
@ -82,7 +94,7 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// conn.write( /// conn.write(
/// ServerboundHelloPacket { /// ServerboundHelloPacket {
/// name: "bot".to_string(), /// name: "bot".to_string(),
/// profile_id: None, /// profile_id: uuid::Uuid::nil(),
/// } /// }
/// .get(), /// .get(),
/// ) /// )
@ -108,7 +120,7 @@ pub struct WriteConnection<W: ProtocolPacket> {
/// conn.set_compression_threshold(p.compression_threshold); /// conn.set_compression_threshold(p.compression_threshold);
/// } /// }
/// ClientboundLoginPacket::GameProfile(p) => { /// ClientboundLoginPacket::GameProfile(p) => {
/// break (conn.game(), p.game_profile); /// break (conn.configuration(), p.game_profile);
/// } /// }
/// ClientboundLoginPacket::LoginDisconnect(p) => { /// ClientboundLoginPacket::LoginDisconnect(p) => {
/// eprintln!("login disconnect: {}", p.reason); /// eprintln!("login disconnect: {}", p.reason);
@ -126,13 +138,9 @@ pub struct Connection<R: ProtocolPacket, W: ProtocolPacket> {
pub writer: WriteConnection<W>, pub writer: WriteConnection<W>,
} }
impl<R> ReadConnection<R> impl RawReadConnection {
where pub async fn read(&mut self) -> Result<Vec<u8>, Box<ReadPacketError>> {
R: ProtocolPacket + Debug, read_raw_packet::<_>(
{
/// Read a packet from the stream.
pub async fn read(&mut self) -> Result<R, Box<ReadPacketError>> {
read_packet::<R, _>(
&mut self.read_stream, &mut self.read_stream,
&mut self.buffer, &mut self.buffer,
self.compression_threshold, self.compression_threshold,
@ -141,10 +149,8 @@ where
.await .await
} }
/// Try to read a packet from the stream, or return Ok(None) if there's no pub fn try_read(&mut self) -> Result<Option<Vec<u8>>, Box<ReadPacketError>> {
/// packet. try_read_raw_packet::<_>(
pub fn try_read(&mut self) -> Result<Option<R>, Box<ReadPacketError>> {
try_read_packet::<R, _>(
&mut self.read_stream, &mut self.read_stream,
&mut self.buffer, &mut self.buffer,
self.compression_threshold, self.compression_threshold,
@ -152,14 +158,11 @@ where
) )
} }
} }
impl<W> WriteConnection<W>
where impl RawWriteConnection {
W: ProtocolPacket + Debug, pub async fn write(&mut self, packet: &[u8]) -> std::io::Result<()> {
{ if let Err(e) = write_raw_packet(
/// Write a packet to the server. packet,
pub async fn write(&mut self, packet: W) -> std::io::Result<()> {
if let Err(e) = write_packet(
&packet,
&mut self.write_stream, &mut self.write_stream,
self.compression_threshold, self.compression_threshold,
&mut self.enc_cipher, &mut self.enc_cipher,
@ -184,6 +187,42 @@ where
} }
} }
impl<R> ReadConnection<R>
where
R: ProtocolPacket + Debug,
{
/// Read a packet from the stream.
pub async fn read(&mut self) -> Result<R, Box<ReadPacketError>> {
let raw_packet = self.raw.read().await?;
deserialize_packet(&mut Cursor::new(raw_packet.as_slice()))
}
/// Try to read a packet from the stream, or return Ok(None) if there's no
/// packet.
pub fn try_read(&mut self) -> Result<Option<R>, Box<ReadPacketError>> {
let Some(raw_packet) = self.raw.try_read()? else {
return Ok(None);
};
Ok(Some(deserialize_packet(&mut Cursor::new(
raw_packet.as_slice(),
))?))
}
}
impl<W> WriteConnection<W>
where
W: ProtocolPacket + Debug,
{
/// Write a packet to the server.
pub async fn write(&mut self, packet: W) -> std::io::Result<()> {
self.raw.write(&serialize_packet(&packet).unwrap()).await
}
/// End the connection.
pub async fn shutdown(&mut self) -> std::io::Result<()> {
self.raw.shutdown().await
}
}
impl<R, W> Connection<R, W> impl<R, W> Connection<R, W>
where where
R: ProtocolPacket + Debug, R: ProtocolPacket + Debug,
@ -230,16 +269,20 @@ impl Connection<ClientboundHandshakePacket, ServerboundHandshakePacket> {
Ok(Connection { Ok(Connection {
reader: ReadConnection { reader: ReadConnection {
read_stream, raw: RawReadConnection {
buffer: BytesMut::new(), read_stream,
compression_threshold: None, buffer: BytesMut::new(),
dec_cipher: None, compression_threshold: None,
dec_cipher: None,
},
_reading: PhantomData, _reading: PhantomData,
}, },
writer: WriteConnection { writer: WriteConnection {
write_stream, raw: RawWriteConnection {
compression_threshold: None, write_stream,
enc_cipher: None, compression_threshold: None,
enc_cipher: None,
},
_writing: PhantomData, _writing: PhantomData,
}, },
}) })
@ -267,11 +310,11 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
pub fn set_compression_threshold(&mut self, threshold: i32) { pub fn set_compression_threshold(&mut self, threshold: i32) {
// if you pass a threshold of less than 0, compression is disabled // if you pass a threshold of less than 0, compression is disabled
if threshold >= 0 { if threshold >= 0 {
self.reader.compression_threshold = Some(threshold as u32); self.reader.raw.compression_threshold = Some(threshold as u32);
self.writer.compression_threshold = Some(threshold as u32); self.writer.raw.compression_threshold = Some(threshold as u32);
} else { } else {
self.reader.compression_threshold = None; self.reader.raw.compression_threshold = None;
self.writer.compression_threshold = None; self.writer.raw.compression_threshold = None;
} }
} }
@ -279,14 +322,16 @@ impl Connection<ClientboundLoginPacket, ServerboundLoginPacket> {
/// the same for both reading and writing. /// the same for both reading and writing.
pub fn set_encryption_key(&mut self, key: [u8; 16]) { pub fn set_encryption_key(&mut self, key: [u8; 16]) {
let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key); let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key);
self.reader.dec_cipher = Some(dec_cipher); self.reader.raw.dec_cipher = Some(dec_cipher);
self.writer.enc_cipher = Some(enc_cipher); self.writer.raw.enc_cipher = Some(enc_cipher);
} }
/// Change our state from login to game. This is the state that's used when /// Change our state from login to configuration. This is the state where
/// you're actually in the game. /// the server sends us the registries and resource pack and stuff.
#[must_use] #[must_use]
pub fn game(self) -> Connection<ClientboundGamePacket, ServerboundGamePacket> { pub fn configuration(
self,
) -> Connection<ClientboundConfigurationPacket, ServerboundConfigurationPacket> {
Connection::from(self) Connection::from(self)
} }
@ -384,11 +429,11 @@ impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
pub fn set_compression_threshold(&mut self, threshold: i32) { pub fn set_compression_threshold(&mut self, threshold: i32) {
// if you pass a threshold of less than 0, compression is disabled // if you pass a threshold of less than 0, compression is disabled
if threshold >= 0 { if threshold >= 0 {
self.reader.compression_threshold = Some(threshold as u32); self.reader.raw.compression_threshold = Some(threshold as u32);
self.writer.compression_threshold = Some(threshold as u32); self.writer.raw.compression_threshold = Some(threshold as u32);
} else { } else {
self.reader.compression_threshold = None; self.reader.raw.compression_threshold = None;
self.writer.compression_threshold = None; self.writer.raw.compression_threshold = None;
} }
} }
@ -396,8 +441,8 @@ impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
/// the same for both reading and writing. /// the same for both reading and writing.
pub fn set_encryption_key(&mut self, key: [u8; 16]) { pub fn set_encryption_key(&mut self, key: [u8; 16]) {
let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key); let (enc_cipher, dec_cipher) = azalea_crypto::create_cipher(&key);
self.reader.dec_cipher = Some(dec_cipher); self.reader.raw.dec_cipher = Some(dec_cipher);
self.writer.enc_cipher = Some(enc_cipher); self.writer.raw.enc_cipher = Some(enc_cipher);
} }
/// Change our state from login to game. This is the state that's used when /// Change our state from login to game. This is the state that's used when
@ -421,6 +466,25 @@ impl Connection<ServerboundLoginPacket, ClientboundLoginPacket> {
} }
} }
impl Connection<ClientboundConfigurationPacket, ServerboundConfigurationPacket> {
/// Change our state from configuration to game. This is the state that's
/// used when the client is actually in the world.
#[must_use]
pub fn game(self) -> Connection<ClientboundGamePacket, ServerboundGamePacket> {
Connection::from(self)
}
}
impl Connection<ClientboundGamePacket, ServerboundGamePacket> {
/// Change our state back to configuration.
#[must_use]
pub fn configuration(
self,
) -> Connection<ClientboundConfigurationPacket, ServerboundConfigurationPacket> {
Connection::from(self)
}
}
// rust doesn't let us implement From because allegedly it conflicts with // rust doesn't let us implement From because allegedly it conflicts with
// `core`'s "impl<T> From<T> for T" so we do this instead // `core`'s "impl<T> From<T> for T" so we do this instead
impl<R1, W1> Connection<R1, W1> impl<R1, W1> Connection<R1, W1>
@ -438,16 +502,11 @@ where
{ {
Connection { Connection {
reader: ReadConnection { reader: ReadConnection {
read_stream: connection.reader.read_stream, raw: connection.reader.raw,
buffer: connection.reader.buffer,
compression_threshold: connection.reader.compression_threshold,
dec_cipher: connection.reader.dec_cipher,
_reading: PhantomData, _reading: PhantomData,
}, },
writer: WriteConnection { writer: WriteConnection {
compression_threshold: connection.writer.compression_threshold, raw: connection.writer.raw,
write_stream: connection.writer.write_stream,
enc_cipher: connection.writer.enc_cipher,
_writing: PhantomData, _writing: PhantomData,
}, },
} }
@ -459,16 +518,20 @@ where
Connection { Connection {
reader: ReadConnection { reader: ReadConnection {
read_stream, raw: RawReadConnection {
buffer: BytesMut::new(), read_stream,
compression_threshold: None, buffer: BytesMut::new(),
dec_cipher: None, compression_threshold: None,
dec_cipher: None,
},
_reading: PhantomData, _reading: PhantomData,
}, },
writer: WriteConnection { writer: WriteConnection {
write_stream, raw: RawWriteConnection {
compression_threshold: None, write_stream,
enc_cipher: None, compression_threshold: None,
enc_cipher: None,
},
_writing: PhantomData, _writing: PhantomData,
}, },
} }
@ -476,6 +539,9 @@ where
/// Convert from a `Connection` into a `TcpStream`. Useful for servers. /// Convert from a `Connection` into a `TcpStream`. Useful for servers.
pub fn unwrap(self) -> Result<TcpStream, ReuniteError> { pub fn unwrap(self) -> Result<TcpStream, ReuniteError> {
self.reader.read_stream.reunite(self.writer.write_stream) self.reader
.raw
.read_stream
.reunite(self.writer.raw.write_stream)
} }
} }

View file

@ -86,7 +86,7 @@ mod tests {
login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket}, login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket},
}, },
read::{compression_decoder, read_packet}, read::{compression_decoder, read_packet},
write::{compression_encoder, packet_encoder, write_packet}, write::{compression_encoder, serialize_packet, write_packet},
}; };
use bytes::BytesMut; use bytes::BytesMut;
use uuid::Uuid; use uuid::Uuid;
@ -95,7 +95,7 @@ mod tests {
async fn test_hello_packet() { async fn test_hello_packet() {
let packet = ServerboundHelloPacket { let packet = ServerboundHelloPacket {
name: "test".to_string(), name: "test".to_string(),
profile_id: Some(Uuid::nil()), profile_id: Uuid::nil(),
} }
.get(); .get();
let mut stream = Vec::new(); let mut stream = Vec::new();
@ -119,7 +119,7 @@ mod tests {
async fn test_double_hello_packet() { async fn test_double_hello_packet() {
let packet = ServerboundHelloPacket { let packet = ServerboundHelloPacket {
name: "test".to_string(), name: "test".to_string(),
profile_id: Some(Uuid::nil()), profile_id: Uuid::nil(),
} }
.get(); .get();
let mut stream = Vec::new(); let mut stream = Vec::new();
@ -145,7 +145,7 @@ mod tests {
async fn test_read_long_compressed_chat() { async fn test_read_long_compressed_chat() {
let compression_threshold = 256; let compression_threshold = 256;
let buf = packet_encoder( let buf = serialize_packet(
&ServerboundChatPacket { &ServerboundChatPacket {
message: "a".repeat(256), message: "a".repeat(256),
timestamp: 0, timestamp: 0,

View file

@ -0,0 +1,16 @@
use azalea_buf::McBuf;
use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation};
#[derive(Clone, Debug, McBuf)]
pub struct CommonPlayerSpawnInfo {
pub dimension_type: ResourceLocation,
pub dimension: ResourceLocation,
pub seed: i64,
pub game_type: GameMode,
pub previous_game_type: OptionalGameType,
pub is_debug: bool,
pub is_flat: bool,
pub last_death_location: Option<GlobalPos>,
#[var]
pub portal_cooldown: u32,
}

View file

@ -0,0 +1,10 @@
use azalea_buf::McBuf;
use azalea_buf::UnsizedByteArray;
use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundCustomPayloadPacket {
pub identifier: ResourceLocation,
pub data: UnsizedByteArray,
}

View file

@ -0,0 +1,8 @@
use azalea_buf::McBuf;
use azalea_chat::FormattedText;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundDisconnectPacket {
pub reason: FormattedText,
}

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundFinishConfigurationPacket {}

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundKeepAlivePacket {
pub id: u64,
}

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundPingPacket {
pub id: u32,
}

View file

@ -0,0 +1,408 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundConfigurationPacket;
use self::registry::RegistryHolder;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundRegistryDataPacket {
pub registry_holder: RegistryHolder,
}
pub mod registry {
//! [ClientboundRegistryDataPacket](super::ClientboundRegistryDataPacket)
//! Registry Structures
//!
//! This module contains the structures used to represent the registry
//! sent to the client upon login. This contains a lot of information about
//! the game, including the types of chat messages, dimensions, and
//! biomes.
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::ResourceLocation;
use azalea_nbt::Nbt;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{collections::HashMap, io::Cursor};
/// The base of the registry.
///
/// This is the registry that is sent to the client upon login.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryHolder {
pub registries: HashMap<ResourceLocation, Nbt>,
}
impl TryFrom<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_from(value: Nbt) -> Result<Self, Self::Error> {
Ok(RegistryHolder {
registries: serde_json::from_value(serde_json::to_value(value)?)?,
})
}
}
impl TryInto<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_into(self) -> Result<Nbt, Self::Error> {
serde_json::from_value(serde_json::to_value(self.registries)?)
}
}
impl McBufReadable for RegistryHolder {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
RegistryHolder::try_from(Nbt::read_from(buf)?)
.map_err(|e| BufReadError::Deserialization { source: e })
}
}
impl McBufWritable for RegistryHolder {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
TryInto::<Nbt>::try_into(self.clone())?.write_into(buf)
}
}
/// A collection of values for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct RegistryType<T> {
#[serde(rename = "type")]
pub kind: ResourceLocation,
pub value: Vec<TypeValue<T>>,
}
/// A value for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TypeValue<T> {
pub id: u32,
pub name: ResourceLocation,
pub element: T,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TrimMaterialElement {
pub asset_name: String,
pub ingredient: ResourceLocation,
pub item_model_index: f32,
pub override_armor_materials: HashMap<String, String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
/// Data about a kind of chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeElement {
pub chat: ChatTypeData,
pub narration: ChatTypeData,
}
/// Data about a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeData {
pub translation_key: String,
pub parameters: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<ChatTypeStyle>,
}
/// The style of a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeStyle {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub bold: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub italic: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub underlined: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub strikethrough: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub obfuscated: Option<bool>,
}
/// Dimension attributes.
#[cfg(feature = "strict_registry")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DimensionTypeElement {
pub ambient_light: f32,
#[serde(with = "Convert")]
pub bed_works: bool,
pub coordinate_scale: f32,
pub effects: ResourceLocation,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_time: Option<u32>,
#[serde(with = "Convert")]
pub has_ceiling: bool,
#[serde(with = "Convert")]
pub has_raids: bool,
#[serde(with = "Convert")]
pub has_skylight: bool,
pub height: u32,
pub infiniburn: ResourceLocation,
pub logical_height: u32,
pub min_y: i32,
pub monster_spawn_block_light_limit: u32,
pub monster_spawn_light_level: MonsterSpawnLightLevel,
#[serde(with = "Convert")]
pub natural: bool,
#[serde(with = "Convert")]
pub piglin_safe: bool,
#[serde(with = "Convert")]
pub respawn_anchor_works: bool,
#[serde(with = "Convert")]
pub ultrawarm: bool,
}
/// Dimension attributes.
#[cfg(not(feature = "strict_registry"))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DimensionTypeElement {
pub height: u32,
pub min_y: i32,
#[serde(flatten)]
pub _extra: HashMap<String, Nbt>,
}
/// The light level at which monsters can spawn.
///
/// This can be either a single minimum value, or a formula with a min and
/// max.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub enum MonsterSpawnLightLevel {
/// A simple minimum value.
Simple(u32),
/// A complex value with a type, minimum, and maximum.
/// Vanilla minecraft only uses one type, "minecraft:uniform".
Complex {
#[serde(rename = "type")]
kind: ResourceLocation,
value: MonsterSpawnLightLevelValues,
},
}
/// The min and max light levels at which monsters can spawn.
///
/// Values are inclusive.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct MonsterSpawnLightLevelValues {
#[serde(rename = "min_inclusive")]
pub min: u32,
#[serde(rename = "max_inclusive")]
pub max: u32,
}
/// Biome attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct WorldTypeElement {
#[serde(with = "Convert")]
pub has_precipitation: bool,
pub temperature: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature_modifier: Option<String>,
pub downfall: f32,
pub effects: BiomeEffects,
}
/// The precipitation of a biome.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub enum BiomePrecipitation {
#[serde(rename = "none")]
None,
#[serde(rename = "rain")]
Rain,
#[serde(rename = "snow")]
Snow,
}
/// The effects of a biome.
///
/// This includes the sky, fog, water, and grass color,
/// as well as music and other sound effects.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeEffects {
pub sky_color: u32,
pub fog_color: u32,
pub water_color: u32,
pub water_fog_color: u32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub foliage_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color_modifier: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub music: Option<BiomeMusic>,
pub mood_sound: BiomeMoodSound,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub additions_sound: Option<AdditionsSound>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub ambient_sound: Option<ResourceLocation>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub particle: Option<BiomeParticle>,
}
/// The music of the biome.
///
/// Some biomes have unique music that only play when inside them.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeMusic {
#[serde(with = "Convert")]
pub replace_current_music: bool,
pub max_delay: u32,
pub min_delay: u32,
pub sound: azalea_registry::SoundEvent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeMoodSound {
pub tick_delay: u32,
pub block_search_extent: u32,
pub offset: f32,
pub sound: azalea_registry::SoundEvent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct AdditionsSound {
pub tick_chance: f32,
pub sound: azalea_registry::SoundEvent,
}
/// Biome particles.
///
/// Some biomes have particles that spawn in the air.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeParticle {
pub probability: f32,
pub options: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TrimPatternElement {
#[serde(flatten)]
pub pattern: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct DamageTypeElement {
pub message_id: String,
pub scaling: String,
pub exhaustion: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub effects: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub death_message_type: Option<String>,
}
// Using a trait because you can't implement methods for
// types you don't own, in this case Option<bool> and bool.
trait Convert: Sized {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer;
fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
// Convert between bool and u8
impl Convert for bool {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(if *self { 1 } else { 0 })
}
fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
convert::<D>(u8::deserialize(deserializer)?)
}
}
// Convert between Option<bool> and u8
impl Convert for Option<bool> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(value) = self {
Convert::serialize(value, serializer)
} else {
serializer.serialize_none()
}
}
fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
where
D: Deserializer<'de>,
{
if let Some(value) = Option::<u8>::deserialize(deserializer)? {
Ok(Some(convert::<D>(value)?))
} else {
Ok(None)
}
}
}
// Deserializing logic here to deduplicate code
fn convert<'de, D>(value: u8) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
match value {
0 => Ok(false),
1 => Ok(true),
other => Err(de::Error::invalid_value(
de::Unexpected::Unsigned(other as u64),
&"zero or one",
)),
}
}
}

View file

@ -0,0 +1,11 @@
use azalea_buf::McBuf;
use azalea_chat::FormattedText;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundResourcePackPacket {
pub url: String,
pub hash: String,
pub required: bool,
pub prompt: Option<FormattedText>,
}

View file

@ -0,0 +1,8 @@
use azalea_buf::McBuf;
use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundUpdateEnabledFeaturesPacket {
pub features: Vec<ResourceLocation>,
}

View file

@ -0,0 +1,73 @@
use azalea_buf::{BufReadError, McBuf, McBufVarReadable, McBufVarWritable};
use azalea_buf::{McBufReadable, McBufWritable};
use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundConfigurationPacket;
use std::io::Cursor;
use std::ops::Deref;
use std::{collections::HashMap, io::Write};
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
pub struct ClientboundUpdateTagsPacket {
pub tags: TagMap,
}
#[derive(Clone, Debug)]
pub struct Tags {
pub name: ResourceLocation,
pub elements: Vec<i32>,
}
#[derive(Clone, Debug)]
pub struct TagMap(pub HashMap<ResourceLocation, Vec<Tags>>);
impl McBufReadable for TagMap {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let length = u32::var_read_from(buf)? as usize;
let mut data = HashMap::with_capacity(length);
for _ in 0..length {
let tag_type = ResourceLocation::read_from(buf)?;
let tags_count = i32::var_read_from(buf)? as usize;
let mut tags_vec = Vec::with_capacity(tags_count);
for _ in 0..tags_count {
let tags = Tags::read_from(buf)?;
tags_vec.push(tags);
}
data.insert(tag_type, tags_vec);
}
Ok(TagMap(data))
}
}
impl McBufWritable for TagMap {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
(self.len() as u32).var_write_into(buf)?;
for (k, v) in &self.0 {
k.write_into(buf)?;
v.write_into(buf)?;
}
Ok(())
}
}
impl McBufReadable for Tags {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
let name = ResourceLocation::read_from(buf)?;
let elements = Vec::<i32>::var_read_from(buf)?;
Ok(Tags { name, elements })
}
}
impl McBufWritable for Tags {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.name.write_into(buf)?;
self.elements.var_write_into(buf)?;
Ok(())
}
}
impl Deref for TagMap {
type Target = HashMap<ResourceLocation, Vec<Tags>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -0,0 +1,39 @@
pub mod clientbound_custom_payload_packet;
pub mod clientbound_disconnect_packet;
pub mod clientbound_finish_configuration_packet;
pub mod clientbound_keep_alive_packet;
pub mod clientbound_ping_packet;
pub mod clientbound_registry_data_packet;
pub mod clientbound_resource_pack_packet;
pub mod clientbound_update_enabled_features_packet;
pub mod clientbound_update_tags_packet;
pub mod serverbound_client_information_packet;
pub mod serverbound_custom_payload_packet;
pub mod serverbound_finish_configuration_packet;
pub mod serverbound_keep_alive_packet;
pub mod serverbound_pong_packet;
pub mod serverbound_resource_pack_packet;
use azalea_protocol_macros::declare_state_packets;
declare_state_packets!(
ConfigurationPacket,
Serverbound => {
0x00: serverbound_client_information_packet::ServerboundClientInformationPacket,
0x01: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x02: serverbound_finish_configuration_packet::ServerboundFinishConfigurationPacket,
0x03: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
0x04: serverbound_pong_packet::ServerboundPongPacket,
0x05: serverbound_resource_pack_packet::ServerboundResourcePackPacket,
},
Clientbound => {
0x00: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket,
0x01: clientbound_disconnect_packet::ClientboundDisconnectPacket,
0x02: clientbound_finish_configuration_packet::ClientboundFinishConfigurationPacket,
0x03: clientbound_keep_alive_packet::ClientboundKeepAlivePacket,
0x04: clientbound_ping_packet::ClientboundPingPacket,
0x05: clientbound_registry_data_packet::ClientboundRegistryDataPacket,
0x06: clientbound_resource_pack_packet::ClientboundResourcePackPacket,
0x07: clientbound_update_enabled_features_packet::ClientboundUpdateEnabledFeaturesPacket,
0x08: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
}
);

View file

@ -0,0 +1,181 @@
use azalea_buf::{McBuf, McBufReadable, McBufWritable};
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundConfigurationPacket;
use bevy_ecs::component::Component;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket, PartialEq, Eq)]
pub struct ServerboundClientInformationPacket {
pub information: ClientInformation,
}
/// A component that contains some of the "settings" for this client that are
/// sent to the server, such as render distance. This is only present on local
/// players.
#[derive(Clone, Debug, McBuf, PartialEq, Eq, Component)]
pub struct ClientInformation {
/// The locale of the client.
pub language: String,
/// The view distance of the client in chunks, same as the render distance
/// in-game.
pub view_distance: u8,
/// The types of chat messages the client wants to receive. Note that many
/// servers ignore this.
pub chat_visibility: ChatVisibility,
/// Whether the messages sent from the server should have colors. Note that
/// many servers ignore this and always send colored messages.
pub chat_colors: bool,
pub model_customization: ModelCustomization,
pub main_hand: HumanoidArm,
pub text_filtering_enabled: bool,
/// Whether the client should show up as "Anonymous Player" in the server
/// list.
pub allows_listing: bool,
}
impl Default for ClientInformation {
fn default() -> Self {
Self {
language: "en_us".to_string(),
view_distance: 8,
chat_visibility: ChatVisibility::default(),
chat_colors: true,
model_customization: ModelCustomization::default(),
main_hand: HumanoidArm::Right,
text_filtering_enabled: false,
allows_listing: false,
}
}
}
#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ChatVisibility {
/// All chat messages should be sent to the client.
#[default]
Full = 0,
/// Chat messages from other players should be not sent to the client, only
/// messages from the server like "Player joined the game" should be sent.
System = 1,
/// No chat messages should be sent to the client.
Hidden = 2,
}
#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum HumanoidArm {
Left = 0,
#[default]
Right = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModelCustomization {
pub cape: bool,
pub jacket: bool,
pub left_sleeve: bool,
pub right_sleeve: bool,
pub left_pants: bool,
pub right_pants: bool,
pub hat: bool,
}
impl Default for ModelCustomization {
fn default() -> Self {
Self {
cape: true,
jacket: true,
left_sleeve: true,
right_sleeve: true,
left_pants: true,
right_pants: true,
hat: true,
}
}
}
impl McBufReadable for ModelCustomization {
fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let set = FixedBitSet::<7>::read_from(buf)?;
Ok(Self {
cape: set.index(0),
jacket: set.index(1),
left_sleeve: set.index(2),
right_sleeve: set.index(3),
left_pants: set.index(4),
right_pants: set.index(5),
hat: set.index(6),
})
}
}
impl McBufWritable for ModelCustomization {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
let mut set = FixedBitSet::<7>::new();
if self.cape {
set.set(0);
}
if self.jacket {
set.set(1);
}
if self.left_sleeve {
set.set(2);
}
if self.right_sleeve {
set.set(3);
}
if self.left_pants {
set.set(4);
}
if self.right_pants {
set.set(5);
}
if self.hat {
set.set(6);
}
set.write_into(buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_client_information_packet() {
{
let data = ClientInformation::default();
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data = ClientInformation::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
{
let data = ClientInformation {
language: "en_gb".to_string(),
view_distance: 24,
chat_visibility: ChatVisibility::Hidden,
chat_colors: false,
model_customization: ModelCustomization {
cape: false,
jacket: false,
left_sleeve: true,
right_sleeve: false,
left_pants: true,
right_pants: false,
hat: true,
},
main_hand: HumanoidArm::Left,
text_filtering_enabled: true,
allows_listing: true,
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data = ClientInformation::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
}
}

View file

@ -0,0 +1,10 @@
use azalea_buf::McBuf;
use azalea_buf::UnsizedByteArray;
use azalea_core::ResourceLocation;
use azalea_protocol_macros::ServerboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)]
pub struct ServerboundCustomPayloadPacket {
pub identifier: ResourceLocation,
pub data: UnsizedByteArray,
}

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)]
pub struct ServerboundFinishConfigurationPacket {}

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)]
pub struct ServerboundKeepAlivePacket {
pub id: u64,
}

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)]
pub struct ServerboundPongPacket {
pub id: u32,
}

View file

@ -0,0 +1,15 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundConfigurationPacket;
#[derive(Clone, Debug, McBuf, ServerboundConfigurationPacket)]
pub struct ServerboundResourcePackPacket {
pub action: Action,
}
#[derive(McBuf, Clone, Copy, Debug)]
pub enum Action {
SuccessfullyLoaded = 0,
Declined = 1,
FailedDownload = 2,
Accepted = 3,
}

View file

@ -0,0 +1,8 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundChunkBatchFinishedPacket {
#[var]
pub batch_size: u32,
}

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundChunkBatchStartPacket {}

View file

@ -1,8 +1,8 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_core::ChunkPos;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundForgetLevelChunkPacket { pub struct ClientboundForgetLevelChunkPacket {
pub x: i32, pub pos: ChunkPos,
pub z: i32,
} }

View file

@ -1,6 +1,7 @@
use self::registry::RegistryHolder; use crate::packets::common::CommonPlayerSpawnInfo;
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation}; use azalea_core::ResourceLocation;
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
/// The first packet sent by the server to the client after login. /// The first packet sent by the server to the client after login.
@ -11,13 +12,7 @@ use azalea_protocol_macros::ClientboundGamePacket;
pub struct ClientboundLoginPacket { pub struct ClientboundLoginPacket {
pub player_id: u32, pub player_id: u32,
pub hardcore: bool, pub hardcore: bool,
pub game_type: GameMode,
pub previous_game_type: OptionalGameType,
pub levels: Vec<ResourceLocation>, pub levels: Vec<ResourceLocation>,
pub registry_holder: RegistryHolder,
pub dimension_type: ResourceLocation,
pub dimension: ResourceLocation,
pub seed: i64,
#[var] #[var]
pub max_players: i32, pub max_players: i32,
#[var] #[var]
@ -26,500 +21,6 @@ pub struct ClientboundLoginPacket {
pub simulation_distance: u32, pub simulation_distance: u32,
pub reduced_debug_info: bool, pub reduced_debug_info: bool,
pub show_death_screen: bool, pub show_death_screen: bool,
pub is_debug: bool, pub do_limited_crafting: bool,
pub is_flat: bool, pub common: CommonPlayerSpawnInfo,
pub last_death_location: Option<GlobalPos>,
#[var]
pub portal_cooldown: u32,
}
pub mod registry {
//! [ClientboundLoginPacket](super::ClientboundLoginPacket) Registry
//! Structures
//!
//! This module contains the structures used to represent the registry
//! sent to the client upon login. This contains a lot of information about
//! the game, including the types of chat messages, dimensions, and
//! biomes.
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::ResourceLocation;
use azalea_nbt::Nbt;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{collections::HashMap, io::Cursor};
/// The base of the registry.
///
/// This is the registry that is sent to the client upon login.
///
/// As a tag, it is a compound tag that only contains a single compound tag.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct RegistryHolder {
#[serde(rename = "")]
pub root: RegistryRoot,
}
impl TryFrom<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_from(value: Nbt) -> Result<Self, Self::Error> {
serde_json::from_value(serde_json::to_value(value)?)
}
}
impl TryInto<Nbt> for RegistryHolder {
type Error = serde_json::Error;
fn try_into(self) -> Result<Nbt, Self::Error> {
serde_json::from_value(serde_json::to_value(self)?)
}
}
impl McBufReadable for RegistryHolder {
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
RegistryHolder::try_from(Nbt::read_from(buf)?)
.map_err(|e| BufReadError::Deserialization { source: e })
}
}
impl McBufWritable for RegistryHolder {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
TryInto::<Nbt>::try_into(self.clone())?.write_into(buf)
}
}
/// The main part of the registry.
///
/// The only field of [`RegistryHolder`].
/// Contains information from the server about chat, dimensions,
/// and world generation.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct RegistryRoot {
#[cfg(feature = "strict_registry")]
#[serde(rename = "minecraft:trim_material")]
pub trim_material: RegistryType<TrimMaterialElement>,
#[cfg(not(feature = "strict_registry"))]
#[serde(default, rename = "minecraft:trim_material")]
pub trim_material: Nbt,
#[cfg(feature = "strict_registry")]
#[serde(rename = "minecraft:chat_type")]
pub chat_type: RegistryType<ChatTypeElement>,
#[cfg(not(feature = "strict_registry"))]
#[serde(default, rename = "minecraft:chat_type")]
pub chat_type: Nbt,
#[serde(rename = "minecraft:dimension_type")]
pub dimension_type: RegistryType<DimensionTypeElement>,
#[cfg(feature = "strict_registry")]
#[serde(rename = "minecraft:worldgen/biome")]
pub world_type: RegistryType<WorldTypeElement>,
#[cfg(not(feature = "strict_registry"))]
#[serde(default, rename = "minecraft:worldgen/biome")]
pub world_type: Nbt,
#[cfg(feature = "strict_registry")]
#[serde(rename = "minecraft:trim_pattern")]
pub trim_pattern: RegistryType<TrimPatternElement>,
#[cfg(not(feature = "strict_registry"))]
#[serde(default, rename = "minecraft:trim_pattern")]
pub trim_pattern: Nbt,
#[cfg(feature = "strict_registry")]
#[serde(rename = "minecraft:damage_type")]
pub damage_type: RegistryType<DamageTypeElement>,
#[cfg(not(feature = "strict_registry"))]
#[serde(default, rename = "minecraft:damage_type")]
pub damage_type: Nbt,
}
/// A collection of values for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct RegistryType<T> {
#[serde(rename = "type")]
pub kind: ResourceLocation,
pub value: Vec<TypeValue<T>>,
}
/// A value for a certain type of registry data.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TypeValue<T> {
pub id: u32,
pub name: ResourceLocation,
pub element: T,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TrimMaterialElement {
pub asset_name: String,
pub ingredient: ResourceLocation,
pub item_model_index: f32,
pub override_armor_materials: HashMap<String, String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
/// Data about a kind of chat message
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeElement {
pub chat: ChatTypeData,
pub narration: ChatTypeData,
}
/// Data about a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeData {
pub translation_key: String,
pub parameters: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<ChatTypeStyle>,
}
/// The style of a chat message.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct ChatTypeStyle {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub bold: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub italic: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub underlined: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub strikethrough: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "Convert")]
pub obfuscated: Option<bool>,
}
/// Dimension attributes.
#[cfg(feature = "strict_registry")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DimensionTypeElement {
pub ambient_light: f32,
#[serde(with = "Convert")]
pub bed_works: bool,
pub coordinate_scale: f32,
pub effects: ResourceLocation,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_time: Option<u32>,
#[serde(with = "Convert")]
pub has_ceiling: bool,
#[serde(with = "Convert")]
pub has_raids: bool,
#[serde(with = "Convert")]
pub has_skylight: bool,
pub height: u32,
pub infiniburn: ResourceLocation,
pub logical_height: u32,
pub min_y: i32,
pub monster_spawn_block_light_limit: u32,
pub monster_spawn_light_level: MonsterSpawnLightLevel,
#[serde(with = "Convert")]
pub natural: bool,
#[serde(with = "Convert")]
pub piglin_safe: bool,
#[serde(with = "Convert")]
pub respawn_anchor_works: bool,
#[serde(with = "Convert")]
pub ultrawarm: bool,
}
/// Dimension attributes.
#[cfg(not(feature = "strict_registry"))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DimensionTypeElement {
pub height: u32,
pub min_y: i32,
#[serde(flatten)]
pub _extra: HashMap<String, Nbt>,
}
/// The light level at which monsters can spawn.
///
/// This can be either a single minimum value, or a formula with a min and
/// max.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub enum MonsterSpawnLightLevel {
/// A simple minimum value.
Simple(u32),
/// A complex value with a type, minimum, and maximum.
/// Vanilla minecraft only uses one type, "minecraft:uniform".
Complex {
#[serde(rename = "type")]
kind: ResourceLocation,
value: MonsterSpawnLightLevelValues,
},
}
/// The min and max light levels at which monsters can spawn.
///
/// Values are inclusive.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct MonsterSpawnLightLevelValues {
#[serde(rename = "min_inclusive")]
pub min: u32,
#[serde(rename = "max_inclusive")]
pub max: u32,
}
/// Biome attributes.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct WorldTypeElement {
#[serde(with = "Convert")]
pub has_precipitation: bool,
pub temperature: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature_modifier: Option<String>,
pub downfall: f32,
pub effects: BiomeEffects,
}
/// The precipitation of a biome.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub enum BiomePrecipitation {
#[serde(rename = "none")]
None,
#[serde(rename = "rain")]
Rain,
#[serde(rename = "snow")]
Snow,
}
/// The effects of a biome.
///
/// This includes the sky, fog, water, and grass color,
/// as well as music and other sound effects.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeEffects {
pub sky_color: u32,
pub fog_color: u32,
pub water_color: u32,
pub water_fog_color: u32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub foliage_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub grass_color_modifier: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub music: Option<BiomeMusic>,
pub mood_sound: BiomeMoodSound,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub additions_sound: Option<AdditionsSound>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub ambient_sound: Option<ResourceLocation>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub particle: Option<BiomeParticle>,
}
/// The music of the biome.
///
/// Some biomes have unique music that only play when inside them.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeMusic {
#[serde(with = "Convert")]
pub replace_current_music: bool,
pub max_delay: u32,
pub min_delay: u32,
pub sound: azalea_registry::SoundEvent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeMoodSound {
pub tick_delay: u32,
pub block_search_extent: u32,
pub offset: f32,
pub sound: azalea_registry::SoundEvent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct AdditionsSound {
pub tick_chance: f32,
pub sound: azalea_registry::SoundEvent,
}
/// Biome particles.
///
/// Some biomes have particles that spawn in the air.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct BiomeParticle {
pub probability: f32,
pub options: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct TrimPatternElement {
#[serde(flatten)]
pub pattern: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
pub struct DamageTypeElement {
pub message_id: String,
pub scaling: String,
pub exhaustion: f32,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub effects: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub death_message_type: Option<String>,
}
// Using a trait because you can't implement methods for
// types you don't own, in this case Option<bool> and bool.
trait Convert: Sized {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer;
fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
// Convert between bool and u8
impl Convert for bool {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(if *self { 1 } else { 0 })
}
fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
convert::<D>(u8::deserialize(deserializer)?)
}
}
// Convert between Option<bool> and u8
impl Convert for Option<bool> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(value) = self {
Convert::serialize(value, serializer)
} else {
serializer.serialize_none()
}
}
fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
where
D: Deserializer<'de>,
{
if let Some(value) = Option::<u8>::deserialize(deserializer)? {
Ok(Some(convert::<D>(value)?))
} else {
Ok(None)
}
}
}
// Deserializing logic here to deduplicate code
fn convert<'de, D>(value: u8) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
match value {
0 => Ok(false),
1 => Ok(true),
other => Err(de::Error::invalid_value(
de::Unexpected::Unsigned(other as u64),
&"zero or one",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::registry::{DimensionTypeElement, RegistryHolder, RegistryRoot, RegistryType};
use azalea_core::ResourceLocation;
use azalea_nbt::Nbt;
#[test]
fn test_convert() {
// Do NOT use Nbt::End, they should be Nbt::Compound.
// This is just for testing.
let registry = RegistryHolder {
root: RegistryRoot {
trim_material: Nbt::End,
chat_type: Nbt::End,
dimension_type: RegistryType::<DimensionTypeElement> {
kind: ResourceLocation::new("minecraft:dimension_type"),
value: Vec::new(),
},
world_type: Nbt::End,
trim_pattern: Nbt::End,
damage_type: Nbt::End,
},
};
let tag: Nbt = registry.try_into().unwrap();
let root = tag
.as_compound()
.unwrap()
.get("")
.unwrap()
.as_compound()
.unwrap();
let dimension = root
.get("minecraft:dimension_type")
.unwrap()
.as_compound()
.unwrap();
let dimension_type = dimension.get("type").unwrap().as_string().unwrap().as_str();
assert!(dimension_type == "minecraft:dimension_type");
}
} }

View file

@ -3,5 +3,6 @@ use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundPingPacket { pub struct ClientboundPingPacket {
#[var]
pub id: u32, pub id: u32,
} }

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundPongResponsePacket {
pub time: u64,
}

View file

@ -1,18 +1,10 @@
use azalea_buf::McBuf; use azalea_buf::McBuf;
use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation};
use azalea_protocol_macros::ClientboundGamePacket; use azalea_protocol_macros::ClientboundGamePacket;
use crate::packets::common::CommonPlayerSpawnInfo;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundRespawnPacket { pub struct ClientboundRespawnPacket {
pub dimension_type: ResourceLocation, pub common: CommonPlayerSpawnInfo,
pub dimension: ResourceLocation,
pub seed: u64,
pub game_type: GameMode,
pub previous_game_type: OptionalGameType,
pub is_debug: bool,
pub is_flat: bool,
pub data_to_keep: u8, pub data_to_keep: u8,
pub last_death_location: Option<GlobalPos>,
#[var]
pub portal_cooldown: u32,
} }

View file

@ -3,6 +3,29 @@ use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundSetDisplayObjectivePacket { pub struct ClientboundSetDisplayObjectivePacket {
pub slot: u8, pub slot: DisplaySlot,
pub objective_name: String, pub objective_name: String,
} }
#[derive(Clone, Debug, Copy, McBuf)]
pub enum DisplaySlot {
List = 0,
Sidebar,
BelowName,
TeamBlack,
TeamDarkBlue,
TeamDarkGreen,
TeamDarkAqua,
TeamDarkRed,
TeamDarkPurple,
TeamGold,
TeamGray,
TeamDarkGray,
TeamBlue,
TeamGreen,
TeamAqua,
TeamRed,
TeamLightPurple,
TeamYellow,
TeamWhite,
}

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ClientboundGamePacket;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundStartConfigurationPacket {}

View file

@ -9,7 +9,7 @@ use std::io::Cursor;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] #[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundUpdateAdvancementsPacket { pub struct ClientboundUpdateAdvancementsPacket {
pub reset: bool, pub reset: bool,
pub added: HashMap<ResourceLocation, Advancement>, pub added: Vec<AdvancementHolder>,
pub removed: Vec<ResourceLocation>, pub removed: Vec<ResourceLocation>,
pub progress: HashMap<ResourceLocation, AdvancementProgress>, pub progress: HashMap<ResourceLocation, AdvancementProgress>,
} }
@ -18,7 +18,6 @@ pub struct ClientboundUpdateAdvancementsPacket {
pub struct Advancement { pub struct Advancement {
pub parent_id: Option<ResourceLocation>, pub parent_id: Option<ResourceLocation>,
pub display: Option<DisplayInfo>, pub display: Option<DisplayInfo>,
pub criteria: HashMap<ResourceLocation, Criterion>,
pub requirements: Vec<Vec<String>>, pub requirements: Vec<Vec<String>>,
pub sends_telemetry_event: bool, pub sends_telemetry_event: bool,
} }
@ -103,15 +102,17 @@ pub enum FrameType {
Goal = 2, Goal = 2,
} }
// nothing is written here pub type AdvancementProgress = HashMap<String, CriterionProgress>;
#[derive(Clone, Debug, McBuf)]
pub struct Criterion {}
pub type AdvancementProgress = HashMap<ResourceLocation, CriterionProgress>;
#[derive(Clone, Debug, McBuf)] #[derive(Clone, Debug, McBuf)]
pub struct CriterionProgress { pub struct CriterionProgress {
date: Option<u64>, pub date: Option<u64>,
}
#[derive(Clone, Debug, McBuf)]
pub struct AdvancementHolder {
pub id: ResourceLocation,
pub value: Advancement,
} }
#[cfg(test)] #[cfg(test)]
@ -125,9 +126,9 @@ mod tests {
fn test() { fn test() {
let packet = ClientboundUpdateAdvancementsPacket { let packet = ClientboundUpdateAdvancementsPacket {
reset: true, reset: true,
added: [( added: [AdvancementHolder {
ResourceLocation::new("minecraft:test"), id: ResourceLocation::new("minecraft:test"),
Advancement { value: Advancement {
parent_id: None, parent_id: None,
display: Some(DisplayInfo { display: Some(DisplayInfo {
title: FormattedText::from("title".to_string()), title: FormattedText::from("title".to_string()),
@ -140,18 +141,17 @@ mod tests {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
}), }),
criteria: HashMap::new(),
requirements: Vec::new(), requirements: Vec::new(),
sends_telemetry_event: false, sends_telemetry_event: false,
}, },
)] }]
.into_iter() .into_iter()
.collect(), .collect(),
removed: vec![ResourceLocation::new("minecraft:test2")], removed: vec![ResourceLocation::new("minecraft:test2")],
progress: [( progress: [(
ResourceLocation::new("minecraft:test3"), ResourceLocation::new("minecraft:test3"),
[( [(
ResourceLocation::new("minecraft:test4"), "minecraft:test4".to_string(),
CriterionProgress { CriterionProgress {
date: Some(123456789), date: Some(123456789),
}, },
@ -173,12 +173,26 @@ mod tests {
let advancement = packet let advancement = packet
.added .added
.get(&ResourceLocation::new("minecraft:test")) .into_iter()
.find_map(|a| {
if a.id == ResourceLocation::new("minecraft:test") {
Some(a.value)
} else {
None
}
})
.unwrap() .unwrap()
.clone(); .clone();
let read_advancement = read_packet let read_advancement = read_packet
.added .added
.get(&ResourceLocation::new("minecraft:test")) .into_iter()
.find_map(|a| {
if a.id == ResourceLocation::new("minecraft:test") {
Some(a.value)
} else {
None
}
})
.unwrap() .unwrap()
.clone(); .clone();
assert_eq!(advancement.parent_id, read_advancement.parent_id); assert_eq!(advancement.parent_id, read_advancement.parent_id);

View file

@ -1,6 +1,5 @@
pub mod clientbound_add_entity_packet; pub mod clientbound_add_entity_packet;
pub mod clientbound_add_experience_orb_packet; pub mod clientbound_add_experience_orb_packet;
pub mod clientbound_add_player_packet;
pub mod clientbound_animate_packet; pub mod clientbound_animate_packet;
pub mod clientbound_award_stats_packet; pub mod clientbound_award_stats_packet;
pub mod clientbound_block_changed_ack_packet; pub mod clientbound_block_changed_ack_packet;
@ -11,6 +10,8 @@ pub mod clientbound_block_update_packet;
pub mod clientbound_boss_event_packet; pub mod clientbound_boss_event_packet;
pub mod clientbound_bundle_packet; pub mod clientbound_bundle_packet;
pub mod clientbound_change_difficulty_packet; pub mod clientbound_change_difficulty_packet;
pub mod clientbound_chunk_batch_finished_packet;
pub mod clientbound_chunk_batch_start_packet;
pub mod clientbound_chunks_biomes_packet; pub mod clientbound_chunks_biomes_packet;
pub mod clientbound_clear_titles_packet; pub mod clientbound_clear_titles_packet;
pub mod clientbound_command_suggestions_packet; pub mod clientbound_command_suggestions_packet;
@ -59,6 +60,7 @@ pub mod clientbound_player_info_remove_packet;
pub mod clientbound_player_info_update_packet; pub mod clientbound_player_info_update_packet;
pub mod clientbound_player_look_at_packet; pub mod clientbound_player_look_at_packet;
pub mod clientbound_player_position_packet; pub mod clientbound_player_position_packet;
pub mod clientbound_pong_response_packet;
pub mod clientbound_recipe_packet; pub mod clientbound_recipe_packet;
pub mod clientbound_remove_entities_packet; pub mod clientbound_remove_entities_packet;
pub mod clientbound_remove_mob_effect_packet; pub mod clientbound_remove_mob_effect_packet;
@ -97,6 +99,7 @@ pub mod clientbound_set_title_text_packet;
pub mod clientbound_set_titles_animation_packet; pub mod clientbound_set_titles_animation_packet;
pub mod clientbound_sound_entity_packet; pub mod clientbound_sound_entity_packet;
pub mod clientbound_sound_packet; pub mod clientbound_sound_packet;
pub mod clientbound_start_configuration_packet;
pub mod clientbound_stop_sound_packet; pub mod clientbound_stop_sound_packet;
pub mod clientbound_system_chat_packet; pub mod clientbound_system_chat_packet;
pub mod clientbound_tab_list_packet; pub mod clientbound_tab_list_packet;
@ -105,7 +108,6 @@ pub mod clientbound_take_item_entity_packet;
pub mod clientbound_teleport_entity_packet; pub mod clientbound_teleport_entity_packet;
pub mod clientbound_update_advancements_packet; pub mod clientbound_update_advancements_packet;
pub mod clientbound_update_attributes_packet; pub mod clientbound_update_attributes_packet;
pub mod clientbound_update_enabled_features_packet;
pub mod clientbound_update_mob_effect_packet; pub mod clientbound_update_mob_effect_packet;
pub mod clientbound_update_recipes_packet; pub mod clientbound_update_recipes_packet;
pub mod clientbound_update_tags_packet; pub mod clientbound_update_tags_packet;
@ -116,9 +118,11 @@ pub mod serverbound_chat_ack_packet;
pub mod serverbound_chat_command_packet; pub mod serverbound_chat_command_packet;
pub mod serverbound_chat_packet; pub mod serverbound_chat_packet;
pub mod serverbound_chat_session_update_packet; pub mod serverbound_chat_session_update_packet;
pub mod serverbound_chunk_batch_received_packet;
pub mod serverbound_client_command_packet; pub mod serverbound_client_command_packet;
pub mod serverbound_client_information_packet; pub mod serverbound_client_information_packet;
pub mod serverbound_command_suggestion_packet; pub mod serverbound_command_suggestion_packet;
pub mod serverbound_configuration_acknowledged_packet;
pub mod serverbound_container_button_click_packet; pub mod serverbound_container_button_click_packet;
pub mod serverbound_container_click_packet; pub mod serverbound_container_click_packet;
pub mod serverbound_container_close_packet; pub mod serverbound_container_close_packet;
@ -136,6 +140,7 @@ pub mod serverbound_move_player_status_only_packet;
pub mod serverbound_move_vehicle_packet; pub mod serverbound_move_vehicle_packet;
pub mod serverbound_paddle_boat_packet; pub mod serverbound_paddle_boat_packet;
pub mod serverbound_pick_item_packet; pub mod serverbound_pick_item_packet;
pub mod serverbound_ping_request_packet;
pub mod serverbound_place_recipe_packet; pub mod serverbound_place_recipe_packet;
pub mod serverbound_player_abilities_packet; pub mod serverbound_player_abilities_packet;
pub mod serverbound_player_action_packet; pub mod serverbound_player_action_packet;
@ -173,162 +178,167 @@ declare_state_packets!(
0x04: serverbound_chat_command_packet::ServerboundChatCommandPacket, 0x04: serverbound_chat_command_packet::ServerboundChatCommandPacket,
0x05: serverbound_chat_packet::ServerboundChatPacket, 0x05: serverbound_chat_packet::ServerboundChatPacket,
0x06: serverbound_chat_session_update_packet::ServerboundChatSessionUpdatePacket, 0x06: serverbound_chat_session_update_packet::ServerboundChatSessionUpdatePacket,
0x07: serverbound_client_command_packet::ServerboundClientCommandPacket, 0x07: serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket,
0x08: serverbound_client_information_packet::ServerboundClientInformationPacket, 0x08: serverbound_client_command_packet::ServerboundClientCommandPacket,
0x09: serverbound_command_suggestion_packet::ServerboundCommandSuggestionPacket, 0x09: serverbound_client_information_packet::ServerboundClientInformationPacket,
0x0a: serverbound_container_button_click_packet::ServerboundContainerButtonClickPacket, 0x0a: serverbound_command_suggestion_packet::ServerboundCommandSuggestionPacket,
0x0b: serverbound_container_click_packet::ServerboundContainerClickPacket, 0x0b: serverbound_configuration_acknowledged_packet::ServerboundConfigurationAcknowledgedPacket,
0x0c: serverbound_container_close_packet::ServerboundContainerClosePacket, 0x0c: serverbound_container_button_click_packet::ServerboundContainerButtonClickPacket,
0x0d: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, 0x0d: serverbound_container_click_packet::ServerboundContainerClickPacket,
0x0e: serverbound_edit_book_packet::ServerboundEditBookPacket, 0x0e: serverbound_container_close_packet::ServerboundContainerClosePacket,
0x0f: serverbound_entity_tag_query::ServerboundEntityTagQuery, 0x0f: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x10: serverbound_interact_packet::ServerboundInteractPacket, 0x10: serverbound_edit_book_packet::ServerboundEditBookPacket,
0x11: serverbound_jigsaw_generate_packet::ServerboundJigsawGeneratePacket, 0x11: serverbound_entity_tag_query::ServerboundEntityTagQuery,
0x12: serverbound_keep_alive_packet::ServerboundKeepAlivePacket, 0x12: serverbound_interact_packet::ServerboundInteractPacket,
0x13: serverbound_lock_difficulty_packet::ServerboundLockDifficultyPacket, 0x13: serverbound_jigsaw_generate_packet::ServerboundJigsawGeneratePacket,
0x14: serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, 0x14: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
0x15: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, 0x15: serverbound_lock_difficulty_packet::ServerboundLockDifficultyPacket,
0x16: serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket, 0x16: serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
0x17: serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket, 0x17: serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
0x18: serverbound_move_vehicle_packet::ServerboundMoveVehiclePacket, 0x18: serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
0x19: serverbound_paddle_boat_packet::ServerboundPaddleBoatPacket, 0x19: serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
0x1a: serverbound_pick_item_packet::ServerboundPickItemPacket, 0x1a: serverbound_move_vehicle_packet::ServerboundMoveVehiclePacket,
0x1b: serverbound_place_recipe_packet::ServerboundPlaceRecipePacket, 0x1b: serverbound_paddle_boat_packet::ServerboundPaddleBoatPacket,
0x1c: serverbound_player_abilities_packet::ServerboundPlayerAbilitiesPacket, 0x1c: serverbound_pick_item_packet::ServerboundPickItemPacket,
0x1d: serverbound_player_action_packet::ServerboundPlayerActionPacket, 0x1d: serverbound_ping_request_packet::ServerboundPingRequestPacket,
0x1e: serverbound_player_command_packet::ServerboundPlayerCommandPacket, 0x1e: serverbound_place_recipe_packet::ServerboundPlaceRecipePacket,
0x1f: serverbound_player_input_packet::ServerboundPlayerInputPacket, 0x1f: serverbound_player_abilities_packet::ServerboundPlayerAbilitiesPacket,
0x20: serverbound_pong_packet::ServerboundPongPacket, 0x20: serverbound_player_action_packet::ServerboundPlayerActionPacket,
0x21: serverbound_recipe_book_change_settings_packet::ServerboundRecipeBookChangeSettingsPacket, 0x21: serverbound_player_command_packet::ServerboundPlayerCommandPacket,
0x22: serverbound_recipe_book_seen_recipe_packet::ServerboundRecipeBookSeenRecipePacket, 0x22: serverbound_player_input_packet::ServerboundPlayerInputPacket,
0x23: serverbound_rename_item_packet::ServerboundRenameItemPacket, 0x23: serverbound_pong_packet::ServerboundPongPacket,
0x24: serverbound_resource_pack_packet::ServerboundResourcePackPacket, 0x24: serverbound_recipe_book_change_settings_packet::ServerboundRecipeBookChangeSettingsPacket,
0x25: serverbound_seen_advancements_packet::ServerboundSeenAdvancementsPacket, 0x25: serverbound_recipe_book_seen_recipe_packet::ServerboundRecipeBookSeenRecipePacket,
0x26: serverbound_select_trade_packet::ServerboundSelectTradePacket, 0x26: serverbound_rename_item_packet::ServerboundRenameItemPacket,
0x27: serverbound_set_beacon_packet::ServerboundSetBeaconPacket, 0x27: serverbound_resource_pack_packet::ServerboundResourcePackPacket,
0x28: serverbound_set_carried_item_packet::ServerboundSetCarriedItemPacket, 0x28: serverbound_seen_advancements_packet::ServerboundSeenAdvancementsPacket,
0x29: serverbound_set_command_block_packet::ServerboundSetCommandBlockPacket, 0x29: serverbound_select_trade_packet::ServerboundSelectTradePacket,
0x2a: serverbound_set_command_minecart_packet::ServerboundSetCommandMinecartPacket, 0x2a: serverbound_set_beacon_packet::ServerboundSetBeaconPacket,
0x2b: serverbound_set_creative_mode_slot_packet::ServerboundSetCreativeModeSlotPacket, 0x2b: serverbound_set_carried_item_packet::ServerboundSetCarriedItemPacket,
0x2c: serverbound_set_jigsaw_block_packet::ServerboundSetJigsawBlockPacket, 0x2c: serverbound_set_command_block_packet::ServerboundSetCommandBlockPacket,
0x2d: serverbound_set_structure_block_packet::ServerboundSetStructureBlockPacket, 0x2d: serverbound_set_command_minecart_packet::ServerboundSetCommandMinecartPacket,
0x2e: serverbound_sign_update_packet::ServerboundSignUpdatePacket, 0x2e: serverbound_set_creative_mode_slot_packet::ServerboundSetCreativeModeSlotPacket,
0x2f: serverbound_swing_packet::ServerboundSwingPacket, 0x2f: serverbound_set_jigsaw_block_packet::ServerboundSetJigsawBlockPacket,
0x30: serverbound_teleport_to_entity_packet::ServerboundTeleportToEntityPacket, 0x30: serverbound_set_structure_block_packet::ServerboundSetStructureBlockPacket,
0x31: serverbound_use_item_on_packet::ServerboundUseItemOnPacket, 0x31: serverbound_sign_update_packet::ServerboundSignUpdatePacket,
0x32: serverbound_use_item_packet::ServerboundUseItemPacket, 0x32: serverbound_swing_packet::ServerboundSwingPacket,
0x33: serverbound_teleport_to_entity_packet::ServerboundTeleportToEntityPacket,
0x34: serverbound_use_item_on_packet::ServerboundUseItemOnPacket,
0x35: serverbound_use_item_packet::ServerboundUseItemPacket,
}, },
Clientbound => { Clientbound => {
0x00: clientbound_bundle_packet::ClientboundBundlePacket, 0x00: clientbound_bundle_packet::ClientboundBundlePacket,
0x01: clientbound_add_entity_packet::ClientboundAddEntityPacket, 0x01: clientbound_add_entity_packet::ClientboundAddEntityPacket,
0x02: clientbound_add_experience_orb_packet::ClientboundAddExperienceOrbPacket, 0x02: clientbound_add_experience_orb_packet::ClientboundAddExperienceOrbPacket,
0x03: clientbound_add_player_packet::ClientboundAddPlayerPacket, 0x03: clientbound_animate_packet::ClientboundAnimatePacket,
0x04: clientbound_animate_packet::ClientboundAnimatePacket, 0x04: clientbound_award_stats_packet::ClientboundAwardStatsPacket,
0x05: clientbound_award_stats_packet::ClientboundAwardStatsPacket, 0x05: clientbound_block_changed_ack_packet::ClientboundBlockChangedAckPacket,
0x06: clientbound_block_changed_ack_packet::ClientboundBlockChangedAckPacket, 0x06: clientbound_block_destruction_packet::ClientboundBlockDestructionPacket,
0x07: clientbound_block_destruction_packet::ClientboundBlockDestructionPacket, 0x07: clientbound_block_entity_data_packet::ClientboundBlockEntityDataPacket,
0x08: clientbound_block_entity_data_packet::ClientboundBlockEntityDataPacket, 0x08: clientbound_block_event_packet::ClientboundBlockEventPacket,
0x09: clientbound_block_event_packet::ClientboundBlockEventPacket, 0x09: clientbound_block_update_packet::ClientboundBlockUpdatePacket,
0x0a: clientbound_block_update_packet::ClientboundBlockUpdatePacket, 0x0a: clientbound_boss_event_packet::ClientboundBossEventPacket,
0x0b: clientbound_boss_event_packet::ClientboundBossEventPacket, 0x0b: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
0x0c: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, 0x0c: clientbound_chunk_batch_finished_packet::ClientboundChunkBatchFinishedPacket,
0x0d: clientbound_chunks_biomes_packet::ClientboundChunksBiomesPacket, 0x0d: clientbound_chunk_batch_start_packet::ClientboundChunkBatchStartPacket,
0x0e: clientbound_clear_titles_packet::ClientboundClearTitlesPacket, 0x0e: clientbound_chunks_biomes_packet::ClientboundChunksBiomesPacket,
0x0f: clientbound_command_suggestions_packet::ClientboundCommandSuggestionsPacket, 0x0f: clientbound_clear_titles_packet::ClientboundClearTitlesPacket,
0x10: clientbound_commands_packet::ClientboundCommandsPacket, 0x10: clientbound_command_suggestions_packet::ClientboundCommandSuggestionsPacket,
0x11: clientbound_container_close_packet::ClientboundContainerClosePacket, 0x11: clientbound_commands_packet::ClientboundCommandsPacket,
0x12: clientbound_container_set_content_packet::ClientboundContainerSetContentPacket, 0x12: clientbound_container_close_packet::ClientboundContainerClosePacket,
0x13: clientbound_container_set_data_packet::ClientboundContainerSetDataPacket, 0x13: clientbound_container_set_content_packet::ClientboundContainerSetContentPacket,
0x14: clientbound_container_set_slot_packet::ClientboundContainerSetSlotPacket, 0x14: clientbound_container_set_data_packet::ClientboundContainerSetDataPacket,
0x15: clientbound_cooldown_packet::ClientboundCooldownPacket, 0x15: clientbound_container_set_slot_packet::ClientboundContainerSetSlotPacket,
0x16: clientbound_custom_chat_completions_packet::ClientboundCustomChatCompletionsPacket, 0x16: clientbound_cooldown_packet::ClientboundCooldownPacket,
0x17: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket, 0x17: clientbound_custom_chat_completions_packet::ClientboundCustomChatCompletionsPacket,
0x18: clientbound_damage_event_packet::ClientboundDamageEventPacket, 0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket,
0x19: clientbound_delete_chat_packet::ClientboundDeleteChatPacket, 0x19: clientbound_damage_event_packet::ClientboundDamageEventPacket,
0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket, 0x1a: clientbound_delete_chat_packet::ClientboundDeleteChatPacket,
0x1b: clientbound_disguised_chat_packet::ClientboundDisguisedChatPacket, 0x1b: clientbound_disconnect_packet::ClientboundDisconnectPacket,
0x1c: clientbound_entity_event_packet::ClientboundEntityEventPacket, 0x1c: clientbound_disguised_chat_packet::ClientboundDisguisedChatPacket,
0x1d: clientbound_explode_packet::ClientboundExplodePacket, 0x1d: clientbound_entity_event_packet::ClientboundEntityEventPacket,
0x1e: clientbound_forget_level_chunk_packet::ClientboundForgetLevelChunkPacket, 0x1e: clientbound_explode_packet::ClientboundExplodePacket,
0x1f: clientbound_game_event_packet::ClientboundGameEventPacket, 0x1f: clientbound_forget_level_chunk_packet::ClientboundForgetLevelChunkPacket,
0x20: clientbound_horse_screen_open_packet::ClientboundHorseScreenOpenPacket, 0x20: clientbound_game_event_packet::ClientboundGameEventPacket,
0x21: clientbound_hurt_animation_packet::ClientboundHurtAnimationPacket, 0x21: clientbound_horse_screen_open_packet::ClientboundHorseScreenOpenPacket,
0x22: clientbound_initialize_border_packet::ClientboundInitializeBorderPacket, 0x22: clientbound_hurt_animation_packet::ClientboundHurtAnimationPacket,
0x23: clientbound_keep_alive_packet::ClientboundKeepAlivePacket, 0x23: clientbound_initialize_border_packet::ClientboundInitializeBorderPacket,
0x24: clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket, 0x24: clientbound_keep_alive_packet::ClientboundKeepAlivePacket,
0x25: clientbound_level_event_packet::ClientboundLevelEventPacket, 0x25: clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket,
0x26: clientbound_level_particles_packet::ClientboundLevelParticlesPacket, 0x26: clientbound_level_event_packet::ClientboundLevelEventPacket,
0x27: clientbound_light_update_packet::ClientboundLightUpdatePacket, 0x27: clientbound_level_particles_packet::ClientboundLevelParticlesPacket,
0x28: clientbound_login_packet::ClientboundLoginPacket, 0x28: clientbound_light_update_packet::ClientboundLightUpdatePacket,
0x29: clientbound_map_item_data_packet::ClientboundMapItemDataPacket, 0x29: clientbound_login_packet::ClientboundLoginPacket,
0x2a: clientbound_merchant_offers_packet::ClientboundMerchantOffersPacket, 0x2a: clientbound_map_item_data_packet::ClientboundMapItemDataPacket,
0x2b: clientbound_move_entity_pos_packet::ClientboundMoveEntityPosPacket, 0x2b: clientbound_merchant_offers_packet::ClientboundMerchantOffersPacket,
0x2c: clientbound_move_entity_pos_rot_packet::ClientboundMoveEntityPosRotPacket, 0x2c: clientbound_move_entity_pos_packet::ClientboundMoveEntityPosPacket,
0x2d: clientbound_move_entity_rot_packet::ClientboundMoveEntityRotPacket, 0x2d: clientbound_move_entity_pos_rot_packet::ClientboundMoveEntityPosRotPacket,
0x2e: clientbound_move_vehicle_packet::ClientboundMoveVehiclePacket, 0x2e: clientbound_move_entity_rot_packet::ClientboundMoveEntityRotPacket,
0x2f: clientbound_open_book_packet::ClientboundOpenBookPacket, 0x2f: clientbound_move_vehicle_packet::ClientboundMoveVehiclePacket,
0x30: clientbound_open_screen_packet::ClientboundOpenScreenPacket, 0x30: clientbound_open_book_packet::ClientboundOpenBookPacket,
0x31: clientbound_open_sign_editor_packet::ClientboundOpenSignEditorPacket, 0x31: clientbound_open_screen_packet::ClientboundOpenScreenPacket,
0x32: clientbound_ping_packet::ClientboundPingPacket, 0x32: clientbound_open_sign_editor_packet::ClientboundOpenSignEditorPacket,
0x33: clientbound_place_ghost_recipe_packet::ClientboundPlaceGhostRecipePacket, 0x33: clientbound_ping_packet::ClientboundPingPacket,
0x34: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, 0x34: clientbound_pong_response_packet::ClientboundPongResponsePacket,
0x35: clientbound_player_chat_packet::ClientboundPlayerChatPacket, 0x35: clientbound_place_ghost_recipe_packet::ClientboundPlaceGhostRecipePacket,
0x36: clientbound_player_combat_end_packet::ClientboundPlayerCombatEndPacket, 0x36: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket,
0x37: clientbound_player_combat_enter_packet::ClientboundPlayerCombatEnterPacket, 0x37: clientbound_player_chat_packet::ClientboundPlayerChatPacket,
0x38: clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, 0x38: clientbound_player_combat_end_packet::ClientboundPlayerCombatEndPacket,
0x39: clientbound_player_info_remove_packet::ClientboundPlayerInfoRemovePacket, 0x39: clientbound_player_combat_enter_packet::ClientboundPlayerCombatEnterPacket,
0x3a: clientbound_player_info_update_packet::ClientboundPlayerInfoUpdatePacket, 0x3a: clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket,
0x3b: clientbound_player_look_at_packet::ClientboundPlayerLookAtPacket, 0x3b: clientbound_player_info_remove_packet::ClientboundPlayerInfoRemovePacket,
0x3c: clientbound_player_position_packet::ClientboundPlayerPositionPacket, 0x3c: clientbound_player_info_update_packet::ClientboundPlayerInfoUpdatePacket,
0x3d: clientbound_recipe_packet::ClientboundRecipePacket, 0x3d: clientbound_player_look_at_packet::ClientboundPlayerLookAtPacket,
0x3e: clientbound_remove_entities_packet::ClientboundRemoveEntitiesPacket, 0x3e: clientbound_player_position_packet::ClientboundPlayerPositionPacket,
0x3f: clientbound_remove_mob_effect_packet::ClientboundRemoveMobEffectPacket, 0x3f: clientbound_recipe_packet::ClientboundRecipePacket,
0x40: clientbound_resource_pack_packet::ClientboundResourcePackPacket, 0x40: clientbound_remove_entities_packet::ClientboundRemoveEntitiesPacket,
0x41: clientbound_respawn_packet::ClientboundRespawnPacket, 0x41: clientbound_remove_mob_effect_packet::ClientboundRemoveMobEffectPacket,
0x42: clientbound_rotate_head_packet::ClientboundRotateHeadPacket, 0x42: clientbound_resource_pack_packet::ClientboundResourcePackPacket,
0x43: clientbound_section_blocks_update_packet::ClientboundSectionBlocksUpdatePacket, 0x43: clientbound_respawn_packet::ClientboundRespawnPacket,
0x44: clientbound_select_advancements_tab_packet::ClientboundSelectAdvancementsTabPacket, 0x44: clientbound_rotate_head_packet::ClientboundRotateHeadPacket,
0x45: clientbound_server_data_packet::ClientboundServerDataPacket, 0x45: clientbound_section_blocks_update_packet::ClientboundSectionBlocksUpdatePacket,
0x46: clientbound_set_action_bar_text_packet::ClientboundSetActionBarTextPacket, 0x46: clientbound_select_advancements_tab_packet::ClientboundSelectAdvancementsTabPacket,
0x47: clientbound_set_border_center_packet::ClientboundSetBorderCenterPacket, 0x47: clientbound_server_data_packet::ClientboundServerDataPacket,
0x48: clientbound_set_border_lerp_size_packet::ClientboundSetBorderLerpSizePacket, 0x48: clientbound_set_action_bar_text_packet::ClientboundSetActionBarTextPacket,
0x49: clientbound_set_border_size_packet::ClientboundSetBorderSizePacket, 0x49: clientbound_set_border_center_packet::ClientboundSetBorderCenterPacket,
0x4a: clientbound_set_border_warning_delay_packet::ClientboundSetBorderWarningDelayPacket, 0x4a: clientbound_set_border_lerp_size_packet::ClientboundSetBorderLerpSizePacket,
0x4b: clientbound_set_border_warning_distance_packet::ClientboundSetBorderWarningDistancePacket, 0x4b: clientbound_set_border_size_packet::ClientboundSetBorderSizePacket,
0x4c: clientbound_set_camera_packet::ClientboundSetCameraPacket, 0x4c: clientbound_set_border_warning_delay_packet::ClientboundSetBorderWarningDelayPacket,
0x4d: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket, 0x4d: clientbound_set_border_warning_distance_packet::ClientboundSetBorderWarningDistancePacket,
0x4e: clientbound_set_chunk_cache_center_packet::ClientboundSetChunkCacheCenterPacket, 0x4e: clientbound_set_camera_packet::ClientboundSetCameraPacket,
0x4f: clientbound_set_chunk_cache_radius_packet::ClientboundSetChunkCacheRadiusPacket, 0x4f: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket,
0x50: clientbound_set_default_spawn_position_packet::ClientboundSetDefaultSpawnPositionPacket, 0x50: clientbound_set_chunk_cache_center_packet::ClientboundSetChunkCacheCenterPacket,
0x51: clientbound_set_display_objective_packet::ClientboundSetDisplayObjectivePacket, 0x51: clientbound_set_chunk_cache_radius_packet::ClientboundSetChunkCacheRadiusPacket,
0x52: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket, 0x52: clientbound_set_default_spawn_position_packet::ClientboundSetDefaultSpawnPositionPacket,
0x53: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket, 0x53: clientbound_set_display_objective_packet::ClientboundSetDisplayObjectivePacket,
0x54: clientbound_set_entity_motion_packet::ClientboundSetEntityMotionPacket, 0x54: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket,
0x55: clientbound_set_equipment_packet::ClientboundSetEquipmentPacket, 0x55: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket,
0x56: clientbound_set_experience_packet::ClientboundSetExperiencePacket, 0x56: clientbound_set_entity_motion_packet::ClientboundSetEntityMotionPacket,
0x57: clientbound_set_health_packet::ClientboundSetHealthPacket, 0x57: clientbound_set_equipment_packet::ClientboundSetEquipmentPacket,
0x58: clientbound_set_objective_packet::ClientboundSetObjectivePacket, 0x58: clientbound_set_experience_packet::ClientboundSetExperiencePacket,
0x59: clientbound_set_passengers_packet::ClientboundSetPassengersPacket, 0x59: clientbound_set_health_packet::ClientboundSetHealthPacket,
0x5a: clientbound_set_player_team_packet::ClientboundSetPlayerTeamPacket, 0x5a: clientbound_set_objective_packet::ClientboundSetObjectivePacket,
0x5b: clientbound_set_score_packet::ClientboundSetScorePacket, 0x5b: clientbound_set_passengers_packet::ClientboundSetPassengersPacket,
0x5c: clientbound_set_simulation_distance_packet::ClientboundSetSimulationDistancePacket, 0x5c: clientbound_set_player_team_packet::ClientboundSetPlayerTeamPacket,
0x5d: clientbound_set_subtitle_text_packet::ClientboundSetSubtitleTextPacket, 0x5d: clientbound_set_score_packet::ClientboundSetScorePacket,
0x5e: clientbound_set_time_packet::ClientboundSetTimePacket, 0x5e: clientbound_set_simulation_distance_packet::ClientboundSetSimulationDistancePacket,
0x5f: clientbound_set_title_text_packet::ClientboundSetTitleTextPacket, 0x5f: clientbound_set_subtitle_text_packet::ClientboundSetSubtitleTextPacket,
0x60: clientbound_set_titles_animation_packet::ClientboundSetTitlesAnimationPacket, 0x60: clientbound_set_time_packet::ClientboundSetTimePacket,
0x61: clientbound_sound_entity_packet::ClientboundSoundEntityPacket, 0x61: clientbound_set_title_text_packet::ClientboundSetTitleTextPacket,
0x62: clientbound_sound_packet::ClientboundSoundPacket, 0x62: clientbound_set_titles_animation_packet::ClientboundSetTitlesAnimationPacket,
0x63: clientbound_stop_sound_packet::ClientboundStopSoundPacket, 0x63: clientbound_sound_entity_packet::ClientboundSoundEntityPacket,
0x64: clientbound_system_chat_packet::ClientboundSystemChatPacket, 0x64: clientbound_sound_packet::ClientboundSoundPacket,
0x65: clientbound_tab_list_packet::ClientboundTabListPacket, 0x65: clientbound_start_configuration_packet::ClientboundStartConfigurationPacket,
0x66: clientbound_tag_query_packet::ClientboundTagQueryPacket, 0x66: clientbound_stop_sound_packet::ClientboundStopSoundPacket,
0x67: clientbound_take_item_entity_packet::ClientboundTakeItemEntityPacket, 0x67: clientbound_system_chat_packet::ClientboundSystemChatPacket,
0x68: clientbound_teleport_entity_packet::ClientboundTeleportEntityPacket, 0x68: clientbound_tab_list_packet::ClientboundTabListPacket,
0x69: clientbound_update_advancements_packet::ClientboundUpdateAdvancementsPacket, 0x69: clientbound_tag_query_packet::ClientboundTagQueryPacket,
0x6a: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket, 0x6a: clientbound_take_item_entity_packet::ClientboundTakeItemEntityPacket,
0x6b: clientbound_update_enabled_features_packet::ClientboundUpdateEnabledFeaturesPacket, 0x6b: clientbound_teleport_entity_packet::ClientboundTeleportEntityPacket,
0x6c: clientbound_update_mob_effect_packet::ClientboundUpdateMobEffectPacket, 0x6c: clientbound_update_advancements_packet::ClientboundUpdateAdvancementsPacket,
0x6d: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket, 0x6d: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket,
0x6e: clientbound_update_tags_packet::ClientboundUpdateTagsPacket, 0x6e: clientbound_update_mob_effect_packet::ClientboundUpdateMobEffectPacket,
0x6f: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket,
0x70: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
} }
); );

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
pub struct ServerboundChunkBatchReceivedPacket {
pub desired_chunks_per_tick: f32,
}

View file

@ -1,175 +1,9 @@
use azalea_buf::{McBuf, McBufReadable, McBufWritable}; use azalea_buf::McBuf;
use azalea_core::FixedBitSet;
use azalea_protocol_macros::ServerboundGamePacket; use azalea_protocol_macros::ServerboundGamePacket;
use bevy_ecs::component::Component;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket, PartialEq, Eq, Component)] use crate::packets::configuration::serverbound_client_information_packet::ClientInformation;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
pub struct ServerboundClientInformationPacket { pub struct ServerboundClientInformationPacket {
/// The locale of the client. pub information: ClientInformation,
pub language: String,
/// The view distance of the client in chunks, same as the render distance
/// in-game.
pub view_distance: u8,
/// The types of chat messages the client wants to receive. Note that many
/// servers ignore this.
pub chat_visibility: ChatVisibility,
/// Whether the messages sent from the server should have colors. Note that
/// many servers ignore this and always send colored messages.
pub chat_colors: bool,
pub model_customization: ModelCustomization,
pub main_hand: HumanoidArm,
pub text_filtering_enabled: bool,
/// Whether the client should show up as "Anonymous Player" in the server
/// list.
pub allows_listing: bool,
}
impl Default for ServerboundClientInformationPacket {
fn default() -> Self {
Self {
language: "en_us".to_string(),
view_distance: 8,
chat_visibility: ChatVisibility::default(),
chat_colors: true,
model_customization: ModelCustomization::default(),
main_hand: HumanoidArm::Right,
text_filtering_enabled: false,
allows_listing: false,
}
}
}
#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ChatVisibility {
/// All chat messages should be sent to the client.
#[default]
Full = 0,
/// Chat messages from other players should be not sent to the client, only
/// messages from the server like "Player joined the game" should be sent.
System = 1,
/// No chat messages should be sent to the client.
Hidden = 2,
}
#[derive(McBuf, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum HumanoidArm {
Left = 0,
#[default]
Right = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModelCustomization {
pub cape: bool,
pub jacket: bool,
pub left_sleeve: bool,
pub right_sleeve: bool,
pub left_pants: bool,
pub right_pants: bool,
pub hat: bool,
}
impl Default for ModelCustomization {
fn default() -> Self {
Self {
cape: true,
jacket: true,
left_sleeve: true,
right_sleeve: true,
left_pants: true,
right_pants: true,
hat: true,
}
}
}
impl McBufReadable for ModelCustomization {
fn read_from(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> {
let set = FixedBitSet::<7>::read_from(buf)?;
Ok(Self {
cape: set.index(0),
jacket: set.index(1),
left_sleeve: set.index(2),
right_sleeve: set.index(3),
left_pants: set.index(4),
right_pants: set.index(5),
hat: set.index(6),
})
}
}
impl McBufWritable for ModelCustomization {
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
let mut set = FixedBitSet::<7>::new();
if self.cape {
set.set(0);
}
if self.jacket {
set.set(1);
}
if self.left_sleeve {
set.set(2);
}
if self.right_sleeve {
set.set(3);
}
if self.left_pants {
set.set(4);
}
if self.right_pants {
set.set(5);
}
if self.hat {
set.set(6);
}
set.write_into(buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_client_information_packet() {
{
let data = ServerboundClientInformationPacket::default();
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data =
ServerboundClientInformationPacket::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
{
let data = ServerboundClientInformationPacket {
language: "en_gb".to_string(),
view_distance: 24,
chat_visibility: ChatVisibility::Hidden,
chat_colors: false,
model_customization: ModelCustomization {
cape: false,
jacket: false,
left_sleeve: true,
right_sleeve: false,
left_pants: true,
right_pants: false,
hat: true,
},
main_hand: HumanoidArm::Left,
text_filtering_enabled: true,
allows_listing: true,
};
let mut buf = Vec::new();
data.write_into(&mut buf).unwrap();
let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
let read_data =
ServerboundClientInformationPacket::read_from(&mut data_cursor).unwrap();
assert_eq!(read_data, data);
}
}
} }

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
pub struct ServerboundConfigurationAcknowledgedPacket {}

View file

@ -0,0 +1,7 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundGamePacket;
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
pub struct ServerboundPingRequestPacket {
pub time: u64,
}

View file

@ -3,9 +3,10 @@ pub mod clientbound_game_profile_packet;
pub mod clientbound_hello_packet; pub mod clientbound_hello_packet;
pub mod clientbound_login_compression_packet; pub mod clientbound_login_compression_packet;
pub mod clientbound_login_disconnect_packet; pub mod clientbound_login_disconnect_packet;
pub mod serverbound_custom_query_packet; pub mod serverbound_custom_query_answer_packet;
pub mod serverbound_hello_packet; pub mod serverbound_hello_packet;
pub mod serverbound_key_packet; pub mod serverbound_key_packet;
pub mod serverbound_login_acknowledged_packet;
use azalea_protocol_macros::declare_state_packets; use azalea_protocol_macros::declare_state_packets;
@ -14,7 +15,8 @@ declare_state_packets!(
Serverbound => { Serverbound => {
0x00: serverbound_hello_packet::ServerboundHelloPacket, 0x00: serverbound_hello_packet::ServerboundHelloPacket,
0x01: serverbound_key_packet::ServerboundKeyPacket, 0x01: serverbound_key_packet::ServerboundKeyPacket,
0x02: serverbound_custom_query_packet::ServerboundCustomQueryPacket, 0x02: serverbound_custom_query_answer_packet::ServerboundCustomQueryAnswerPacket,
0x03: serverbound_login_acknowledged_packet::ServerboundLoginAcknowledgedPacket,
}, },
Clientbound => { Clientbound => {
0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket,

View file

@ -0,0 +1,10 @@
use azalea_buf::{McBuf, UnsizedByteArray};
use azalea_protocol_macros::ServerboundLoginPacket;
use std::hash::Hash;
#[derive(Hash, Clone, Debug, McBuf, ServerboundLoginPacket)]
pub struct ServerboundCustomQueryAnswerPacket {
#[var]
pub transaction_id: u32,
pub data: Option<UnsizedByteArray>,
}

View file

@ -5,7 +5,7 @@ use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Eq, McBuf, ServerboundLoginPacket)] #[derive(Clone, Debug, PartialEq, Eq, McBuf, ServerboundLoginPacket)]
pub struct ServerboundHelloPacket { pub struct ServerboundHelloPacket {
pub name: String, pub name: String,
pub profile_id: Option<Uuid>, pub profile_id: Uuid,
} }
#[cfg(test)] #[cfg(test)]
@ -19,7 +19,7 @@ mod tests {
fn test_read_write() { fn test_read_write() {
let packet = ServerboundHelloPacket { let packet = ServerboundHelloPacket {
name: "test".to_string(), name: "test".to_string(),
profile_id: Some(Uuid::nil()), profile_id: Uuid::nil(),
}; };
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
packet.write_into(&mut buf).unwrap(); packet.write_into(&mut buf).unwrap();

View file

@ -0,0 +1,5 @@
use azalea_buf::McBuf;
use azalea_protocol_macros::ServerboundLoginPacket;
#[derive(Clone, Debug, McBuf, ServerboundLoginPacket)]
pub struct ServerboundLoginAcknowledgedPacket {}

View file

@ -1,5 +1,7 @@
pub mod common;
pub mod configuration;
pub mod game; pub mod game;
pub mod handshake; pub mod handshaking;
pub mod login; pub mod login;
pub mod status; pub mod status;
@ -10,7 +12,7 @@ use std::io::{Cursor, Write};
// TODO: rename the packet files to just like clientbound_add_entity instead of // TODO: rename the packet files to just like clientbound_add_entity instead of
// clientbound_add_entity_packet // clientbound_add_entity_packet
pub const PROTOCOL_VERSION: u32 = 763; pub const PROTOCOL_VERSION: u32 = 764;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConnectionProtocol { pub enum ConnectionProtocol {
@ -18,6 +20,7 @@ pub enum ConnectionProtocol {
Game = 0, Game = 0,
Status = 1, Status = 1,
Login = 2, Login = 2,
Configuration = 3,
} }
impl ConnectionProtocol { impl ConnectionProtocol {
@ -28,6 +31,7 @@ impl ConnectionProtocol {
0 => Some(ConnectionProtocol::Game), 0 => Some(ConnectionProtocol::Game),
1 => Some(ConnectionProtocol::Status), 1 => Some(ConnectionProtocol::Status),
2 => Some(ConnectionProtocol::Login), 2 => Some(ConnectionProtocol::Login),
3 => Some(ConnectionProtocol::Configuration),
_ => None, _ => None,
} }
} }

View file

@ -127,7 +127,7 @@ fn frame_splitter(buffer: &mut BytesMut) -> Result<Option<Vec<u8>>, FrameSplitte
Ok(None) Ok(None)
} }
fn packet_decoder<P: ProtocolPacket + Debug>( pub fn deserialize_packet<P: ProtocolPacket + Debug>(
stream: &mut Cursor<&[u8]>, stream: &mut Cursor<&[u8]>,
) -> Result<P, Box<ReadPacketError>> { ) -> Result<P, Box<ReadPacketError>> {
// Packet ID // Packet ID
@ -216,26 +216,9 @@ pub async fn read_packet<'a, P: ProtocolPacket + Debug, R>(
where where
R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync, R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync,
{ {
let mut framed = FramedRead::new(stream, BytesCodec::new()); let raw_packet = read_raw_packet(stream, buffer, compression_threshold, cipher).await?;
loop { let packet = deserialize_packet(&mut Cursor::new(raw_packet.as_slice()))?;
if let Some(buf) = try_process_buffer::<P, R>(buffer, compression_threshold)? { Ok(packet)
// we got a full packet!!
return Ok(buf);
};
// if we were given a cipher, decrypt the packet
if let Some(message) = framed.next().await {
let mut bytes = message.map_err(ReadPacketError::from)?;
if let Some(cipher) = cipher {
azalea_crypto::decrypt_packet(cipher, &mut bytes);
}
buffer.extend_from_slice(&bytes);
} else {
return Err(Box::new(ReadPacketError::ConnectionClosed));
};
}
} }
/// Try to read a single packet from a stream. Returns None if we haven't /// Try to read a single packet from a stream. Returns None if we haven't
@ -246,41 +229,110 @@ pub fn try_read_packet<P: ProtocolPacket + Debug, R>(
compression_threshold: Option<u32>, compression_threshold: Option<u32>,
cipher: &mut Option<Aes128CfbDec>, cipher: &mut Option<Aes128CfbDec>,
) -> Result<Option<P>, Box<ReadPacketError>> ) -> Result<Option<P>, Box<ReadPacketError>>
where
R: AsyncRead + Unpin + Send + Sync,
{
let Some(raw_packet) = try_read_raw_packet(stream, buffer, compression_threshold, cipher)?
else {
return Ok(None);
};
let packet = deserialize_packet(&mut Cursor::new(raw_packet.as_slice()))?;
Ok(Some(packet))
}
pub async fn read_raw_packet<'a, R>(
stream: &'a mut R,
buffer: &mut BytesMut,
compression_threshold: Option<u32>,
cipher: &mut Option<Aes128CfbDec>,
) -> Result<Vec<u8>, Box<ReadPacketError>>
where where
R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync, R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync,
{ {
let mut framed = FramedRead::new(stream, BytesCodec::new());
loop { loop {
if let Some(buf) = try_process_buffer::<P, R>(buffer, compression_threshold)? { if let Some(buf) = read_raw_packet_from_buffer::<R>(buffer, compression_threshold)? {
// we got a full packet!!
return Ok(buf);
};
let bytes = read_and_decrypt_frame(stream, cipher).await?;
buffer.extend_from_slice(&bytes);
}
}
pub fn try_read_raw_packet<R>(
stream: &mut R,
buffer: &mut BytesMut,
compression_threshold: Option<u32>,
cipher: &mut Option<Aes128CfbDec>,
) -> Result<Option<Vec<u8>>, Box<ReadPacketError>>
where
R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync,
{
loop {
if let Some(buf) = read_raw_packet_from_buffer::<R>(buffer, compression_threshold)? {
// we got a full packet!! // we got a full packet!!
return Ok(Some(buf)); return Ok(Some(buf));
}; };
let Some(bytes) = try_read_and_decrypt_frame(stream, cipher)? else {
// if we were given a cipher, decrypt the packet // no data received
if let Some(message) = future::block_on(future::poll_once(framed.next())) {
if let Some(message) = message {
let mut bytes = message.map_err(ReadPacketError::from)?;
if let Some(cipher) = cipher {
azalea_crypto::decrypt_packet(cipher, &mut bytes);
}
buffer.extend_from_slice(&bytes);
} else {
return Err(Box::new(ReadPacketError::ConnectionClosed));
}
} else {
return Ok(None); return Ok(None);
}; };
// we got some data, so add it to the buffer and try again
buffer.extend_from_slice(&bytes);
} }
} }
/// Try to get a Minecraft packet from a buffer. Returns None if the packet async fn read_and_decrypt_frame<R>(
/// isn't complete yet. stream: &mut R,
pub fn try_process_buffer<P: ProtocolPacket + Debug, R>( cipher: &mut Option<Aes128CfbDec>,
) -> Result<BytesMut, Box<ReadPacketError>>
where
R: AsyncRead + Unpin + Send + Sync,
{
let mut framed = FramedRead::new(stream, BytesCodec::new());
let Some(message) = framed.next().await else {
return Err(Box::new(ReadPacketError::ConnectionClosed));
};
let mut bytes = message.map_err(ReadPacketError::from)?;
// decrypt if necessary
if let Some(cipher) = cipher {
azalea_crypto::decrypt_packet(cipher, &mut bytes);
}
Ok(bytes)
}
fn try_read_and_decrypt_frame<R>(
stream: &mut R,
cipher: &mut Option<Aes128CfbDec>,
) -> Result<Option<BytesMut>, Box<ReadPacketError>>
where
R: AsyncRead + Unpin + Send + Sync,
{
let mut framed = FramedRead::new(stream, BytesCodec::new());
let Some(message) = future::block_on(future::poll_once(framed.next())) else {
// nothing yet
return Ok(None);
};
let Some(message) = message else {
return Err(Box::new(ReadPacketError::ConnectionClosed));
};
let mut bytes = message.map_err(ReadPacketError::from)?;
// decrypt if necessary
if let Some(cipher) = cipher {
azalea_crypto::decrypt_packet(cipher, &mut bytes);
}
Ok(Some(bytes))
}
pub fn read_raw_packet_from_buffer<R>(
buffer: &mut BytesMut, buffer: &mut BytesMut,
compression_threshold: Option<u32>, compression_threshold: Option<u32>,
) -> Result<Option<P>, Box<ReadPacketError>> ) -> Result<Option<Vec<u8>>, Box<ReadPacketError>>
where where
R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync, R: AsyncRead + std::marker::Unpin + std::marker::Send + std::marker::Sync,
{ {
@ -306,7 +358,5 @@ where
trace!("Reading packet with bytes: {buf_string}"); trace!("Reading packet with bytes: {buf_string}");
} }
let packet = packet_decoder(&mut Cursor::new(&buf[..]))?; Ok(Some(buf))
Ok(Some(packet))
} }

View file

@ -29,7 +29,7 @@ pub enum PacketEncodeError {
}, },
} }
pub fn packet_encoder<P: ProtocolPacket + std::fmt::Debug>( pub fn serialize_packet<P: ProtocolPacket + Debug>(
packet: &P, packet: &P,
) -> Result<Vec<u8>, PacketEncodeError> { ) -> Result<Vec<u8>, PacketEncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -89,14 +89,28 @@ where
W: AsyncWrite + Unpin + Send, W: AsyncWrite + Unpin + Send,
{ {
trace!("Sending packet: {packet:?}"); trace!("Sending packet: {packet:?}");
let mut buf = packet_encoder(packet).unwrap(); let raw_packet = serialize_packet(packet).unwrap();
write_raw_packet(&raw_packet, stream, compression_threshold, cipher).await
}
pub async fn write_raw_packet<W>(
raw_packet: &[u8],
stream: &mut W,
compression_threshold: Option<u32>,
cipher: &mut Option<Aes128CfbEnc>,
) -> std::io::Result<()>
where
W: AsyncWrite + Unpin + Send,
{
trace!("Writing raw packet: {raw_packet:?}");
let mut raw_packet = raw_packet.to_vec();
if let Some(threshold) = compression_threshold { if let Some(threshold) = compression_threshold {
buf = compression_encoder(&buf, threshold).await.unwrap(); raw_packet = compression_encoder(&raw_packet, threshold).await.unwrap();
} }
buf = frame_prepender(buf).unwrap(); raw_packet = frame_prepender(raw_packet).unwrap();
// if we were given a cipher, encrypt the packet // if we were given a cipher, encrypt the packet
if let Some(cipher) = cipher { if let Some(cipher) = cipher {
azalea_crypto::encrypt_packet(cipher, &mut buf); azalea_crypto::encrypt_packet(cipher, &mut raw_packet);
} }
stream.write_all(&buf).await stream.write_all(&raw_packet).await
} }

View file

@ -122,6 +122,7 @@ enum Attribute {
GenericArmor => "minecraft:generic.armor", GenericArmor => "minecraft:generic.armor",
GenericArmorToughness => "minecraft:generic.armor_toughness", GenericArmorToughness => "minecraft:generic.armor_toughness",
GenericLuck => "minecraft:generic.luck", GenericLuck => "minecraft:generic.luck",
GenericMaxAbsorption => "minecraft:generic.max_absorption",
ZombieSpawnReinforcements => "minecraft:zombie.spawn_reinforcements", ZombieSpawnReinforcements => "minecraft:zombie.spawn_reinforcements",
HorseJumpStrength => "minecraft:horse.jump_strength", HorseJumpStrength => "minecraft:horse.jump_strength",
} }
@ -1642,8 +1643,7 @@ enum GameEvent {
EntityInteract => "minecraft:entity_interact", EntityInteract => "minecraft:entity_interact",
EntityMount => "minecraft:entity_mount", EntityMount => "minecraft:entity_mount",
EntityPlace => "minecraft:entity_place", EntityPlace => "minecraft:entity_place",
EntityRoar => "minecraft:entity_roar", EntityAction => "minecraft:entity_action",
EntityShake => "minecraft:entity_shake",
Equip => "minecraft:equip", Equip => "minecraft:equip",
Explode => "minecraft:explode", Explode => "minecraft:explode",
Flap => "minecraft:flap", Flap => "minecraft:flap",
@ -1667,6 +1667,7 @@ enum GameEvent {
Step => "minecraft:step", Step => "minecraft:step",
Swim => "minecraft:swim", Swim => "minecraft:swim",
Teleport => "minecraft:teleport", Teleport => "minecraft:teleport",
Unequip => "minecraft:unequip",
Resonate1 => "minecraft:resonate_1", Resonate1 => "minecraft:resonate_1",
Resonate2 => "minecraft:resonate_2", Resonate2 => "minecraft:resonate_2",
Resonate3 => "minecraft:resonate_3", Resonate3 => "minecraft:resonate_3",
@ -3031,6 +3032,7 @@ enum LootFunctionKind {
SetPotion => "minecraft:set_potion", SetPotion => "minecraft:set_potion",
SetInstrument => "minecraft:set_instrument", SetInstrument => "minecraft:set_instrument",
Reference => "minecraft:reference", Reference => "minecraft:reference",
Sequence => "minecraft:sequence",
} }
} }
@ -4810,6 +4812,12 @@ enum SoundEvent {
EntitySpiderStep => "minecraft:entity.spider.step", EntitySpiderStep => "minecraft:entity.spider.step",
EntitySplashPotionBreak => "minecraft:entity.splash_potion.break", EntitySplashPotionBreak => "minecraft:entity.splash_potion.break",
EntitySplashPotionThrow => "minecraft:entity.splash_potion.throw", EntitySplashPotionThrow => "minecraft:entity.splash_potion.throw",
BlockSpongeBreak => "minecraft:block.sponge.break",
BlockSpongeFall => "minecraft:block.sponge.fall",
BlockSpongeHit => "minecraft:block.sponge.hit",
BlockSpongePlace => "minecraft:block.sponge.place",
BlockSpongeStep => "minecraft:block.sponge.step",
BlockSpongeAbsorb => "minecraft:block.sponge.absorb",
ItemSpyglassUse => "minecraft:item.spyglass.use", ItemSpyglassUse => "minecraft:item.spyglass.use",
ItemSpyglassStopUsing => "minecraft:item.spyglass.stop_using", ItemSpyglassStopUsing => "minecraft:item.spyglass.stop_using",
EntitySquidAmbient => "minecraft:entity.squid.ambient", EntitySquidAmbient => "minecraft:entity.squid.ambient",
@ -4954,6 +4962,11 @@ enum SoundEvent {
BlockWetGrassHit => "minecraft:block.wet_grass.hit", BlockWetGrassHit => "minecraft:block.wet_grass.hit",
BlockWetGrassPlace => "minecraft:block.wet_grass.place", BlockWetGrassPlace => "minecraft:block.wet_grass.place",
BlockWetGrassStep => "minecraft:block.wet_grass.step", BlockWetGrassStep => "minecraft:block.wet_grass.step",
BlockWetSpongeBreak => "minecraft:block.wet_sponge.break",
BlockWetSpongeFall => "minecraft:block.wet_sponge.fall",
BlockWetSpongeHit => "minecraft:block.wet_sponge.hit",
BlockWetSpongePlace => "minecraft:block.wet_sponge.place",
BlockWetSpongeStep => "minecraft:block.wet_sponge.step",
EntityWitchAmbient => "minecraft:entity.witch.ambient", EntityWitchAmbient => "minecraft:entity.witch.ambient",
EntityWitchCelebrate => "minecraft:entity.witch.celebrate", EntityWitchCelebrate => "minecraft:entity.witch.celebrate",
EntityWitchDeath => "minecraft:entity.witch.death", EntityWitchDeath => "minecraft:entity.witch.death",

View file

@ -22,6 +22,12 @@ pub static MINEABLE_SHOVEL: Lazy<HashSet<Block>> = Lazy::new(|| {
Block::Snow, Block::Snow,
Block::SoulSand, Block::SoulSand,
Block::DirtPath, Block::DirtPath,
Block::SoulSoil,
Block::RootedDirt,
Block::MuddyMangroveRoots,
Block::Mud,
Block::SuspiciousSand,
Block::SuspiciousGravel,
Block::WhiteConcretePowder, Block::WhiteConcretePowder,
Block::OrangeConcretePowder, Block::OrangeConcretePowder,
Block::MagentaConcretePowder, Block::MagentaConcretePowder,
@ -38,12 +44,6 @@ pub static MINEABLE_SHOVEL: Lazy<HashSet<Block>> = Lazy::new(|| {
Block::GreenConcretePowder, Block::GreenConcretePowder,
Block::RedConcretePowder, Block::RedConcretePowder,
Block::BlackConcretePowder, Block::BlackConcretePowder,
Block::SoulSoil,
Block::RootedDirt,
Block::MuddyMangroveRoots,
Block::Mud,
Block::SuspiciousSand,
Block::SuspiciousGravel,
]) ])
}); });
pub static MINEABLE_AXE: Lazy<HashSet<Block>> = Lazy::new(|| { pub static MINEABLE_AXE: Lazy<HashSet<Block>> = Lazy::new(|| {
@ -979,6 +979,26 @@ pub static DEAD_BUSH_MAY_PLACE_ON: Lazy<HashSet<Block>> = Lazy::new(|| {
Block::MuddyMangroveRoots, Block::MuddyMangroveRoots,
]) ])
}); });
pub static CONCRETE_POWDER: Lazy<HashSet<Block>> = Lazy::new(|| {
HashSet::from_iter(vec![
Block::WhiteConcretePowder,
Block::OrangeConcretePowder,
Block::MagentaConcretePowder,
Block::LightBlueConcretePowder,
Block::YellowConcretePowder,
Block::LimeConcretePowder,
Block::PinkConcretePowder,
Block::GrayConcretePowder,
Block::LightGrayConcretePowder,
Block::CyanConcretePowder,
Block::PurpleConcretePowder,
Block::BlueConcretePowder,
Block::BrownConcretePowder,
Block::GreenConcretePowder,
Block::RedConcretePowder,
Block::BlackConcretePowder,
])
});
pub static WOLVES_SPAWNABLE_ON: Lazy<HashSet<Block>> = pub static WOLVES_SPAWNABLE_ON: Lazy<HashSet<Block>> =
Lazy::new(|| HashSet::from_iter(vec![Block::GrassBlock, Block::Snow, Block::SnowBlock])); Lazy::new(|| HashSet::from_iter(vec![Block::GrassBlock, Block::Snow, Block::SnowBlock]));
pub static LUSH_GROUND_REPLACEABLE: Lazy<HashSet<Block>> = Lazy::new(|| { pub static LUSH_GROUND_REPLACEABLE: Lazy<HashSet<Block>> = Lazy::new(|| {
@ -1370,6 +1390,30 @@ pub static LOGS_THAT_BURN: Lazy<HashSet<Block>> = Lazy::new(|| {
Block::StrippedCherryWood, Block::StrippedCherryWood,
]) ])
}); });
pub static CAMEL_SAND_STEP_SOUND_BLOCKS: Lazy<HashSet<Block>> = Lazy::new(|| {
HashSet::from_iter(vec![
Block::Sand,
Block::RedSand,
Block::SuspiciousSand,
Block::SuspiciousSand,
Block::WhiteConcretePowder,
Block::OrangeConcretePowder,
Block::MagentaConcretePowder,
Block::LightBlueConcretePowder,
Block::YellowConcretePowder,
Block::LimeConcretePowder,
Block::PinkConcretePowder,
Block::GrayConcretePowder,
Block::LightGrayConcretePowder,
Block::CyanConcretePowder,
Block::PurpleConcretePowder,
Block::BlueConcretePowder,
Block::BrownConcretePowder,
Block::GreenConcretePowder,
Block::RedConcretePowder,
Block::BlackConcretePowder,
])
});
pub static CHERRY_LOGS: Lazy<HashSet<Block>> = Lazy::new(|| { pub static CHERRY_LOGS: Lazy<HashSet<Block>> = Lazy::new(|| {
HashSet::from_iter(vec![ HashSet::from_iter(vec![
Block::CherryLog, Block::CherryLog,
@ -1528,6 +1572,8 @@ pub static FLOWERS: Lazy<HashSet<Block>> = Lazy::new(|| {
Block::MangrovePropagule, Block::MangrovePropagule,
Block::CherryLeaves, Block::CherryLeaves,
Block::PinkPetals, Block::PinkPetals,
Block::ChorusFlower,
Block::SporeBlossom,
Block::Dandelion, Block::Dandelion,
Block::Poppy, Block::Poppy,
Block::BlueOrchid, Block::BlueOrchid,

View file

@ -376,6 +376,8 @@ pub static FLOWERS: Lazy<HashSet<Item>> = Lazy::new(|| {
Item::MangrovePropagule, Item::MangrovePropagule,
Item::CherryLeaves, Item::CherryLeaves,
Item::PinkPetals, Item::PinkPetals,
Item::ChorusFlower,
Item::SporeBlossom,
Item::Dandelion, Item::Dandelion,
Item::Poppy, Item::Poppy,
Item::BlueOrchid, Item::BlueOrchid,

View file

@ -38,7 +38,7 @@ async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<(
let chest_block = bot let chest_block = bot
.world() .world()
.read() .read()
.find_block(bot.position(), &azalea::Block::Chest.into()); .find_block(bot.position(), &azalea::registry::Block::Chest.into());
// TODO: update this when find_blocks is implemented // TODO: update this when find_blocks is implemented
let Some(chest_block) = chest_block else { let Some(chest_block) = chest_block else {
bot.chat("No chest found"); bot.chat("No chest found");
@ -59,7 +59,7 @@ async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<(
{ {
println!("Checking slot {index}: {slot:?}"); println!("Checking slot {index}: {slot:?}");
if let ItemSlot::Present(item) = slot { if let ItemSlot::Present(item) = slot {
if item.kind == azalea::Item::Diamond { if item.kind == azalea::registry::Item::Diamond {
println!("clicking slot ^"); println!("clicking slot ^");
chest.click(QuickMoveClick::Left { slot: index as u16 }); chest.click(QuickMoveClick::Left { slot: index as u16 });
} }

View file

@ -7,14 +7,13 @@ use azalea::entity::{metadata::Player, EyeHeight, Position};
use azalea::interact::HitResultComponent; use azalea::interact::HitResultComponent;
use azalea::inventory::ItemSlot; use azalea::inventory::ItemSlot;
use azalea::pathfinder::goals::BlockPosGoal; use azalea::pathfinder::goals::BlockPosGoal;
use azalea::protocol::packets::game::ClientboundGamePacket; use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
use azalea::world::{heightmap::HeightmapKind, InstanceName, MinecraftEntityId}; use azalea::{Account, Client, Event};
use azalea::SprintDirection; use azalea_client::SprintDirection;
use azalea::{prelude::*, swarm::prelude::*}; use azalea_core::{ChunkBlockPos, ChunkPos, Vec3};
use azalea::{ use azalea_protocol::packets::game::ClientboundGamePacket;
Account, BlockPos, ChunkPos, Client, Event, GameProfileComponent, Vec3, WalkDirection, use azalea_world::heightmap::HeightmapKind;
}; use azalea_world::{InstanceName, MinecraftEntityId};
use azalea_core::ChunkBlockPos;
use std::time::Duration; use std::time::Duration;
#[derive(Default, Clone, Component)] #[derive(Default, Clone, Component)]

View file

@ -1,5 +1,7 @@
use crate::app::{App, Plugin}; use crate::app::{App, Plugin};
use azalea_client::packet_handling::{death_event_on_0_health, ResourcePackEvent}; use azalea_client::chunk_batching::handle_chunk_batch_finished_event;
use azalea_client::inventory::InventorySet;
use azalea_client::packet_handling::{death_event_on_0_health, game::ResourcePackEvent};
use azalea_client::respawn::perform_respawn; use azalea_client::respawn::perform_respawn;
use azalea_client::SendPacketEvent; use azalea_client::SendPacketEvent;
use azalea_protocol::packets::game::serverbound_resource_pack_packet::{ use azalea_protocol::packets::game::serverbound_resource_pack_packet::{
@ -17,7 +19,9 @@ impl Plugin for AcceptResourcePacksPlugin {
Update, Update,
accept_resource_pack accept_resource_pack
.before(perform_respawn) .before(perform_respawn)
.after(death_event_on_0_health), .after(death_event_on_0_health)
.after(handle_chunk_batch_finished_event)
.after(InventorySet),
); );
} }
} }

View file

@ -1,6 +1,8 @@
use crate::app::{App, Plugin}; use crate::app::{App, Plugin};
use azalea_client::packet_handling::{death_event_on_0_health, DeathEvent}; use azalea_client::{
use azalea_client::respawn::{perform_respawn, PerformRespawnEvent}; packet_handling::{death_event_on_0_health, game::DeathEvent},
respawn::{perform_respawn, PerformRespawnEvent},
};
use bevy_app::Update; use bevy_app::Update;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;

View file

@ -2,7 +2,7 @@ use std::fmt::Formatter;
use azalea_client::{ use azalea_client::{
inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent}, inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent},
packet_handling::PacketEvent, packet_handling::game::PacketEvent,
Client, Client,
}; };
use azalea_core::BlockPos; use azalea_core::BlockPos;
@ -36,7 +36,7 @@ impl ContainerClientExt for Client {
/// let target_pos = bot /// let target_pos = bot
/// .world() /// .world()
/// .read() /// .read()
/// .find_block(bot.position(), &azalea::Block::Chest.into()); /// .find_block(bot.position(), &azalea::registry::Block::Chest.into());
/// let Some(target_pos) = target_pos else { /// let Some(target_pos) = target_pos else {
/// bot.chat("no chest found"); /// bot.chat("no chest found");
/// return; /// return;

View file

@ -74,7 +74,8 @@ impl Simulation {
.iter() .iter()
.cloned() .cloned()
.collect(), .collect(),
}); })
.add_event::<azalea_client::SendPacketEvent>();
app.edit_schedule(bevy_app::Main, |schedule| { app.edit_schedule(bevy_app::Main, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);

View file

@ -1,4 +1,4 @@
use azalea_client::LocalPlayer; use azalea_client::InstanceHolder;
use azalea_world::MinecraftEntityId; use azalea_world::MinecraftEntityId;
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
@ -21,7 +21,7 @@ pub struct SwarmReadyEvent;
struct IsSwarmReady(bool); struct IsSwarmReady(bool);
fn check_ready( fn check_ready(
query: Query<Option<&MinecraftEntityId>, With<LocalPlayer>>, query: Query<Option<&MinecraftEntityId>, With<InstanceHolder>>,
mut is_swarm_ready: ResMut<IsSwarmReady>, mut is_swarm_ready: ResMut<IsSwarmReady>,
mut ready_events: EventWriter<SwarmReadyEvent>, mut ready_events: EventWriter<SwarmReadyEvent>,
) { ) {

View file

@ -6,10 +6,10 @@ from typing import Optional
import re import re
METADATA_RS_DIR = get_dir_location( METADATA_RS_DIR = get_dir_location(
'../azalea-world/src/entity/metadata.rs') '../azalea-entity/src/metadata.rs')
DATA_RS_DIR = get_dir_location( DATA_RS_DIR = get_dir_location(
'../azalea-world/src/entity/data.rs') '../azalea-entity/src/data.rs')
def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings): def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings):
serializer_names: list[Optional[str]] = [None] * len(burger_dataserializers) serializer_names: list[Optional[str]] = [None] * len(burger_dataserializers)
@ -105,7 +105,7 @@ use super::{
SnifferState, VillagerData SnifferState, VillagerData
}; };
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
use azalea_core::{BlockPos, Direction, Particle, Vec3}; use azalea_core::{particle::Particle, BlockPos, Direction, Vec3};
use azalea_inventory::ItemSlot; use azalea_inventory::ItemSlot;
use bevy_ecs::{bundle::Bundle, component::Component}; use bevy_ecs::{bundle::Bundle, component::Component};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};

View file

@ -237,12 +237,16 @@ def burger_instruction_to_code(instructions: list[dict], index: int, generated_p
field_type_rs = None field_type_rs = None
field_comment = None field_comment = None
print('instruction', instruction) print('instruction', instruction, next_instruction, next_next_instruction)
# iterators # iterators
if instruction['operation'] == 'write' and instruction['field'].endswith('.size()') and next_instruction and next_instruction['type'] == 'Iterator' and next_next_instruction and next_next_instruction['operation'] == 'loop': if instruction['operation'] == 'write'\
obfuscated_field_name = instruction['field'].split('.')[ and instruction['field'].endswith('.size()')\
0] and next_instruction\
and next_instruction['type'] == 'Iterator'\
and next_next_instruction\
and next_next_instruction['operation'] == 'loop':
obfuscated_field_name = instruction['field'].split('.')[0]
field_name = mappings.get_field( field_name = mappings.get_field(
obfuscated_class_name, obfuscated_field_name) obfuscated_class_name, obfuscated_field_name)
@ -303,6 +307,11 @@ def burger_instruction_to_code(instructions: list[dict], index: int, generated_p
condition_types_rs = [] condition_types_rs = []
for condition_instruction in condition_instructions: for condition_instruction in condition_instructions:
print('condition_instruction', condition_instruction)
if 'type' not in condition_instruction:
# weird type, maybe it's a loop or something
condition_types_rs.append('todo!("weird type, maybe it\'s a loop or something")')
continue
condition_type_rs, is_var, this_uses, this_extra_code = burger_type_to_rust_type( condition_type_rs, is_var, this_uses, this_extra_code = burger_type_to_rust_type(
condition_instruction['type'], None, condition_instruction, mappings, obfuscated_class_name) condition_instruction['type'], None, condition_instruction, mappings, obfuscated_class_name)
condition_types_rs.append(condition_type_rs) condition_types_rs.append(condition_type_rs)
@ -381,8 +390,11 @@ def burger_field_to_type(field, mappings: Mappings, obfuscated_class_name: str,
if obfuscated_first in known_variable_types: if obfuscated_first in known_variable_types:
first_type = known_variable_types[obfuscated_first] first_type = known_variable_types[obfuscated_first]
else: else:
first_type = mappings.get_field_type( try:
obfuscated_class_name, obfuscated_first) first_type = mappings.get_field_type(
obfuscated_class_name, obfuscated_first)
except:
first_type = 'TODO'
first_obfuscated_class_name: Optional[str] = mappings.get_class_from_deobfuscated_name( first_obfuscated_class_name: Optional[str] = mappings.get_class_from_deobfuscated_name(
first_type) first_type)
if first_obfuscated_class_name: if first_obfuscated_class_name:

View file

@ -71,6 +71,11 @@ def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, inst
field_type_rs = 'todo!("fixed bitset")' field_type_rs = 'todo!("fixed bitset")'
elif burger_type == 'abstract': elif burger_type == 'abstract':
field_type_rs = 'todo!()' field_type_rs = 'todo!()'
elif burger_type == 'interface':
# depends on context
field_type_rs = 'todo!()'
elif burger_type == 'Iterator':
field_type_rs = 'todo!()'
elif burger_type == 'enum': elif burger_type == 'enum':
if not instruction or not mappings or not obfuscated_class_name: if not instruction or not mappings or not obfuscated_class_name:
field_type_rs = 'todo!("enum")' field_type_rs = 'todo!("enum")'
@ -152,7 +157,9 @@ def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, inst
uses.add('azalea_buf::UnsizedByteArray') uses.add('azalea_buf::UnsizedByteArray')
else: else:
raise Exception(f'Unknown field type: {burger_type}') print('instruction that we errored on:', instruction)
deobfuscated_class_name = mappings.get_class(obfuscated_class_name) if obfuscated_class_name else None
raise Exception(f'Unknown field type: {burger_type} ({deobfuscated_class_name or obfuscated_class_name})')
return field_type_rs, is_var, uses, extra_code return field_type_rs, is_var, uses, extra_code