diff --git a/Cargo.lock b/Cargo.lock index 1b4f1e745..5757ec123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom", @@ -68,17 +68,59 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] [[package]] name = "anyhow" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "as-any" @@ -88,13 +130,15 @@ checksum = "5b8a30a44e99a1c83ccb2a6298c563c888952a1c9134953db26876528f84c93a" [[package]] name = "async-channel" -version = "1.9.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 2.5.3", + "event-listener 5.0.0", + "event-listener-strategy 0.5.0", "futures-core", + "pin-project-lite", ] [[package]] @@ -106,8 +150,8 @@ dependencies = [ "async-lock", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.1.0", + "fastrand", + "futures-lite", "slab", ] @@ -117,8 +161,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" dependencies = [ - "event-listener 4.0.1", - "event-listener-strategy", + "event-listener 4.0.2", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -130,24 +174,24 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] name = "async-task" -version = "4.6.0" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -183,7 +227,7 @@ dependencies = [ "criterion", "derive_more", "futures", - "futures-lite 2.1.0", + "futures-lite", "nohash-hasher", "num-traits", "parking_lot", @@ -233,7 +277,7 @@ version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -264,7 +308,7 @@ version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -395,7 +439,7 @@ version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -420,8 +464,10 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_time", + "nohash-hasher", "once_cell", "parking_lot", + "smallvec", "tracing", "uuid", ] @@ -449,7 +495,7 @@ dependencies = [ "bytes", "flate2", "futures", - "futures-lite 2.1.0", + "futures-lite", "futures-util", "log", "once_cell", @@ -472,7 +518,7 @@ version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -491,7 +537,7 @@ version = "0.9.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -507,10 +553,10 @@ dependencies = [ "bevy_ecs", "criterion", "derive_more", - "enum-as-inner", "nohash-hasher", "once_cell", "parking_lot", + "rustc-hash", "serde", "serde_json", "simdnbt", @@ -536,9 +582,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -548,9 +594,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bevy_app" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41731817993f92e4363dd3335558e779e290bc71eefc0b5547052b85810907e" +checksum = "8bce3544afc010ffed39c136f6d5a9322d20d38df1394d468ba9106caa0434cb" dependencies = [ "bevy_derive", "bevy_ecs", @@ -564,20 +610,20 @@ dependencies = [ [[package]] name = "bevy_derive" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f484318350462c58ba3942a45a656c1fd6b6e484a6b6b7abc3a787ad1a51e500" +checksum = "028ae2a34678055185d7f1beebb1ebe6a8dcf3733e139e4ee1383a7f29ae8ba6" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] name = "bevy_ecs" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709fbd22f81fb681534cd913c41e1cd18b17143368743281195d7f024b61aea" +checksum = "b85406d5febbbdbcac4444ef61cd9a816f2f025ed692a3fc5439a32153070304" dependencies = [ "async-channel", "bevy_ecs_macros", @@ -586,7 +632,6 @@ dependencies = [ "bevy_tasks", "bevy_utils", "downcast-rs", - "event-listener 2.5.3", "fixedbitset", "rustc-hash", "serde", @@ -596,21 +641,21 @@ dependencies = [ [[package]] name = "bevy_ecs_macros" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8843aa489f159f25cdcd9fee75cd7d221a7098a71eaa72cb2d6b40ac4e3f1ba" +checksum = "9a3ce4b65d7c5f1990e729df75cec2ea6e2241b4a0c37b31c281a04c59c11b7b" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] name = "bevy_log" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc10ba1d225a8477b9e80a1bf797d8a8b8274e83c9b24fb4d9351aec9229755" +checksum = "cfd5bcc3531f8008897fb03cc8751b86d0d29ef94f8fd38b422f9603b7ae80d0" dependencies = [ "android_log-sys", "bevy_app", @@ -624,22 +669,22 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e566640c6b6dced73d2006c764c2cffebe1a82be4809486c4a5d7b4b50efed4d" +checksum = "ac4401c25b197e7c1455a4875a90b61bba047a9e8d290ce029082c818ab1a21c" dependencies = [ "proc-macro2", "quote", "rustc-hash", - "syn 2.0.43", + "syn 2.0.49", "toml_edit", ] [[package]] name = "bevy_math" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ddc2b76783939c530178f88e5711a1b01044d7b02db4033e2eb8b43b6cf4ec" +checksum = "6f312b1b8aa6d3965b65040b08e33efac030db3071f20b44f9da9c4c3dfcaf76" dependencies = [ "glam", "serde", @@ -647,15 +692,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ec20c8fafcdc196508ef5ccb4f0400a8d193cb61f7b14a36ed9a25ad423cf" +checksum = "86afa4a88ee06b10fe1e6f28a796ba2eedd16804717cbbb911df0cbb0cd6677b" [[package]] name = "bevy_reflect" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7921f15fc944c9c8ad01d7dbcea6505b8909c6655cd9382bab1407181556038" +checksum = "133dfab8d403d0575eeed9084e85780bbb449dcf75dd687448439117789b40a2" dependencies = [ "bevy_math", "bevy_ptr", @@ -665,43 +710,42 @@ dependencies = [ "erased-serde", "glam", "serde", - "smallvec", "smol_str", "thiserror", ] [[package]] name = "bevy_reflect_derive" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a8c5475f216e751ef4452a1306b00711f33d2d04d9f149e4c845dfeb6753a0" +checksum = "ce1679a4dfdb2c9ff24ca590914c3cec119d7c9e1b56fa637776913acc030386" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", "uuid", ] [[package]] name = "bevy_tasks" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fefa7fe0da8923525f7500e274f1bd60dbd79918a25cf7d0dfa0a6ba15c1cf" +checksum = "b20f243f6fc4c4ba10c2dbff891e947ddae947bb20b263f43e023558b35294bd" dependencies = [ "async-channel", "async-executor", "async-task", "concurrent-queue", - "futures-lite 1.13.0", + "futures-lite", "wasm-bindgen-futures", ] [[package]] name = "bevy_time" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6250d76eed3077128b6a3d004f9f198b01107800b9824051e32bb658054e837" +checksum = "9738901b6b251d2c9250542af7002d6f671401fc3b74504682697c5ec822f210" dependencies = [ "bevy_app", "bevy_ecs", @@ -713,31 +757,32 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7915222f4a08ccc782e08d10b751b42e5f9d786e697d0cb3fd09333cb7e8b6ea" +checksum = "94a06aca1c1863606416b892f4c79e300dbc6211b6690953269051a431c2cca0" dependencies = [ "ahash", "bevy_utils_proc_macros", "getrandom", "hashbrown 0.14.3", - "instant", "nonmax", "petgraph", + "smallvec", "thiserror", "tracing", "uuid", + "web-time", ] [[package]] name = "bevy_utils_proc_macros" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aafecc952b6b8eb1a93c12590bd867d25df2f4ae1033a01dfdfc3c35ebccfff" +checksum = "31ae98e9c0c08b0f5c90e22cd713201f759b98d4fd570b99867a695f8641859a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -769,9 +814,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" [[package]] name = "byteorder" @@ -817,9 +862,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "num-traits", "serde", @@ -887,6 +932,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -936,9 +987,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -990,11 +1041,10 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] @@ -1022,12 +1072,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" @@ -1110,20 +1157,30 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", ] [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1134,9 +1191,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.31" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +checksum = "55d05712b2d8d88102bc9868020c9e5c7a1f5527c452b9b97450a1d006140ba7" dependencies = [ "serde", ] @@ -1153,15 +1210,20 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "218a870470cce1469024e9fb66b901aa983929d81304a1cdb299f28118e550d5" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "event-listener" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712" +checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" dependencies = [ "concurrent-queue", "parking", @@ -1174,17 +1236,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener 4.0.1", + "event-listener 4.0.2", "pin-project-lite", ] [[package]] -name = "fastrand" -version = "1.9.0" +name = "event-listener-strategy" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" dependencies = [ - "instant", + "event-listener 5.0.0", + "pin-project-lite", ] [[package]] @@ -1274,26 +1337,11 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ - "fastrand 2.0.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -1308,7 +1356,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -1353,9 +1401,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", @@ -1372,9 +1420,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glam" -version = "0.24.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" dependencies = [ "bytemuck", "serde", @@ -1382,9 +1430,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -1392,7 +1440,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -1430,9 +1478,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" [[package]] name = "http" @@ -1544,9 +1592,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1561,18 +1609,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "ipnet" version = "2.9.0" @@ -1607,9 +1643,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1625,9 +1661,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -1837,9 +1873,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -1936,7 +1972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.2.3", ] [[package]] @@ -2008,9 +2044,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" +checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" dependencies = [ "autocfg", "indexmap 1.9.3", @@ -2018,18 +2054,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2095,13 +2131,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -2116,9 +2152,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -2139,9 +2175,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64", "bytes", @@ -2165,6 +2201,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -2342,29 +2379,29 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -2435,8 +2472,8 @@ dependencies = [ [[package]] name = "simdnbt" -version = "0.3.0" -source = "git+https://github.com/azalea-rs/simdnbt#c255ab673a7439c52dd299eac53ea59566cdda9d" +version = "0.4.0" +source = "git+https://github.com/azalea-rs/simdnbt#4594562ef0146431fd8c4405c753b32d1bb89c6a" dependencies = [ "byteorder", "flate2", @@ -2447,12 +2484,12 @@ dependencies = [ [[package]] name = "simdnbt-derive" -version = "0.3.0" -source = "git+https://github.com/azalea-rs/simdnbt#c255ab673a7439c52dd299eac53ea59566cdda9d" +version = "0.4.0" +source = "git+https://github.com/azalea-rs/simdnbt#4594562ef0146431fd8c4405c753b32d1bb89c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -2478,9 +2515,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" dependencies = [ "serde", ] @@ -2559,15 +2596,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2589,33 +2632,24 @@ dependencies = [ "libc", ] -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -2665,9 +2699,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2690,7 +2724,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -2725,11 +2759,11 @@ checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.3", "toml_datetime", "winnow", ] @@ -2759,7 +2793,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] @@ -2881,9 +2915,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2917,11 +2951,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", "md-5", @@ -2940,12 +2980,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" - [[package]] name = "walkdir" version = "2.4.0" @@ -2973,9 +3007,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2983,24 +3017,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -3010,9 +3044,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3020,28 +3054,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ "js-sys", "wasm-bindgen", @@ -3218,9 +3262,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -3252,7 +3296,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.49", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 diff --git a/README.md b/README.md index a110e76e7..70fa2b18c 100755 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ A collection of Rust crates for making Minecraft bots, clients, and tools.

- Azalea + Azalea

+ _Currently supported Minecraft version: `1.20.4`._ diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index 82420abea..72612454f 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -11,23 +11,23 @@ version = "0.9.0" [dependencies] azalea-buf = { path = "../azalea-buf", version = "0.9.0" } azalea-crypto = { path = "../azalea-crypto", version = "0.9.0" } -base64 = "0.21.5" -chrono = { version = "0.4.31", default-features = false, features = ["serde"] } +base64 = "0.21.7" +chrono = { version = "0.4.34", default-features = false, features = ["serde"] } tracing = "0.1.40" num-bigint = "0.4.4" -once_cell = "1.18.0" -reqwest = { version = "0.11.22", default-features = false, features = [ +once_cell = "1.19.0" +reqwest = { version = "0.11.24", default-features = false, features = [ "json", "rustls-tls", ] } rsa = "0.9.6" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" -thiserror = "1.0.50" -tokio = { version = "1.34.0", features = ["fs"] } -uuid = { version = "1.6.1", features = ["serde", "v3"] } +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" +thiserror = "1.0.57" +tokio = { version = "1.36.0", features = ["fs"] } +uuid = { version = "1.7.0", features = ["serde", "v3"] } md-5 = "0.10.6" [dev-dependencies] -env_logger = "0.10.1" -tokio = { version = "1.34.0", features = ["full"] } +env_logger = "0.11.2" +tokio = { version = "1.36.0", features = ["full"] } diff --git a/azalea-auth/src/auth.rs b/azalea-auth/src/auth.rs index 67a45aa3c..e0a75adff 100755 --- a/azalea-auth/src/auth.rs +++ b/azalea-auth/src/auth.rs @@ -140,6 +140,9 @@ pub async fn auth(email: &str, opts: AuthOpts) -> Result /// /// If you don't have a Microsoft auth token, you can get it from /// [`get_ms_link_code`] and then [`get_ms_auth_token`]. +/// +/// If you got the MSA token from your own app (as opposed to the default +/// Nintendo Switch one), you may have to prepend "d=" to the token. pub async fn get_minecraft_token( client: &reqwest::Client, msa: &str, @@ -235,6 +238,7 @@ pub struct MinecraftAuthResponse { pub struct GameOwnershipResponse { pub items: Vec, pub signature: String, + #[serde(rename = "keyId")] pub key_id: String, } @@ -418,9 +422,9 @@ async fn auth_with_xbox_live( "Properties": { "AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", - // i thought this was supposed to be d={} but it doesn't work for - // me when i add it ?????? - "RpsTicket": format!("{access_token}") + // this value should have "d=" prepended if you're using your own app (as opposed to + // the default nintendo switch one) + "RpsTicket": access_token }, "RelyingParty": "http://auth.xboxlive.com", "TokenType": "JWT" diff --git a/azalea-block/azalea-block-macros/Cargo.toml b/azalea-block/azalea-block-macros/Cargo.toml index 720e44804..7489a93ea 100644 --- a/azalea-block/azalea-block-macros/Cargo.toml +++ b/azalea-block/azalea-block-macros/Cargo.toml @@ -12,6 +12,6 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "1.0.70" -quote = "1.0.33" -syn = "2.0.39" +proc-macro2 = "1.0.78" +quote = "1.0.35" +syn = "2.0.49" diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 3b983fb43..427da03b4 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -8,7 +8,7 @@ use quote::quote; use std::collections::HashMap; use std::fmt::Write; use syn::{ - self, braced, + braced, ext::IdentExt, parenthesized, parse::{Parse, ParseStream, Result}, diff --git a/azalea-block/src/generated.rs b/azalea-block/src/generated.rs index 97236b4ba..5267c9a18 100755 --- a/azalea-block/src/generated.rs +++ b/azalea-block/src/generated.rs @@ -5380,4 +5380,3 @@ make_block_states! { }, } } - diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index 983de5791..2a46fd010 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +#![feature(trait_upcasting)] mod behavior; mod generated; @@ -58,6 +59,8 @@ impl BlockState { state_id <= Self::max_state() } + /// Returns true if the block is air. This only checks for normal air, not + /// other types like cave air. #[inline] pub fn is_air(&self) -> bool { self == &Self::AIR diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml index f15525b7a..eb4325802 100644 --- a/azalea-buf/Cargo.toml +++ b/azalea-buf/Cargo.toml @@ -9,13 +9,13 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.9.0" } byteorder = "^1.5.0" tracing = "0.1.40" serde_json = { version = "^1.0", optional = true } -thiserror = "1.0.50" -uuid = "^1.6.1" +thiserror = "1.0.57" +uuid = "^1.7.0" [features] serde_json = ["dep:serde_json"] diff --git a/azalea-buf/azalea-buf-macros/Cargo.toml b/azalea-buf/azalea-buf-macros/Cargo.toml index facc04b1e..0bef0aca2 100644 --- a/azalea-buf/azalea-buf-macros/Cargo.toml +++ b/azalea-buf/azalea-buf-macros/Cargo.toml @@ -11,6 +11,6 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "^1.0.70" -quote = "^1.0.33" -syn = { version = "^2.0.39", features = ["extra-traits"] } +proc-macro2 = "^1.0.78" +quote = "^1.0.35" +syn = { version = "^2.0.49", features = ["extra-traits"] } diff --git a/azalea-buf/azalea-buf-macros/src/lib.rs b/azalea-buf/azalea-buf-macros/src/lib.rs index 4089b4b7a..4d17daa6d 100755 --- a/azalea-buf/azalea-buf-macros/src/lib.rs +++ b/azalea-buf/azalea-buf-macros/src/lib.rs @@ -3,7 +3,7 @@ mod write; use proc_macro::TokenStream; use quote::quote; -use syn::{self, parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(McBufReadable, attributes(var))] pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream { diff --git a/azalea-buf/azalea-buf-macros/src/read.rs b/azalea-buf/azalea-buf-macros/src/read.rs index 010c7f57f..c3e9595c7 100644 --- a/azalea-buf/azalea-buf-macros/src/read.rs +++ b/azalea-buf/azalea-buf-macros/src/read.rs @@ -1,5 +1,5 @@ use quote::{quote, ToTokens}; -use syn::{self, punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident}; +use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident}; fn read_named_fields( named: &Punctuated, diff --git a/azalea-buf/azalea-buf-macros/src/write.rs b/azalea-buf/azalea-buf-macros/src/write.rs index bc493f382..35c2d49a1 100644 --- a/azalea-buf/azalea-buf-macros/src/write.rs +++ b/azalea-buf/azalea-buf-macros/src/write.rs @@ -1,6 +1,6 @@ use proc_macro2::Span; use quote::{quote, ToTokens}; -use syn::{self, punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident}; +use syn::{punctuated::Punctuated, token::Comma, Data, Field, FieldsNamed, Ident}; fn write_named_fields( named: &Punctuated, diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml index 247285c51..2536bfaa7 100644 --- a/azalea-chat/Cargo.toml +++ b/azalea-chat/Cargo.toml @@ -19,9 +19,9 @@ azalea-buf = { path = "../azalea-buf", features = [ "serde_json", ], version = "0.9.0", optional = true } azalea-language = { path = "../azalea-language", version = "0.9.0" } -simdnbt = { version = "0.3", optional = true, git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", optional = true, git = "https://github.com/azalea-rs/simdnbt" } tracing = "0.1.40" -once_cell = "1.18.0" +once_cell = "1.19.0" serde = { version = "^1.0", features = ["derive"] } -serde_json = "^1.0.108" +serde_json = "^1.0.113" azalea-registry = { path = "../azalea-registry", version = "0.9.0", optional = true } diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 807d0b1aa..d301aba15 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -11,6 +11,7 @@ use serde::{de, Deserialize, Deserializer, Serialize}; #[cfg(feature = "simdnbt")] use simdnbt::{Deserialize as _, FromNbtTag as _, Serialize as _}; use std::fmt::Display; +use tracing::{trace, warn}; /// A chat component, basically anything you can see in chat. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)] @@ -313,22 +314,60 @@ impl simdnbt::FromNbtTag for FormattedText { // if it's a string component with no styling and no siblings, just add // a string to with_array otherwise add the // component to the array - let c = FormattedText::from_nbt_tag( + if let Some(primitive) = item.get("") { + // minecraft does this sometimes, for example + // for the /give system messages + match primitive { + simdnbt::borrow::NbtTag::Byte(b) => { + // interpreted as boolean + with_array.push(StringOrComponent::String( + if *b != 0 { "true" } else { "false" }.to_string(), + )); + } + simdnbt::borrow::NbtTag::Short(s) => { + with_array.push(StringOrComponent::String(s.to_string())); + } + simdnbt::borrow::NbtTag::Int(i) => { + with_array.push(StringOrComponent::String(i.to_string())); + } + simdnbt::borrow::NbtTag::Long(l) => { + with_array.push(StringOrComponent::String(l.to_string())); + } + simdnbt::borrow::NbtTag::Float(f) => { + with_array.push(StringOrComponent::String(f.to_string())); + } + simdnbt::borrow::NbtTag::Double(d) => { + with_array.push(StringOrComponent::String(d.to_string())); + } + simdnbt::borrow::NbtTag::String(s) => { + with_array.push(StringOrComponent::String(s.to_string())); + } + _ => { + warn!("couldn't parse {item:?} as FormattedText because it has a disallowed primitive"); + with_array.push(StringOrComponent::String("?".to_string())); + } + } + } else if let Some(c) = FormattedText::from_nbt_tag( &simdnbt::borrow::NbtTag::Compound(item.clone()), - )?; - if let FormattedText::Text(text_component) = c { - if text_component.base.siblings.is_empty() - && text_component.base.style.is_empty() - { - with_array.push(StringOrComponent::String(text_component.text)); - continue; + ) { + if let FormattedText::Text(text_component) = c { + if text_component.base.siblings.is_empty() + && text_component.base.style.is_empty() + { + with_array + .push(StringOrComponent::String(text_component.text)); + continue; + } } + with_array.push(StringOrComponent::FormattedText( + FormattedText::from_nbt_tag( + &simdnbt::borrow::NbtTag::Compound(item.clone()), + )?, + )); + } else { + warn!("couldn't parse {item:?} as FormattedText"); + with_array.push(StringOrComponent::String("?".to_string())); } - with_array.push(StringOrComponent::FormattedText( - FormattedText::from_nbt_tag(&simdnbt::borrow::NbtTag::Compound( - item.clone(), - ))?, - )); } component = FormattedText::Translatable(TranslatableComponent::new( translate, with_array, @@ -344,24 +383,21 @@ impl simdnbt::FromNbtTag for FormattedText { // object = GsonHelper.getAsJsonObject(jsonObject, "score"); if score.get("name").is_none() || score.get("objective").is_none() { // A score component needs at least a name and an objective - tracing::trace!("A score component needs at least a name and an objective"); + trace!("A score component needs at least a name and an objective"); return None; } // TODO, score text components aren't yet supported return None; } else if compound.get("selector").is_some() { // selector text components aren't yet supported - tracing::trace!("selector text components aren't yet supported"); + trace!("selector text components aren't yet supported"); return None; } else if compound.get("keybind").is_some() { // keybind text components aren't yet supported - tracing::trace!("keybind text components aren't yet supported"); + trace!("keybind text components aren't yet supported"); return None; } else { - let Some(_nbt) = compound.get("nbt") else { - // Don't know how to turn 'nbt' into a FormattedText - return None; - }; + let _nbt = compound.get("nbt")?; let _separator = FormattedText::parse_separator_nbt(compound)?; let _interpret = match compound.get("interpret") { @@ -369,7 +405,7 @@ impl simdnbt::FromNbtTag for FormattedText { None => false, }; if let Some(_block) = compound.get("block") {} - // nbt text components aren't yet supported + trace!("nbt text components aren't yet supported"); return None; } if let Some(extra) = compound.get("extra") { diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index b18d1762d..ff3b67e85 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -9,10 +9,10 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } -reqwest = { version = "0.11.22", default-features = false } -anyhow = "1.0.75" -async-trait = "0.1.74" +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } +reqwest = { version = "0.11.24", default-features = false } +anyhow = "1.0.79" +async-trait = "0.1.77" azalea-auth = { path = "../azalea-auth", version = "0.9.0" } azalea-block = { path = "../azalea-block", version = "0.9.0" } azalea-chat = { path = "../azalea-chat", version = "0.9.0" } @@ -23,27 +23,27 @@ azalea-buf = { path = "../azalea-buf", version = "0.9.0" } azalea-protocol = { path = "../azalea-protocol", version = "0.9.0" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } azalea-world = { path = "../azalea-world", version = "0.9.0" } -bevy_app = "0.12.1" -bevy_ecs = "0.12.1" -bevy_log = { version = "0.12.1", optional = true } -bevy_tasks = "0.12.1" -bevy_time = "0.12.1" +bevy_app = "0.13.0" +bevy_ecs = "0.13.0" +bevy_log = { version = "0.13.0", optional = true } +bevy_tasks = "0.13.0" +bevy_time = "0.13.0" azalea-inventory = { path = "../azalea-inventory", version = "0.9.0" } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } -futures = "0.3.29" +futures = "0.3.30" tracing = "0.1.40" nohash-hasher = "0.2.0" -once_cell = "1.18.0" +once_cell = "1.19.0" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } -regex = "1.10.2" -thiserror = "^1.0.50" -tokio = { version = "^1.34.0", features = ["sync"] } -uuid = "^1.6.1" +regex = "1.10.3" +thiserror = "^1.0.57" +tokio = { version = "^1.36.0", features = ["sync"] } +uuid = "^1.7.0" azalea-entity = { version = "0.9.0", path = "../azalea-entity" } -serde_json = "1.0.108" -serde = "1.0.192" +serde_json = "1.0.113" +serde = "1.0.196" +minecraft_folder_path = "0.1.2" socks5-impl = "0.5.6" -minecraft_folder_path = "0.1.1" [features] default = ["log"] diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 911b70a75..882de1ad1 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -2,6 +2,7 @@ use crate::{ attack::{self, AttackPlugin}, chat::ChatPlugin, chunks::{ChunkBatchInfo, ChunkPlugin}, + configuration::ConfigurationPlugin, disconnect::{DisconnectEvent, DisconnectPlugin}, events::{Event, EventPlugin, LocalPlayerEvents}, interact::{CurrentSequenceNumber, InteractPlugin}, @@ -13,7 +14,6 @@ use crate::{ mining::{self, MinePlugin}, movement::{LastSentLookDirection, PhysicsState, PlayerMovePlugin}, packet_handling::{ - game::{handle_send_packet_event, SendPacketEvent}, login::{self, LoginSendPacketQueue}, PacketHandlerPlugin, }, @@ -25,9 +25,8 @@ use crate::{ }; use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError}; -use azalea_buf::McBufWritable; use azalea_chat::FormattedText; -use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; +use azalea_core::{position::Vec3, tick::GameTick}; use azalea_entity::{ indexing::{EntityIdIndex, EntityUuidIndex}, metadata::Health, @@ -39,7 +38,6 @@ use azalea_protocol::{ packets::{ configuration::{ serverbound_client_information_packet::ClientInformation, - serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, ClientboundConfigurationPacket, ServerboundConfigurationPacket, }, game::ServerboundGamePacket, @@ -248,20 +246,8 @@ impl Client { let (mut conn, game_profile) = Self::handshake(ecs_lock.clone(), entity, 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( - ServerboundCustomPayloadPacket { - identifier: ResourceLocation::new("brand"), - data: brand_data.into(), - } - .get(), - ) - .await?; - } + // note that we send the proper packets in + // crate::configuration::handle_in_configuration_state let (read_conn, write_conn) = conn.into_split(); let (read_conn, write_conn) = (read_conn.raw, write_conn.raw); @@ -681,10 +667,8 @@ impl Plugin for AzaleaPlugin { death_event, // add GameProfileComponent when we get an AddPlayerEvent retroactively_add_game_profile_component.after(EntityUpdateSet::Index), - handle_send_packet_event, ), ) - .add_event::() .init_resource::() .init_resource::(); } @@ -841,6 +825,7 @@ impl PluginGroup for DefaultPlugins { .add(MinePlugin) .add(AttackPlugin) .add(ChunkPlugin) + .add(ConfigurationPlugin) .add(TickBroadcastPlugin); #[cfg(feature = "log")] { diff --git a/azalea-client/src/configuration.rs b/azalea-client/src/configuration.rs new file mode 100644 index 000000000..99b97f60d --- /dev/null +++ b/azalea-client/src/configuration.rs @@ -0,0 +1,52 @@ +use azalea_buf::McBufWritable; +use azalea_core::resource_location::ResourceLocation; +use azalea_protocol::packets::configuration::{ + serverbound_client_information_packet::{ + ClientInformation, ServerboundClientInformationPacket, + }, + serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, +}; +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; + +use crate::{ + client::InConfigurationState, packet_handling::configuration::SendConfigurationPacketEvent, +}; + +pub struct ConfigurationPlugin; +impl Plugin for ConfigurationPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + handle_in_configuration_state + .after(crate::packet_handling::configuration::handle_send_packet_event), + ); + } +} + +fn handle_in_configuration_state( + query: Query<(Entity, &ClientInformation), Added>, + mut send_packet_events: EventWriter, +) { + for (entity, client_information) in query.iter() { + let mut brand_data = Vec::new(); + // they don't have to know :) + "vanilla".write_into(&mut brand_data).unwrap(); + send_packet_events.send(SendConfigurationPacketEvent { + entity, + packet: ServerboundCustomPayloadPacket { + identifier: ResourceLocation::new("brand"), + data: brand_data.into(), + } + .get(), + }); + + send_packet_events.send(SendConfigurationPacketEvent { + entity, + packet: ServerboundClientInformationPacket { + information: client_information.clone(), + } + .get(), + }); + } +} diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs index 42b7b0cac..be892ee56 100644 --- a/azalea-client/src/entity_query.rs +++ b/azalea-client/src/entity_query.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use bevy_ecs::{ component::Component, entity::Entity, - query::{ROQueryItem, ReadOnlyWorldQuery, WorldQuery}, + query::QueryData, + query::{QueryFilter, ROQueryItem}, world::World, }; use parking_lot::Mutex; @@ -22,13 +23,13 @@ impl Client { /// .is_some(); /// # } /// ``` - pub fn query<'w, Q: WorldQuery>(&self, ecs: &'w mut World) -> ::Item<'w> { - ecs.query::() + pub fn query<'w, D: QueryData>(&self, ecs: &'w mut World) -> D::Item<'w> { + ecs.query::() .get_mut(ecs, self.entity) .unwrap_or_else(|_| { panic!( "Our client is missing a required component {:?}", - std::any::type_name::() + std::any::type_name::() ) }) } @@ -58,7 +59,7 @@ impl Client { /// ``` /// /// [`Entity`]: bevy_ecs::entity::Entity - pub fn entity_by( + pub fn entity_by( &mut self, predicate: impl EntityPredicate, ) -> Option { @@ -93,14 +94,14 @@ impl Client { } } -pub trait EntityPredicate { +pub trait EntityPredicate { fn find(&self, ecs_lock: Arc>) -> Option; } impl EntityPredicate for F where F: Fn(&ROQueryItem) -> bool, - Q: ReadOnlyWorldQuery, - Filter: ReadOnlyWorldQuery, + Q: QueryData, + Filter: QueryFilter, { fn find(&self, ecs_lock: Arc>) -> Option { let mut ecs = ecs_lock.lock(); @@ -114,8 +115,8 @@ where // impl<'a, F, Q1, Q2> EntityPredicate<'a, (Q1, Q2)> for F // where // F: Fn(&::Item<'_>, &::Item<'_>) -> -// bool, Q1: ReadOnlyWorldQuery, -// Q2: ReadOnlyWorldQuery, +// bool, Q1: QueryFilter, +// Q2: QueryFilter, // { // fn find(&self, ecs: &mut Ecs) -> Option { // // (self)(query) diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs index 026e94caa..8a3aa49b5 100644 --- a/azalea-client/src/interact.rs +++ b/azalea-client/src/interact.rs @@ -156,7 +156,7 @@ pub fn handle_block_interact_event( sequence: sequence_number.0, } .get(), - }) + }); } } diff --git a/azalea-client/src/inventory.rs b/azalea-client/src/inventory.rs index 527feae78..4bfed69f2 100644 --- a/azalea-client/src/inventory.rs +++ b/azalea-client/src/inventory.rs @@ -90,6 +90,9 @@ pub struct InventoryComponent { /// The current container menu that the player has open. If no container is /// open, this will be `None`. pub container_menu: Option, + /// The custom name of the menu that's currently open. This is Some when + /// `container_menu` is Some. + pub container_menu_title: Option, /// The item that is currently held by the cursor. `Slot::Empty` if nothing /// is currently being held. /// @@ -566,6 +569,7 @@ impl Default for InventoryComponent { inventory_menu: Menu::Player(azalea_inventory::Player::default()), id: 0, container_menu: None, + container_menu_title: None, carried: ItemSlot::Empty, state_id: 0, quick_craft_status: QuickCraftStatusKind::Start, @@ -593,6 +597,7 @@ fn handle_menu_opened_event( let mut inventory = query.get_mut(event.entity).unwrap(); inventory.id = event.window_id as u8; inventory.container_menu = Some(Menu::from_kind(event.menu_type)); + inventory.container_menu_title = Some(event.title.clone()); } } @@ -651,6 +656,7 @@ pub fn handle_client_side_close_container_event( let mut inventory = query.get_mut(event.entity).unwrap(); inventory.container_menu = None; inventory.id = 0; + inventory.container_menu_title = None; } } @@ -702,7 +708,7 @@ pub fn handle_container_click_event( carried_item: inventory.carried.clone(), } .get(), - }) + }); } } diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 1eaf46057..41399ce97 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -13,6 +13,7 @@ pub mod attack; pub mod chat; pub mod chunks; mod client; +pub mod configuration; pub mod disconnect; mod entity_query; mod events; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 3b3ae89b3..b0582d8ba 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -5,7 +5,7 @@ use azalea_core::game_type::GameMode; use azalea_entity::Dead; use azalea_protocol::packets::game::clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket; use azalea_world::{Instance, PartialInstance}; -use bevy_ecs::{component::Component, entity::Entity, prelude::*, query::Added, system::Query}; +use bevy_ecs::{component::Component, prelude::*}; use derive_more::{Deref, DerefMut}; use parking_lot::RwLock; use thiserror::Error; diff --git a/azalea-client/src/mining.rs b/azalea-client/src/mining.rs index d2e66ca84..f0b86db19 100644 --- a/azalea-client/src/mining.rs +++ b/azalea-client/src/mining.rs @@ -253,7 +253,7 @@ fn handle_start_mining_block_with_direction_event( entity: event.entity, position: event.position, destroy_stage: mine_progress.destroy_stage(), - }) + }); } send_packet_events.send(SendPacketEvent { @@ -572,7 +572,7 @@ fn continue_mining_block( entity, position: mining.pos, direction: mining.dir, - }) + }); } swing_arm_events.send(SwingArmEvent { entity }); diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index 2f70a40ee..ba47b7c84 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -86,9 +86,7 @@ impl Client { /// Returns whether the player will try to jump next tick. pub fn jumping(&self) -> bool { - let mut ecs = self.ecs.lock(); - let jumping_ref = self.query::<&Jumping>(&mut ecs); - **jumping_ref + *self.component::() } /// Sets the direction the client is looking. `y_rot` is yaw (looking to the @@ -101,6 +99,14 @@ impl Client { (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot); } + + /// Returns the direction the client is looking. The first value is the y + /// rotation (ie. yaw, looking to the side) and the second value is the x + /// rotation (ie. pitch, looking up and down). + pub fn direction(&self) -> (f32, f32) { + let look_direction = self.component::(); + (look_direction.y_rot, look_direction.x_rot) + } } /// A component that contains the look direction that was last sent over the diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index ddf77a52f..da5ce57fe 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -6,7 +6,9 @@ use azalea_protocol::packets::configuration::serverbound_finish_configuration_pa 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::configuration::{ + ClientboundConfigurationPacket, ServerboundConfigurationPacket, +}; use azalea_protocol::packets::ConnectionProtocol; use azalea_protocol::read::deserialize_packet; use azalea_world::Instance; @@ -201,3 +203,25 @@ pub fn process_packet_events(ecs: &mut World) { } } } + +/// An event for sending a packet to the server while we're in the +/// `configuration` state. +#[derive(Event)] +pub struct SendConfigurationPacketEvent { + pub entity: Entity, + pub packet: ServerboundConfigurationPacket, +} + +pub fn handle_send_packet_event( + mut send_packet_events: EventReader, + mut query: Query<&mut RawConnection>, +) { + for event in send_packet_events.read() { + if let Ok(raw_connection) = query.get_mut(event.entity) { + // debug!("Sending packet: {:?}", event.packet); + if let Err(e) = raw_connection.write_packet(event.packet.clone()) { + error!("Failed to send packet: {e}"); + } + } + } +} diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 91a3c835e..30b736c08 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -419,7 +419,6 @@ pub fn process_packet_events(ecs: &mut World) { debug!("Got recipe packet"); } ClientboundGamePacket::PlayerPosition(p) => { - // TODO: reply with teleport confirm debug!("Got player position packet {p:?}"); #[allow(clippy::type_complexity)] @@ -748,6 +747,7 @@ pub fn process_packet_events(ecs: &mut World) { { warn!("{e}"); } + commands_system_state.apply(world); }); }), }); @@ -838,6 +838,10 @@ pub fn process_packet_events(ecs: &mut World) { if let Some(entity) = entity { let new_pos = p.position; + let new_look_direction = LookDirection { + x_rot: (p.x_rot as i32 * 360) as f32 / 256., + y_rot: (p.y_rot as i32 * 360) as f32 / 256., + }; commands.entity(entity).add(RelativeEntityUpdate { partial_world: instance_holder.partial_instance.clone(), update: Box::new(move |entity| { @@ -845,6 +849,10 @@ pub fn process_packet_events(ecs: &mut World) { if new_pos != **position { **position = new_pos; } + let mut look_direction = entity.get_mut::().unwrap(); + if new_look_direction != *look_direction { + *look_direction = new_look_direction; + } }), }); } else { @@ -902,6 +910,11 @@ pub fn process_packet_events(ecs: &mut World) { if let Some(entity) = entity { let delta = p.delta.clone(); + let new_look_direction = LookDirection { + x_rot: (p.x_rot as i32 * 360) as f32 / 256., + y_rot: (p.y_rot as i32 * 360) as f32 / 256., + }; + commands.entity(entity).add(RelativeEntityUpdate { partial_world: instance_holder.partial_instance.clone(), update: Box::new(move |entity_mut| { @@ -910,6 +923,10 @@ pub fn process_packet_events(ecs: &mut World) { if new_pos != **position { **position = new_pos; } + let mut look_direction = entity_mut.get_mut::().unwrap(); + if new_look_direction != *look_direction { + *look_direction = new_look_direction; + } }), }); } else { @@ -922,8 +939,39 @@ pub fn process_packet_events(ecs: &mut World) { system_state.apply(ecs); } - ClientboundGamePacket::MoveEntityRot(_p) => { - // debug!("Got move entity rot packet {p:?}"); + ClientboundGamePacket::MoveEntityRot(p) => { + let mut system_state: SystemState<( + Commands, + Query<(&EntityIdIndex, &InstanceHolder)>, + )> = SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let (entity_id_index, instance_holder) = query.get_mut(player_entity).unwrap(); + + let entity = entity_id_index.get(&MinecraftEntityId(p.entity_id)); + + if let Some(entity) = entity { + let new_look_direction = LookDirection { + x_rot: (p.x_rot as i32 * 360) as f32 / 256., + y_rot: (p.y_rot as i32 * 360) as f32 / 256., + }; + + commands.entity(entity).add(RelativeEntityUpdate { + partial_world: instance_holder.partial_instance.clone(), + update: Box::new(move |entity_mut| { + let mut look_direction = entity_mut.get_mut::().unwrap(); + if new_look_direction != *look_direction { + *look_direction = new_look_direction; + } + }), + }); + } else { + warn!( + "Got move entity rot packet for unknown entity id {}", + p.entity_id + ); + } + + system_state.apply(ecs); } ClientboundGamePacket::KeepAlive(p) => { debug!("Got keep alive packet {p:?} for {player_entity:?}"); @@ -1176,7 +1224,7 @@ pub fn process_packet_events(ecs: &mut World) { let mut client_side_close_container_events = system_state.get_mut(ecs); client_side_close_container_events.send(ClientSideCloseContainerEvent { entity: player_entity, - }) + }); } ClientboundGamePacket::Cooldown(_) => {} ClientboundGamePacket::CustomChatCompletions(_) => {} @@ -1225,7 +1273,7 @@ pub fn process_packet_events(ecs: &mut World) { window_id: p.container_id, menu_type: p.menu_type, title: p.title.to_owned(), - }) + }); } ClientboundGamePacket::OpenSignEditor(_) => {} ClientboundGamePacket::Ping(p) => { diff --git a/azalea-client/src/packet_handling/mod.rs b/azalea-client/src/packet_handling/mod.rs index 9823035d3..19de27eb5 100644 --- a/azalea-client/src/packet_handling/mod.rs +++ b/azalea-client/src/packet_handling/mod.rs @@ -49,10 +49,22 @@ impl Plugin for PacketHandlerPlugin { login::process_packet_events, ), ) - .add_systems(Update, death_event_on_0_health.before(death_listener)) + .add_systems( + Update, + ( + ( + configuration::handle_send_packet_event, + game::handle_send_packet_event, + ) + .chain(), + death_event_on_0_health.before(death_listener), + ), + ) // we do this instead of add_event so we can handle the events ourselves .init_resource::>() .init_resource::>() + .add_event::() + .add_event::() .add_event::() .add_event::() .add_event::() diff --git a/azalea-client/src/raw_connection.rs b/azalea-client/src/raw_connection.rs index bcb1ada04..9f7a85f12 100644 --- a/azalea-client/src/raw_connection.rs +++ b/azalea-client/src/raw_connection.rs @@ -10,7 +10,7 @@ use azalea_protocol::{ use bevy_ecs::prelude::*; use parking_lot::Mutex; use thiserror::Error; -use tokio::sync::mpsc; +use tokio::sync::mpsc::{self, error::SendError}; use tracing::error; /// A component for clients that can read and write packets to the server. This @@ -51,6 +51,12 @@ pub enum WritePacketError { }, #[error(transparent)] Encoding(#[from] azalea_protocol::write::PacketEncodeError), + #[error(transparent)] + SendError { + #[from] + #[backtrace] + source: SendError>, + }, } impl RawConnection { @@ -88,11 +94,9 @@ impl RawConnection { } } - pub fn write_raw_packet(&self, raw_packet: Vec) { - self.writer - .outgoing_packets_sender - .send(raw_packet) - .unwrap(); + pub fn write_raw_packet(&self, raw_packet: Vec) -> Result<(), WritePacketError> { + self.writer.outgoing_packets_sender.send(raw_packet)?; + Ok(()) } /// Write the packet with the given state to the server. @@ -106,7 +110,8 @@ impl RawConnection { packet: P, ) -> Result<(), WritePacketError> { let raw_packet = serialize_packet(&packet)?; - self.write_raw_packet(raw_packet); + self.write_raw_packet(raw_packet)?; + Ok(()) } diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index 2b862d3ce..d294b755b 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -9,16 +9,16 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-buf = { path = "../azalea-buf", version = "0.9.0" } azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } -bevy_ecs = { version = "0.12.1", default-features = false, optional = true } +bevy_ecs = { version = "0.13.0", default-features = false, optional = true } nohash-hasher = "0.2.0" -num-traits = "0.2.17" +num-traits = "0.2.18" serde = { version = "^1.0", optional = true } -uuid = "^1.6.1" -serde_json = "^1.0.108" +uuid = "^1.7.0" +serde_json = "^1.0.113" tracing = "0.1.40" [features] diff --git a/azalea-core/src/cursor3d.rs b/azalea-core/src/cursor3d.rs index 0594a80a6..dc96d890d 100755 --- a/azalea-core/src/cursor3d.rs +++ b/azalea-core/src/cursor3d.rs @@ -3,9 +3,7 @@ use crate::position::BlockPos; pub struct Cursor3d { index: usize, - origin_x: i32, - origin_y: i32, - origin_z: i32, + origin: BlockPos, width: usize, height: usize, @@ -14,6 +12,12 @@ pub struct Cursor3d { end: usize, } +impl Cursor3d { + pub fn origin(&self) -> BlockPos { + self.origin + } +} + impl Iterator for Cursor3d { type Item = CursorIteration; @@ -40,9 +44,9 @@ impl Iterator for Cursor3d { Some(CursorIteration { pos: BlockPos { - x: self.origin_x + x as i32, - y: self.origin_y + y as i32, - z: self.origin_z + z as i32, + x: self.origin.x + x as i32, + y: self.origin.y + y as i32, + z: self.origin.z + z as i32, }, iteration_type: iteration_type.into(), }) @@ -64,30 +68,21 @@ pub struct CursorIteration { } impl Cursor3d { - pub fn new( - origin_x: i32, - origin_y: i32, - origin_z: i32, - end_x: i32, - end_y: i32, - end_z: i32, - ) -> Self { - let width = (end_x - origin_x + 1) + pub fn new(origin: BlockPos, end: BlockPos) -> Self { + let width = (end.x - origin.x + 1) .try_into() .expect("Impossible width."); - let height = (end_y - origin_y + 1) + let height = (end.y - origin.y + 1) .try_into() .expect("Impossible height."); - let depth = (end_z - origin_z + 1) + let depth = (end.z - origin.z + 1) .try_into() .expect("Impossible depth."); Self { index: 0, - origin_x, - origin_y, - origin_z, + origin, width, height, diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs index 99f0c0fea..7c7a43a82 100644 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -1,6 +1,6 @@ use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufWritable}; use std::io::{Cursor, Write}; -use tracing::warn; +use tracing::debug; /// A Minecraft gamemode, like survival or creative. #[derive(Hash, Copy, Clone, Debug, Default, Eq, PartialEq)] @@ -96,11 +96,11 @@ impl McBufReadable for GameMode { fn read_from(buf: &mut Cursor<&[u8]>) -> Result { let id = u32::var_read_from(buf)?; let id = id.try_into().unwrap_or_else(|_| { - warn!("Unknown game mode id {id}, defaulting to survival"); + debug!("Unknown game mode id {id}, defaulting to survival"); 0 }); Ok(GameMode::from_id(id).unwrap_or_else(|| { - warn!("Unknown game mode id {id}, defaulting to survival"); + debug!("Unknown game mode id {id}, defaulting to survival"); GameMode::Survival })) } diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index e98640357..e2a9c4cc2 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -186,6 +186,25 @@ macro_rules! vec3_impl { } } } + + impl From<($type, $type, $type)> for $name { + #[inline] + fn from(pos: ($type, $type, $type)) -> Self { + Self::new(pos.0, pos.1, pos.2) + } + } + impl From<&($type, $type, $type)> for $name { + #[inline] + fn from(pos: &($type, $type, $type)) -> Self { + Self::new(pos.0, pos.1, pos.2) + } + } + impl From<$name> for ($type, $type, $type) { + #[inline] + fn from(pos: $name) -> Self { + (pos.x, pos.y, pos.z) + } + } }; } @@ -545,6 +564,12 @@ impl fmt::Display for BlockPos { write!(f, "{} {} {}", self.x, self.y, self.z) } } +impl fmt::Display for Vec3 { + /// Display a position as `x y z`. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.x, self.y, self.z) + } +} const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25 const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH; diff --git a/azalea-core/src/tick.rs b/azalea-core/src/tick.rs index db10d80ad..c6f02c129 100644 --- a/azalea-core/src/tick.rs +++ b/azalea-core/src/tick.rs @@ -1,4 +1,8 @@ use bevy_ecs::schedule::ScheduleLabel; +/// A Bevy schedule that runs every Minecraft game tick, i.e. every 50ms. +/// +/// Many client systems run on this schedule, the most important one being +/// physics. #[derive(ScheduleLabel, Hash, Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct GameTick; diff --git a/azalea-crypto/Cargo.toml b/azalea-crypto/Cargo.toml index e15374f97..04423f2a6 100644 --- a/azalea-crypto/Cargo.toml +++ b/azalea-crypto/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/azalea-rs/azalea/tree/main/azalea-crypto" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aes = "0.8.3" +aes = "0.8.4" azalea-buf = { path = "../azalea-buf", version = "0.9.0" } cfb8 = "0.8.1" num-bigint = "^0.4.4" @@ -18,7 +18,7 @@ rsa = { version = "0.9.6", features = ["sha2"] } rsa_public_encrypt_pkcs1 = "0.4.0" sha-1 = "^0.10.1" sha2 = "0.10.8" -uuid = "^1.6.1" +uuid = "^1.7.0" [dev-dependencies] criterion = { version = "^0.5.1", features = ["html_reports"] } diff --git a/azalea-entity/Cargo.toml b/azalea-entity/Cargo.toml index b3762da83..c35323bbf 100644 --- a/azalea-entity/Cargo.toml +++ b/azalea-entity/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-block = { version = "0.9.0", path = "../azalea-block" } azalea-buf = { version = "0.9.0", path = "../azalea-buf" } azalea-chat = { version = "0.9.0", path = "../azalea-chat", features = [ @@ -19,12 +19,12 @@ azalea-core = { version = "0.9.0", path = "../azalea-core" } azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-registry = { version = "0.9.0", path = "../azalea-registry" } azalea-world = { version = "0.9.0", path = "../azalea-world" } -bevy_app = "0.12.1" -bevy_ecs = "0.12.1" +bevy_app = "0.13.0" +bevy_ecs = "0.13.0" derive_more = "0.99.17" enum-as-inner = "0.6.0" tracing = "0.1.40" nohash-hasher = "0.2.0" parking_lot = "0.12.1" -thiserror = "1.0.50" -uuid = "1.6.1" +thiserror = "1.0.57" +uuid = "1.7.0" diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index eb5b5b252..dd818c6dc 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -23,7 +23,11 @@ use bevy_ecs::{bundle::Bundle, component::Component}; pub use data::*; use derive_more::{Deref, DerefMut}; pub use dimensions::EntityDimensions; -use std::fmt::Debug; +use plugin::indexing::EntityChunkPos; +use std::{ + fmt::Debug, + hash::{Hash, Hasher}, +}; use uuid::Uuid; pub use crate::plugin::*; @@ -199,15 +203,42 @@ impl From<&LastSentPosition> for BlockPos { /// /// If this is true, the entity will try to jump every tick. (It's equivalent to /// the space key being held in vanilla.) -#[derive(Debug, Component, Clone, Deref, DerefMut, Default)] +#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default)] pub struct Jumping(bool); /// A component that contains the direction an entity is looking. -#[derive(Debug, Component, Clone, Default)] +#[derive(Debug, Component, Copy, Clone, Default, PartialEq)] pub struct LookDirection { - pub x_rot: f32, + /// Left and right. Aka yaw. pub y_rot: f32, + /// Up and down. Aka pitch. + pub x_rot: f32, +} + +impl LookDirection { + pub fn new(y_rot: f32, x_rot: f32) -> Self { + Self { y_rot, x_rot } + } +} + +impl From for (f32, f32) { + fn from(value: LookDirection) -> Self { + (value.y_rot, value.x_rot) + } +} +impl From<(f32, f32)> for LookDirection { + fn from((y_rot, x_rot): (f32, f32)) -> Self { + Self { y_rot, x_rot } + } +} + +impl Hash for LookDirection { + fn hash(&self, state: &mut H) { + self.y_rot.to_bits().hash(state); + self.x_rot.to_bits().hash(state); + } } +impl Eq for LookDirection {} /// The physics data relating to the entity, such as position, velocity, and /// bounding box. @@ -317,6 +348,9 @@ pub struct EntityBundle { pub world_name: InstanceName, pub position: Position, pub last_sent_position: LastSentPosition, + + pub chunk_pos: EntityChunkPos, + pub physics: Physics, pub direction: LookDirection, pub eye_height: EyeHeight, @@ -345,6 +379,7 @@ impl EntityBundle { uuid: EntityUuid(uuid), world_name: InstanceName(world_name), position: Position(pos), + chunk_pos: EntityChunkPos(ChunkPos::from(&pos)), last_sent_position: LastSentPosition(pos), physics: Physics::new(dimensions, &pos), eye_height: EyeHeight(eye_height), @@ -375,7 +410,7 @@ pub struct PlayerBundle { /// be updated by other clients. /// /// If this is for a client then all of our clients will have this. -#[derive(Component)] +#[derive(Component, Clone)] pub struct LocalEntity; #[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] diff --git a/azalea-entity/src/plugin/indexing.rs b/azalea-entity/src/plugin/indexing.rs index 86e91ff21..c8aaffb00 100644 --- a/azalea-entity/src/plugin/indexing.rs +++ b/azalea-entity/src/plugin/indexing.rs @@ -8,12 +8,13 @@ use bevy_ecs::{ query::Changed, system::{Commands, Query, Res, ResMut, Resource}, }; +use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; use std::{collections::HashMap, fmt::Debug}; use tracing::{debug, warn}; use uuid::Uuid; -use crate::{EntityUuid, LastSentPosition, Position}; +use crate::{EntityUuid, Position}; use super::LoadedBy; @@ -83,33 +84,37 @@ impl Debug for EntityUuidIndex { } } -// TODO: this should keep track of chunk position changes in a better way -// instead of relying on LastSentPosition +/// The chunk position that an entity is currently in. +#[derive(Component, Debug, Deref, DerefMut)] +pub struct EntityChunkPos(pub ChunkPos); /// Update the chunk position indexes in [`Instance::entities_by_chunk`]. /// /// [`Instance::entities_by_chunk`]: azalea_world::Instance::entities_by_chunk pub fn update_entity_chunk_positions( - mut query: Query<(Entity, &Position, &LastSentPosition, &InstanceName), Changed>, + mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed>, instance_container: Res, ) { - for (entity, pos, last_pos, world_name) in query.iter_mut() { + for (entity, pos, world_name, mut entity_chunk_pos) in query.iter_mut() { let instance_lock = instance_container.get(world_name).unwrap(); let mut instance = instance_lock.write(); - let old_chunk = ChunkPos::from(*last_pos); + let old_chunk = **entity_chunk_pos; let new_chunk = ChunkPos::from(*pos); - if old_chunk != new_chunk { - // move the entity from the old chunk to the new one - if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) { - entities.remove(&entity); + **entity_chunk_pos = new_chunk; + + if old_chunk != new_chunk { + // move the entity from the old chunk to the new one + if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) { + entities.remove(&entity); + } + instance + .entities_by_chunk + .entry(new_chunk) + .or_default() + .insert(entity); } - instance - .entities_by_chunk - .entry(new_chunk) - .or_default() - .insert(entity); } } } diff --git a/azalea-inventory/Cargo.toml b/azalea-inventory/Cargo.toml index b3c66eceb..aacbd4340 100644 --- a/azalea-inventory/Cargo.toml +++ b/azalea-inventory/Cargo.toml @@ -9,7 +9,7 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-buf = { version = "0.9.0", path = "../azalea-buf" } azalea-inventory-macros = { version = "0.9.0", path = "./azalea-inventory-macros" } azalea-registry = { version = "0.9.0", path = "../azalea-registry" } diff --git a/azalea-inventory/azalea-inventory-macros/Cargo.toml b/azalea-inventory/azalea-inventory-macros/Cargo.toml index a18e2769c..f3c406909 100644 --- a/azalea-inventory/azalea-inventory-macros/Cargo.toml +++ b/azalea-inventory/azalea-inventory-macros/Cargo.toml @@ -12,6 +12,6 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "1.0.70" -quote = "1.0.33" -syn = "2.0.39" +proc-macro2 = "1.0.78" +quote = "1.0.35" +syn = "2.0.49" diff --git a/azalea-inventory/azalea-inventory-macros/src/lib.rs b/azalea-inventory/azalea-inventory-macros/src/lib.rs index d3faa0917..e39f1bc67 100644 --- a/azalea-inventory/azalea-inventory-macros/src/lib.rs +++ b/azalea-inventory/azalea-inventory-macros/src/lib.rs @@ -8,7 +8,7 @@ use parse_macro::{DeclareMenus, Field}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; -use syn::{self, parse_macro_input, Ident}; +use syn::{parse_macro_input, Ident}; #[proc_macro] pub fn declare_menus(input: TokenStream) -> TokenStream { diff --git a/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs b/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs index 6aafe16f9..20c2ddc30 100644 --- a/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs +++ b/azalea-inventory/azalea-inventory-macros/src/parse_macro.rs @@ -1,5 +1,5 @@ use syn::{ - self, braced, + braced, parse::{Parse, ParseStream, Result}, Ident, LitInt, Token, }; diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs index 814b6c37a..c4f8da056 100644 --- a/azalea-inventory/src/slot.rs +++ b/azalea-inventory/src/slot.rs @@ -71,6 +71,14 @@ impl ItemSlot { } } } + + /// Convert this slot into an [`ItemSlotData`], if it's present. + pub fn as_present(&self) -> Option<&ItemSlotData> { + match self { + ItemSlot::Empty => None, + ItemSlot::Present(i) => Some(i), + } + } } /// An item in an inventory, with a count and NBT. Usually you want [`ItemSlot`] diff --git a/azalea-language/Cargo.toml b/azalea-language/Cargo.toml index 57bfe1453..3ffa1f1c8 100644 --- a/azalea-language/Cargo.toml +++ b/azalea-language/Cargo.toml @@ -9,7 +9,7 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -once_cell = "1.18.0" +once_cell = "1.19.0" serde = "^1.0" -serde_json = "^1.0.108" +serde_json = "^1.0.113" # tokio = {version = "^1.21.2", features = ["fs"]} diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index 9652fdb22..7b7628961 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -15,12 +15,14 @@ azalea-entity = { version = "0.9.0", path = "../azalea-entity" } azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } azalea-world = { path = "../azalea-world", version = "0.9.0" } -bevy_app = "0.12.1" -bevy_ecs = "0.12.1" +bevy_app = "0.13.0" +bevy_ecs = "0.13.0" tracing = "0.1.40" -once_cell = "1.18.0" +once_cell = "1.19.0" parking_lot = "^0.12.1" +nohash-hasher = "0.2.0" +smallvec = "1.13.1" [dev-dependencies] -bevy_time = "0.12.1" -uuid = "^1.6.1" +bevy_time = "0.13.0" +uuid = "^1.7.0" diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs index f63b7c2af..211e63032 100755 --- a/azalea-physics/src/collision/discrete_voxel_shape.rs +++ b/azalea-physics/src/collision/discrete_voxel_shape.rs @@ -13,6 +13,7 @@ pub enum DiscreteVoxelShape { } impl DiscreteVoxelShape { + #[inline] pub fn size(&self, axis: Axis) -> u32 { match self { DiscreteVoxelShape::BitSet(shape) => shape.size(axis), @@ -305,6 +306,7 @@ impl BitSetDiscreteVoxelShape { } impl BitSetDiscreteVoxelShape { + #[inline] fn size(&self, axis: Axis) -> u32 { axis.choose(self.x_size, self.y_size, self.z_size) } diff --git a/azalea-physics/src/collision/mergers.rs b/azalea-physics/src/collision/mergers.rs index 5744084e6..c43412d12 100755 --- a/azalea-physics/src/collision/mergers.rs +++ b/azalea-physics/src/collision/mergers.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, convert::TryInto}; +use std::cmp::{self, Ordering}; use super::CubePointRange; use azalea_core::math::{gcd, lcm, EPSILON}; @@ -135,27 +135,27 @@ impl IndexMerger { } } - pub fn new_indirect(var1: &[f64], var2: &[f64], var3: bool, var4: bool) -> Self { + pub fn new_indirect(coords1: &[f64], coords2: &[f64], var3: bool, var4: bool) -> Self { let mut var5 = f64::NAN; - let var7 = var1.len(); - let var8 = var2.len(); - let var9 = var7 + var8; - let mut result = vec![0.0; var9]; - let mut first_indices: Vec = vec![0; var9]; - let mut second_indices: Vec = vec![0; var9]; + let coords1_len = coords1.len(); + let coords2_len = coords2.len(); + let number_of_indices = coords1_len + coords2_len; + let mut result = vec![0.0; number_of_indices]; + let mut first_indices: Vec = vec![0; number_of_indices]; + let mut second_indices: Vec = vec![0; number_of_indices]; let var10 = !var3; let var11 = !var4; let mut var12 = 0; - let mut var13 = 0; - let mut var14 = 0; + let mut coords1_index = 0; + let mut coords2_index = 0; loop { - let mut var17: bool; + let mut iterating_coords1: bool; loop { - let var15 = var13 >= var7; - let var16 = var14 >= var8; - if var15 && var16 { - let result_length = std::cmp::max(1, var12); + let at_end_of_coords1 = coords1_index >= coords1_len; + let at_end_of_coords2 = coords2_index >= coords2_len; + if at_end_of_coords1 && at_end_of_coords2 { + let result_length = cmp::max(1, var12); return Self::Indirect { result, first_indices, @@ -164,26 +164,28 @@ impl IndexMerger { }; } - var17 = !var15 && (var16 || var1[var13] < var2[var14] + EPSILON); - if var17 { - var13 += 1; - if !var10 || var14 != 0 && !var16 { + iterating_coords1 = !at_end_of_coords1 + && (at_end_of_coords2 + || coords1[coords1_index] < coords2[coords2_index] + EPSILON); + if iterating_coords1 { + coords1_index += 1; + if !var10 || coords2_index != 0 && !at_end_of_coords2 { break; } } else { - var14 += 1; - if !var11 || var13 != 0 && !var15 { + coords2_index += 1; + if !var11 || coords1_index != 0 && !at_end_of_coords1 { break; } } } - let var18: isize = (var13 as isize) - 1; - let var19: isize = (var14 as isize) - 1; - let var20 = if var17 { - var1[TryInto::::try_into(var18).unwrap()] + let var18: isize = (coords1_index as isize) - 1; + let var19: isize = (coords2_index as isize) - 1; + let var20 = if iterating_coords1 { + coords1[usize::try_from(var18).unwrap()] } else { - var2[TryInto::::try_into(var19).unwrap()] + coords2[usize::try_from(var19).unwrap()] }; match var5.partial_cmp(&(var20 - EPSILON)) { None | Some(Ordering::Less) => { diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 72151b6b3..3986dc477 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -266,7 +266,6 @@ fn collide_bounding_box( let block_collisions = get_block_collisions(world, entity_bounding_box.expand_towards(movement)); - let block_collisions = block_collisions.collect::>(); collision_boxes.extend(block_collisions); collide_with_shapes(movement, *entity_bounding_box, &collision_boxes) } diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index 41ade73cb..710074de2 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -208,8 +208,12 @@ impl Shapes { /// Check if the op is true anywhere when joining the two shapes /// vanilla calls this joinIsNotEmpty - pub fn matches_anywhere(a: &VoxelShape, b: &VoxelShape, op: fn(bool, bool) -> bool) -> bool { - assert!(!op(false, false)); + pub fn matches_anywhere( + a: &VoxelShape, + b: &VoxelShape, + op: impl Fn(bool, bool) -> bool, + ) -> bool { + debug_assert!(!op(false, false)); let a_is_empty = a.is_empty(); let b_is_empty = b.is_empty(); if a_is_empty || b_is_empty { @@ -269,7 +273,7 @@ impl Shapes { merged_z: IndexMerger, shape1: DiscreteVoxelShape, shape2: DiscreteVoxelShape, - op: fn(bool, bool) -> bool, + op: impl Fn(bool, bool) -> bool, ) -> bool { !merged_x.for_merged_indexes(|var5x, var6, _var7| { merged_y.for_merged_indexes(|var6x, var7x, _var8| { @@ -285,13 +289,13 @@ impl Shapes { pub fn create_index_merger( _var0: i32, - var1: Vec, - var2: Vec, + coords1: &[f64], + coords2: &[f64], var3: bool, var4: bool, ) -> IndexMerger { - let var5 = var1.len() - 1; - let var6 = var2.len() - 1; + let var5 = coords1.len() - 1; + let var6 = coords2.len() - 1; // if (&var1 as &dyn Any).is::() && (&var2 as &dyn // Any).is::() { // return new DiscreteCubeMerger(var0, var5, var6, var3, var4); @@ -302,22 +306,24 @@ impl Shapes { // } // } - if var1[var5] < var2[0] - EPSILON { + if coords1[var5] < coords2[0] - EPSILON { IndexMerger::NonOverlapping { - lower: var1, - upper: var2, + lower: coords1.to_vec(), + upper: coords2.to_vec(), swap: false, } - } else if var2[var6] < var1[0] - EPSILON { + } else if coords2[var6] < coords1[0] - EPSILON { IndexMerger::NonOverlapping { - lower: var2, - upper: var1, + lower: coords2.to_vec(), + upper: coords1.to_vec(), swap: true, } - } else if var5 == var6 && var1 == var2 { - IndexMerger::Identical { coords: var1 } + } else if var5 == var6 && coords1 == coords2 { + IndexMerger::Identical { + coords: coords1.to_vec(), + } } else { - IndexMerger::new_indirect(&var1, &var2, var3, var4) + IndexMerger::new_indirect(&coords1, &coords2, var3, var4) } } } @@ -361,7 +367,7 @@ impl VoxelShape { } } - pub fn get_coords(&self, axis: Axis) -> Vec { + pub fn get_coords(&self, axis: Axis) -> &[f64] { match self { VoxelShape::Array(s) => s.get_coords(axis), VoxelShape::Cube(s) => s.get_coords(axis), @@ -386,6 +392,7 @@ impl VoxelShape { )) } + #[inline] pub fn get(&self, axis: Axis, index: usize) -> f64 { // self.get_coords(axis)[index] match self { @@ -403,9 +410,8 @@ impl VoxelShape { match self { VoxelShape::Cube(s) => s.find_index(axis, coord), _ => { - binary_search(0, (self.shape().size(axis) + 1) as i32, &|t| { - coord < self.get(axis, t as usize) - }) - 1 + let upper_limit = (self.shape().size(axis) + 1) as i32; + binary_search(0, upper_limit, &|t| coord < self.get(axis, t as usize)) - 1 } } } @@ -625,6 +631,10 @@ pub struct CubeVoxelShape { // TODO: check where faces is used in minecraft #[allow(dead_code)] faces: Option>, + + x_coords: Vec, + y_coords: Vec, + z_coords: Vec, } impl ArrayVoxelShape { @@ -634,9 +644,9 @@ impl ArrayVoxelShape { let z_size = shape.size(Axis::Z) + 1; // Lengths of point arrays must be consistent with the size of the VoxelShape. - assert_eq!(x_size, xs.len() as u32); - assert_eq!(y_size, ys.len() as u32); - assert_eq!(z_size, zs.len() as u32); + debug_assert_eq!(x_size, xs.len() as u32); + debug_assert_eq!(y_size, ys.len() as u32); + debug_assert_eq!(z_size, zs.len() as u32); Self { faces: None, @@ -648,19 +658,31 @@ impl ArrayVoxelShape { } } -impl CubeVoxelShape { - pub fn new(shape: DiscreteVoxelShape) -> Self { - Self { shape, faces: None } - } -} - impl ArrayVoxelShape { fn shape(&self) -> &DiscreteVoxelShape { &self.shape } - fn get_coords(&self, axis: Axis) -> Vec { - axis.choose(self.xs.clone(), self.ys.clone(), self.zs.clone()) + #[inline] + fn get_coords(&self, axis: Axis) -> &[f64] { + axis.choose(&self.xs, &self.ys, &self.zs) + } +} + +impl CubeVoxelShape { + pub fn new(shape: DiscreteVoxelShape) -> Self { + // pre-calculate the coor + let x_coords = Self::calculate_coords(&shape, Axis::X); + let y_coords = Self::calculate_coords(&shape, Axis::Y); + let z_coords = Self::calculate_coords(&shape, Axis::Z); + + Self { + shape, + faces: None, + x_coords, + y_coords, + z_coords, + } } } @@ -669,8 +691,8 @@ impl CubeVoxelShape { &self.shape } - fn get_coords(&self, axis: Axis) -> Vec { - let size = self.shape.size(axis); + fn calculate_coords(shape: &DiscreteVoxelShape, axis: Axis) -> Vec { + let size = shape.size(axis); let mut parts = Vec::with_capacity(size as usize); for i in 0..=size { parts.push(i as f64 / size as f64); @@ -678,6 +700,11 @@ impl CubeVoxelShape { parts } + #[inline] + fn get_coords(&self, axis: Axis) -> &[f64] { + axis.choose(&self.x_coords, &self.y_coords, &self.z_coords) + } + fn find_index(&self, axis: Axis, coord: f64) -> i32 { let n = self.shape().size(axis); (f64::clamp(coord * (n as f64), -1f64, n as f64)) as i32 diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index c4c6a846e..54493d622 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -4,35 +4,104 @@ use azalea_block::BlockState; use azalea_core::{ cursor3d::{Cursor3d, CursorIterationType}, math::EPSILON, - position::{ChunkPos, ChunkSectionPos}, + position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos}, }; use azalea_world::{Chunk, Instance}; use parking_lot::RwLock; -use std::{ops::Deref, sync::Arc}; +use std::sync::Arc; -pub fn get_block_collisions(world: &Instance, aabb: AABB) -> BlockCollisions<'_> { - BlockCollisions::new(world, aabb) +pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec { + let mut state = BlockCollisionsState::new(world, aabb); + let mut block_collisions = Vec::new(); + + let initial_chunk_pos = ChunkPos::from(state.cursor.origin()); + let initial_chunk = world.chunks.get(&initial_chunk_pos).unwrap(); + let initial_chunk = initial_chunk.read(); + + while let Some(item) = state.cursor.next() { + if item.iteration_type == CursorIterationType::Corner { + continue; + } + + let item_chunk_pos = ChunkPos::from(item.pos); + let block_state: BlockState = if item_chunk_pos == initial_chunk_pos { + initial_chunk + .get(&ChunkBlockPos::from(item.pos), state.world.chunks.min_y) + .unwrap_or(BlockState::AIR) + } else { + state.get_block_state(item.pos) + }; + + if block_state.is_air() { + // fast path since we can't collide with air + continue; + } + + // TODO: continue if self.only_suffocating_blocks and the block is not + // suffocating + + // if it's a full block do a faster collision check + if block_state.is_shape_full() { + if !state.aabb.intersects_aabb(&AABB { + min_x: item.pos.x as f64, + min_y: item.pos.y as f64, + min_z: item.pos.z as f64, + max_x: (item.pos.x + 1) as f64, + max_y: (item.pos.y + 1) as f64, + max_z: (item.pos.z + 1) as f64, + }) { + continue; + } + + block_collisions.push(BLOCK_SHAPE.move_relative( + item.pos.x as f64, + item.pos.y as f64, + item.pos.z as f64, + )); + continue; + } + + let block_shape = state.get_block_shape(block_state); + + let block_shape = + block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64); + // if the entity shape and block shape don't collide, continue + if !Shapes::matches_anywhere(&block_shape, &state.entity_shape, |a, b| a && b) { + continue; + } + + block_collisions.push(block_shape); + } + + block_collisions } -pub struct BlockCollisions<'a> { +pub struct BlockCollisionsState<'a> { pub world: &'a Instance, pub aabb: AABB, pub entity_shape: VoxelShape, pub cursor: Cursor3d, pub only_suffocating_blocks: bool, + + cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>, + cached_block_shapes: Vec<(BlockState, &'static VoxelShape)>, } -impl<'a> BlockCollisions<'a> { +impl<'a> BlockCollisionsState<'a> { pub fn new(world: &'a Instance, aabb: AABB) -> Self { - let origin_x = (aabb.min_x - EPSILON).floor() as i32 - 1; - let origin_y = (aabb.min_y - EPSILON).floor() as i32 - 1; - let origin_z = (aabb.min_z - EPSILON).floor() as i32 - 1; + let origin = BlockPos { + x: (aabb.min_x - EPSILON).floor() as i32 - 1, + y: (aabb.min_y - EPSILON).floor() as i32 - 1, + z: (aabb.min_z - EPSILON).floor() as i32 - 1, + }; - let end_x = (aabb.max_x + EPSILON).floor() as i32 + 1; - let end_y = (aabb.max_y + EPSILON).floor() as i32 + 1; - let end_z = (aabb.max_z + EPSILON).floor() as i32 + 1; + let end = BlockPos { + x: (aabb.max_x + EPSILON).floor() as i32 + 1, + y: (aabb.max_y + EPSILON).floor() as i32 + 1, + z: (aabb.max_z + EPSILON).floor() as i32 + 1, + }; - let cursor = Cursor3d::new(origin_x, origin_y, origin_z, end_x, end_y, end_z); + let cursor = Cursor3d::new(origin, end); Self { world, @@ -40,6 +109,9 @@ impl<'a> BlockCollisions<'a> { entity_shape: VoxelShape::from(aabb), cursor, only_suffocating_blocks: false, + + cached_sections: Vec::new(), + cached_block_shapes: Vec::new(), } } @@ -63,63 +135,51 @@ impl<'a> BlockCollisions<'a> { self.world.chunks.get(&chunk_pos) } -} -impl<'a> Iterator for BlockCollisions<'a> { - type Item = VoxelShape; + fn get_block_state(&mut self, block_pos: BlockPos) -> BlockState { + let section_pos = ChunkSectionPos::from(block_pos); + let section_block_pos = ChunkSectionBlockPos::from(block_pos); - fn next(&mut self) -> Option { - while let Some(item) = self.cursor.next() { - if item.iteration_type == CursorIterationType::Corner { - continue; + for (cached_section_pos, cached_section) in &self.cached_sections { + if section_pos == *cached_section_pos { + return cached_section.get(section_block_pos); } + } - let chunk = self.get_chunk(item.pos.x, item.pos.z); - let Some(chunk) = chunk else { - continue; - }; - - let pos = item.pos; - let block_state: BlockState = chunk - .read() - .get(&(&pos).into(), self.world.chunks.min_y) - .unwrap_or(BlockState::AIR); - - // TODO: continue if self.only_suffocating_blocks and the block is not - // suffocating - - let block_shape = block_state.shape(); - - // if it's a full block do a faster collision check - if block_shape == BLOCK_SHAPE.deref() { - if !self.aabb.intersects_aabb(&AABB { - min_x: item.pos.x as f64, - min_y: item.pos.y as f64, - min_z: item.pos.z as f64, - max_x: (item.pos.x + 1) as f64, - max_y: (item.pos.y + 1) as f64, - max_z: (item.pos.z + 1) as f64, - }) { - continue; - } - - return Some(block_shape.move_relative( - item.pos.x as f64, - item.pos.y as f64, - item.pos.z as f64, - )); - } + let chunk = self.get_chunk(block_pos.x, block_pos.z); + let Some(chunk) = chunk else { + return BlockState::AIR; + }; + let chunk = chunk.read(); - let block_shape = - block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64); - // if the entity shape and block shape don't collide, continue - if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) { - continue; - } + let sections = &chunk.sections; + let section_index = + azalea_world::chunk_storage::section_index(block_pos.y, self.world.chunks.min_y) + as usize; + + let Some(section) = sections.get(section_index) else { + return BlockState::AIR; + }; + + self.cached_sections.push((section_pos, section.clone())); + + // println!("chunk section palette: {:?}", section.states.palette); + // println!("chunk section data: {:?}", section.states.storage.data); + // println!("biome length: {}", section.biomes.storage.data.len()); - return Some(block_shape); + section.get(section_block_pos) + } + + fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape { + for (cached_block_state, cached_shape) in &self.cached_block_shapes { + if block_state == *cached_block_state { + return cached_shape; + } } - None + let shape = block_state.shape(); + self.cached_block_shapes.push((block_state, shape)); + + shape } } diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index a69c1bcf2..ed813b569 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -74,9 +74,9 @@ fn travel( jumping, ) in &mut query { - let world_lock = instance_container - .get(world_name) - .expect("All entities should be in a valid world"); + let Some(world_lock) = instance_container.get(world_name) else { + continue; + }; let world = world_lock.read(); // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() { // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal); @@ -424,10 +424,9 @@ fn jump_boost_power() -> f64 { mod tests { use super::*; - use azalea_core::{position::ChunkPos, resource_location::ResourceLocation, tick::GameTick}; + use azalea_core::{position::ChunkPos, resource_location::ResourceLocation}; use azalea_entity::{EntityBundle, EntityPlugin}; use azalea_world::{Chunk, MinecraftEntityId, PartialInstance}; - use bevy_app::App; use uuid::Uuid; /// You need an app to spawn entities in the world and do updates. diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 05329c0fc..cc92121ca 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -9,7 +9,7 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } async-recursion = "1.0.5" azalea-auth = { path = "../azalea-auth", version = "0.9.0" } azalea-block = { path = "../azalea-block", default-features = false, version = "0.9.0" } @@ -29,26 +29,27 @@ azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.9.0" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } azalea-world = { path = "../azalea-world", version = "0.9.0" } -bevy_ecs = { version = "0.12.1", default-features = false } +bevy_ecs = { version = "0.13.0", default-features = false } byteorder = "^1.5.0" bytes = "^1.5.0" flate2 = "1.0.28" -futures = "0.3.29" -futures-lite = "2.1.0" -futures-util = "0.3.29" +futures = "0.3.30" +futures-lite = "2.2.0" +futures-util = "0.3.30" tracing = "0.1.40" serde = { version = "^1.0", features = ["serde_derive"] } -serde_json = "^1.0.108" -thiserror = "1.0.50" -tokio = { version = "^1.34.0", features = ["io-util", "net", "macros"] } +serde_json = "^1.0.113" +thiserror = "1.0.57" +tokio = { version = "^1.36.0", features = ["io-util", "net", "macros"] } tokio-util = { version = "0.7.10", features = ["codec"] } trust-dns-resolver = { version = "^0.23.2", default-features = false, features = [ "tokio-runtime", ] } -socks5-impl = "0.5.6" -uuid = "1.6.1" +uuid = "1.7.0" log = "0.4.20" +socks5-impl = "0.5.6" + [features] connecting = [] default = ["packets"] @@ -56,7 +57,7 @@ packets = ["connecting", "dep:azalea-core"] strict_registry = ["packets"] [dev-dependencies] -anyhow = "^1.0.75" +anyhow = "^1.0.79" tracing = "^0.1.40" tracing-subscriber = "^0.3.18" -once_cell = "1.18.0" +once_cell = "1.19.0" diff --git a/azalea-protocol/azalea-protocol-macros/Cargo.toml b/azalea-protocol/azalea-protocol-macros/Cargo.toml index b20ac4da1..f37ca2ab1 100644 --- a/azalea-protocol/azalea-protocol-macros/Cargo.toml +++ b/azalea-protocol/azalea-protocol-macros/Cargo.toml @@ -11,6 +11,6 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "^1.0.70" -quote = "^1.0.33" -syn = "^2.0.39" +proc-macro2 = "^1.0.78" +quote = "^1.0.35" +syn = "^2.0.49" diff --git a/azalea-protocol/azalea-protocol-macros/src/lib.rs b/azalea-protocol/azalea-protocol-macros/src/lib.rs index ffecc13a7..0c072b849 100755 --- a/azalea-protocol/azalea-protocol-macros/src/lib.rs +++ b/azalea-protocol/azalea-protocol-macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - self, braced, + braced, parse::{Parse, ParseStream, Result}, parse_macro_input, DeriveInput, Ident, LitInt, Token, }; diff --git a/azalea-protocol/examples/handshake_proxy.rs b/azalea-protocol/examples/handshake_proxy.rs index e63e8197c..a7ac67c74 100644 --- a/azalea-protocol/examples/handshake_proxy.rs +++ b/azalea-protocol/examples/handshake_proxy.rs @@ -40,7 +40,7 @@ static PROXY_FAVICON: Lazy> = Lazy::new(|| None); static PROXY_VERSION: Lazy = Lazy::new(|| Version { name: "1.19.3".to_string(), - protocol: PROTOCOL_VERSION as i32, + protocol: PROTOCOL_VERSION, }); const PROXY_PLAYERS: Players = Players { diff --git a/azalea-protocol/src/packets/game/clientbound_explode_packet.rs b/azalea-protocol/src/packets/game/clientbound_explode_packet.rs index ae39135a9..23c416d1b 100755 --- a/azalea-protocol/src/packets/game/clientbound_explode_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_explode_packet.rs @@ -1,9 +1,12 @@ -use std::io::{Cursor, Write}; +use std::{ + io::{Cursor, Write}, + str::FromStr, +}; use azalea_buf::{ BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, }; -use azalea_core::position::BlockPos; +use azalea_core::{position::BlockPos, resource_location::ResourceLocation}; use azalea_protocol_macros::ClientboundGamePacket; use azalea_registry::{ParticleKind, SoundEvent}; @@ -59,7 +62,14 @@ impl McBufReadable for ClientboundExplodePacket { let block_interaction = BlockInteraction::read_from(buf)?; let small_explosion_particles = ParticleKind::read_from(buf)?; let large_explosion_particles = ParticleKind::read_from(buf)?; - let explosion_sound = SoundEvent::read_from(buf)?; + + let sound_event_resource_location = ResourceLocation::read_from(buf)?.to_string(); + let explosion_sound = + SoundEvent::from_str(&sound_event_resource_location).map_err(|_| { + BufReadError::UnexpectedStringEnumVariant { + id: sound_event_resource_location, + } + })?; Ok(Self { x, @@ -108,7 +118,10 @@ impl McBufWritable for ClientboundExplodePacket { self.block_interaction.write_into(buf)?; self.small_explosion_particles.write_into(buf)?; self.large_explosion_particles.write_into(buf)?; - self.explosion_sound.write_into(buf)?; + + let sound_event_resource_location = + ResourceLocation::new(&self.explosion_sound.to_string()); + sound_event_resource_location.write_into(buf)?; Ok(()) } diff --git a/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs index bd1b2ab61..2416f7c33 100755 --- a/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_game_event_packet.rs @@ -21,4 +21,6 @@ pub enum EventType { PufferFishSting = 9, GuardianElderEffect = 10, ImmediateRespawn = 11, + LimitedCrafting = 12, + WaitForLevelChunks = 13, } diff --git a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs index f8927ca42..f7212ba7c 100755 --- a/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_level_chunk_with_light_packet.rs @@ -28,33 +28,39 @@ pub struct BlockEntity { pub data: Nbt, } -// Compound(NbtCompound { -// inner: [("", Compound(NbtCompound { -// inner: [ -// ("MOTION_BLOCKING", LongArray([2310355422147575936, -// 2292305770412047999, 2310355422147575423, 2292305770412310656, -// 2310355422013095551, 2292305839266005120, 2310320168921529983, -// 2310355422147575936, 2292305770412048512, 2310355422147575935, -// 2292305839266005120, 2310355422147313279, 2292305770546528384, -// 2310355353293618815, 2292305839266005120, 2292305770412047999, -// 2310355422147575936, 2292305770412047999, 2310355422147575423, -// 2292305770412048512, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 17079008895])), -// ("WORLD_SURFACE", LongArray([2310355422147575936, -// 2292340954784136831, 2310355422147575423, 2292305770412310656, -// 2310355422013095551, 2292305839266005120, 2310320168921529983, -// 2310355422147575936, 2292305770412048512, 2310355422147575935, -// 2292305839266005120, 2310355422147313279, 2292305770546528384, -// 2310355353293618815, 2292305839266005120, 2292305770412047999, -// 2310355422147575936, 2292305770412047999, 2310355422147575423, -// 2292305770412048512, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 2292305770412047999, -// 2292305770412047999, 2292305770412047999, 17079008895]))] }))] -// }) +#[cfg(test)] +mod tests { + use std::{io::Cursor, ops::Deref}; + + use azalea_buf::McBufReadable; + use azalea_world::Chunk; + use simdnbt::owned::BaseNbt; + + use super::*; + + #[test] + fn test_clientbound_level_chunk_with_light_packet() { + #[rustfmt::skip] + let bytes = [ + 255, 255, 255, 253, 0, 0, 0, 1, 10, 12, 0, 15, 77, 79, 84, 73, 79, 78, 95, 66, 76, 79, 67, 75, 73, 78, 71, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 240, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 195, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 33, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 37, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 4, 2, 0, 137, 51, 128, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 4, 2, 0, 137, 51, 128, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 0, 0, 1, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 2, 140, 1, 1, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 4, 128, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 128, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 32, 67, 101, 0, 0, 0, 0, 0, 32, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 33, 67, 101, 135, 169, 203, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 33, 67, 101, 135, 169, 203, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 16, 50, 84, 118, 152, 186, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 33, 67, 101, 135, 169, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 16, 50, 84, 118, 152, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 33, 67, 101, 135, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 16, 50, 84, 118, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 33, 67, 101, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 128, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 16, 50, 84, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 33, 67, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 16, 50, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + let packet = + ClientboundLevelChunkWithLightPacket::read_from(&mut Cursor::new(&bytes)).unwrap(); + + let heightmaps_nbt = &packet.chunk_data.heightmaps; + // necessary to make the unwrap_or work + let empty_nbt = BaseNbt::default(); + let heightmaps = heightmaps_nbt.unwrap_or(&empty_nbt).deref(); + + let chunk = Chunk::read_with_dimension_height( + &mut Cursor::new(&packet.chunk_data.data), + 256, + 0, + heightmaps, + ) + .unwrap(); + + assert_eq!(chunk.sections.len(), 16); + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_ping_packet.rs b/azalea-protocol/src/packets/game/clientbound_ping_packet.rs index 82de4fab9..0bd2c8c3a 100755 --- a/azalea-protocol/src/packets/game/clientbound_ping_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_ping_packet.rs @@ -3,6 +3,5 @@ use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundPingPacket { - #[var] pub id: u32, } diff --git a/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs index 8a5823a0a..420dfd36f 100755 --- a/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs @@ -7,3 +7,26 @@ pub struct ClientboundSystemChatPacket { pub content: FormattedText, pub overlay: bool, } + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use azalea_buf::McBufReadable; + + use super::*; + + #[test] + fn test_clientbound_system_chat_packet() { + #[rustfmt::skip] + let bytes = [ + 10, 9, 0, 4, 119, 105, 116, 104, 10, 0, 0, 0, 2, 10, 0, 10, 104, 111, 118, 101, 114, 69, 118, 101, 110, 116, 10, 0, 8, 99, 111, 110, 116, 101, 110, 116, 115, 8, 0, 4, 110, 97, 109, 101, 0, 3, 112, 121, 53, 11, 0, 2, 105, 100, 0, 0, 0, 4, 101, 54, 191, 237, 134, 149, 72, 253, 131, 161, 236, 210, 76, 242, 160, 253, 8, 0, 4, 116, 121, 112, 101, 0, 16, 109, 105, 110, 101, 99, 114, 97, 102, 116, 58, 112, 108, 97, 121, 101, 114, 0, 8, 0, 6, 97, 99, 116, 105, 111, 110, 0, 11, 115, 104, 111, 119, 95, 101, 110, 116, 105, 116, 121, 0, 10, 0, 10, 99, 108, 105, 99, 107, 69, 118, 101, 110, 116, 8, 0, 6, 97, 99, 116, 105, 111, 110, 0, 15, 115, 117, 103, 103, 101, 115, 116, 95, 99, 111, 109, 109, 97, 110, 100, 8, 0, 5, 118, 97, 108, 117, 101, 0, 10, 47, 116, 101, 108, 108, 32, 112, 121, 53, 32, 0, 8, 0, 9, 105, 110, 115, 101, 114, 116, 105, 111, 110, 0, 3, 112, 121, 53, 8, 0, 4, 116, 101, 120, 116, 0, 3, 112, 121, 53, 0, 9, 0, 4, 119, 105, 116, 104, 10, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 1, 0, 10, 0, 10, 104, 111, 118, 101, 114, 69, 118, 101, 110, 116, 10, 0, 8, 99, 111, 110, 116, 101, 110, 116, 115, 8, 0, 2, 105, 100, 0, 25, 109, 105, 110, 101, 99, 114, 97, 102, 116, 58, 100, 105, 97, 109, 111, 110, 100, 95, 112, 105, 99, 107, 97, 120, 101, 8, 0, 3, 116, 97, 103, 0, 10, 123, 68, 97, 109, 97, 103, 101, 58, 48, 125, 0, 8, 0, 6, 97, 99, 116, 105, 111, 110, 0, 9, 115, 104, 111, 119, 95, 105, 116, 101, 109, 0, 9, 0, 4, 119, 105, 116, 104, 10, 0, 0, 0, 1, 9, 0, 5, 101, 120, 116, 114, 97, 10, 0, 0, 0, 1, 8, 0, 9, 116, 114, 97, 110, 115, 108, 97, 116, 101, 0, 30, 105, 116, 101, 109, 46, 109, 105, 110, 101, 99, 114, 97, 102, 116, 46, 100, 105, 97, 109, 111, 110, 100, 95, 112, 105, 99, 107, 97, 120, 101, 0, 8, 0, 4, 116, 101, 120, 116, 0, 0, 0, 8, 0, 5, 99, 111, 108, 111, 114, 0, 5, 119, 104, 105, 116, 101, 8, 0, 9, 116, 114, 97, 110, 115, 108, 97, 116, 101, 0, 20, 99, 104, 97, 116, 46, 115, 113, 117, 97, 114, 101, 95, 98, 114, 97, 99, 107, 101, 116, 115, 0, 10, 0, 10, 104, 111, 118, 101, 114, 69, 118, 101, 110, 116, 10, 0, 8, 99, 111, 110, 116, 101, 110, 116, 115, 8, 0, 4, 110, 97, 109, 101, 0, 3, 112, 121, 53, 11, 0, 2, 105, 100, 0, 0, 0, 4, 101, 54, 191, 237, 134, 149, 72, 253, 131, 161, 236, 210, 76, 242, 160, 253, 8, 0, 4, 116, 121, 112, 101, 0, 16, 109, 105, 110, 101, 99, 114, 97, 102, 116, 58, 112, 108, 97, 121, 101, 114, 0, 8, 0, 6, 97, 99, 116, 105, 111, 110, 0, 11, 115, 104, 111, 119, 95, 101, 110, 116, 105, 116, 121, 0, 10, 0, 10, 99, 108, 105, 99, 107, 69, 118, 101, 110, 116, 8, 0, 6, 97, 99, 116, 105, 111, 110, 0, 15, 115, 117, 103, 103, 101, 115, 116, 95, 99, 111, 109, 109, 97, 110, 100, 8, 0, 5, 118, 97, 108, 117, 101, 0, 10, 47, 116, 101, 108, 108, 32, 112, 121, 53, 32, 0, 8, 0, 9, 105, 110, 115, 101, 114, 116, 105, 111, 110, 0, 3, 112, 121, 53, 8, 0, 4, 116, 101, 120, 116, 0, 3, 112, 121, 53, 0, 8, 0, 9, 116, 114, 97, 110, 115, 108, 97, 116, 101, 0, 28, 99, 111, 109, 109, 97, 110, 100, 115, 46, 103, 105, 118, 101, 46, 115, 117, 99, 99, 101, 115, 115, 46, 115, 105, 110, 103, 108, 101, 0, 8, 0, 5, 99, 111, 108, 111, 114, 0, 4, 103, 114, 97, 121, 1, 0, 6, 105, 116, 97, 108, 105, 99, 1, 8, 0, 9, 116, 114, 97, 110, 115, 108, 97, 116, 101, 0, 15, 99, 104, 97, 116, 46, 116, 121, 112, 101, 46, 97, 100, 109, 105, 110, 0, 0 + ]; + + let packet = ClientboundSystemChatPacket::read_from(&mut Cursor::new(&bytes)).unwrap(); + assert_eq!( + packet.content.to_string(), + "[py5: Gave 1 [Diamond Pickaxe] to py5]".to_string() + ); + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs index efd557618..00a846e8f 100755 --- a/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs @@ -119,8 +119,6 @@ pub struct AdvancementHolder { mod tests { use super::*; use azalea_buf::{McBufReadable, McBufWritable}; - use azalea_core::resource_location::ResourceLocation; - use std::io::Cursor; #[test] fn test() { diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index 7c641c761..434cc74a9 100755 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -17,6 +17,7 @@ use std::{ use thiserror::Error; use tokio::io::AsyncRead; use tokio_util::codec::{BytesCodec, FramedRead}; +use tracing::trace; #[derive(Error, Debug)] pub enum ReadPacketError { @@ -348,15 +349,17 @@ where } if log::log_enabled!(log::Level::Trace) { + const DO_NOT_CUT_OFF_PACKET_LOGS: bool = false; + let buf_string: String = { - if buf.len() > 500 { + if !DO_NOT_CUT_OFF_PACKET_LOGS && buf.len() > 500 { let cut_off_buf = &buf[..500]; format!("{cut_off_buf:?}...") } else { format!("{buf:?}") } }; - tracing::trace!("Reading packet with bytes: {buf_string}"); + trace!("Reading packet with bytes: {buf_string}"); }; Ok(Some(buf)) diff --git a/azalea-registry/Cargo.toml b/azalea-registry/Cargo.toml index 5a86d30bd..e1bae3afd 100644 --- a/azalea-registry/Cargo.toml +++ b/azalea-registry/Cargo.toml @@ -9,11 +9,10 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } - +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-buf = { path = "../azalea-buf", version = "0.9.0" } azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.9.0" } -once_cell = "1.18.0" +once_cell = "1.19.0" [features] serde = ["azalea-registry-macros/serde"] default = ["serde"] diff --git a/azalea-registry/azalea-registry-macros/Cargo.toml b/azalea-registry/azalea-registry-macros/Cargo.toml index 5941f0404..cb07e2f70 100644 --- a/azalea-registry/azalea-registry-macros/Cargo.toml +++ b/azalea-registry/azalea-registry-macros/Cargo.toml @@ -12,9 +12,9 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = "1.0.70" -quote = "1.0.33" -syn = "2.0.39" +proc-macro2 = "1.0.78" +quote = "1.0.35" +syn = "2.0.49" [features] serde = [] diff --git a/azalea-registry/azalea-registry-macros/src/lib.rs b/azalea-registry/azalea-registry-macros/src/lib.rs index f3289d961..460ff06af 100755 --- a/azalea-registry/azalea-registry-macros/src/lib.rs +++ b/azalea-registry/azalea-registry-macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - self, braced, + braced, parse::{Parse, ParseStream, Result}, parse_macro_input, punctuated::Punctuated, diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 81a7839d1..20e3a261c 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -9,7 +9,7 @@ version = "0.9.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simdnbt = { version = "0.3", git = "https://github.com/azalea-rs/simdnbt" } +simdnbt = { version = "0.4", git = "https://github.com/azalea-rs/simdnbt" } azalea-block = { path = "../azalea-block", default-features = false, version = "0.9.0" } azalea-buf = { path = "../azalea-buf", version = "0.9.0" } azalea-core = { path = "../azalea-core", version = "0.9.0", features = [ @@ -17,17 +17,17 @@ azalea-core = { path = "../azalea-core", version = "0.9.0", features = [ ] } azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } -bevy_ecs = "0.12.1" +bevy_ecs = "0.13.0" derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } -enum-as-inner = "0.6.0" tracing = "0.1.40" nohash-hasher = "0.2.0" -once_cell = "1.18.0" +once_cell = "1.19.0" parking_lot = "^0.12.1" -thiserror = "1.0.50" -uuid = "1.6.1" -serde_json = "1.0.108" -serde = "1.0.193" +thiserror = "1.0.57" +uuid = "1.7.0" +serde_json = "1.0.113" +serde = "1.0.196" +rustc-hash = "1.1.0" [dev-dependencies] azalea-client = { path = "../azalea-client" } diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index 58ff28c4a..0456d7275 100755 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -114,8 +114,7 @@ impl BitStorage { } } - // vanilla has this assert but it's not always true for some reason?? - // assert!(bits >= 1 && bits <= 32); + debug_assert!((1..=32).contains(&bits)); let values_per_long = 64 / bits; let magic_index = values_per_long - 1; diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 8bc0b32cb..512f231b3 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -450,9 +450,8 @@ impl McBufReadable for Section { for i in 0..states.storage.size() { if !BlockState::is_valid_state(states.storage.get(i) as u32) { return Err(BufReadError::Custom(format!( - "Invalid block state {} (index {}) found in section.", - states.storage.get(i), - i + "Invalid block state {} (index {i}) found in section.", + states.storage.get(i) ))); } } diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 0b68ead68..69b8f9084 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -3,6 +3,7 @@ use bevy_ecs::{component::Component, system::Resource}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; use parking_lot::RwLock; +use rustc_hash::FxHashMap; use std::{ collections::HashMap, sync::{Arc, Weak}, @@ -27,7 +28,7 @@ pub struct InstanceContainer { // telling them apart. We hope most servers are nice and don't do that though. It's only an // issue when there's multiple clients with the same WorldContainer in different worlds // anyways. - pub instances: HashMap>>, + pub instances: FxHashMap>>, } impl InstanceContainer { diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index 5e57989c7..2b3cdc536 100755 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -1,5 +1,7 @@ use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable}; +use azalea_core::math; use std::io::{Cursor, Write}; +use tracing::warn; use crate::BitStorage; @@ -41,17 +43,39 @@ impl PalettedContainer { buf: &mut Cursor<&[u8]>, container_type: &'static PalettedContainerKind, ) -> Result { - let bits_per_entry = u8::read_from(buf)?; - let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type); + let server_bits_per_entry = u8::read_from(buf)?; + let palette_type = PaletteKind::from_bits_and_type(server_bits_per_entry, container_type); let palette = palette_type.read(buf)?; let size = container_type.size(); - let data = Vec::::read_from(buf)?; + + // we can only trust the bits per entry that we're sent if there's enough data + // that it'd be global. if it's not global, then we have to calculate it + // ourselves. + // this almost never matters, except on some custom servers like hypixel limbo + let calculated_bits_per_entry = math::ceil_log2(data.len() as u32) as u8; + let calculated_bits_per_entry_palette_kind = + PaletteKind::from_bits_and_type(calculated_bits_per_entry, container_type); + let bits_per_entry = if calculated_bits_per_entry_palette_kind == PaletteKind::Global { + server_bits_per_entry + } else { + calculated_bits_per_entry + }; + debug_assert!( bits_per_entry != 0 || data.is_empty(), "Bits per entry is 0 but data is not empty." ); - let storage = BitStorage::new(bits_per_entry.into(), size, Some(data)).unwrap(); + + let storage = match BitStorage::new(bits_per_entry.into(), size, Some(data)) { + Ok(storage) => storage, + Err(e) => { + warn!("Failed to create bit storage: {:?}", e); + return Err(BufReadError::Custom( + "Failed to create bit storage".to_string(), + )); + } + }; Ok(PalettedContainer { bits_per_entry, diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 2e8ed752a..c4e4a6d44 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -12,8 +12,8 @@ pre-release-replacements = [ ] [dependencies] -anyhow = "^1.0.75" -async-trait = "0.1.74" +anyhow = "^1.0.79" +async-trait = "0.1.77" azalea-block = { version = "0.9.0", path = "../azalea-block" } azalea-chat = { version = "0.9.0", path = "../azalea-chat" } azalea-client = { version = "0.9.0", path = "../azalea-client", default-features = false } @@ -26,23 +26,23 @@ azalea-world = { version = "0.9.0", path = "../azalea-world" } azalea-auth = { version = "0.9.0", path = "../azalea-auth" } azalea-brigadier = { version = "0.9.0", path = "../azalea-brigadier" } azalea-buf = { version = "0.9.0", path = "../azalea-buf" } -bevy_app = "0.12.1" -bevy_ecs = "0.12.1" -bevy_tasks = { version = "0.12.1", features = ["multi-threaded"] } +bevy_app = "0.13.0" +bevy_ecs = "0.13.0" +bevy_tasks = { version = "0.13.0", features = ["multi-threaded"] } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } -futures = "0.3.29" -futures-lite = "2.1.0" +futures = "0.3.30" +futures-lite = "2.2.0" tracing = "0.1.40" nohash-hasher = "0.2.0" -num-traits = "0.2.17" +num-traits = "0.2.18" parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } -priority-queue = "1.3.2" -thiserror = "^1.0.50" -tokio = "^1.34.0" -uuid = "1.6.1" -bevy_log = "0.12.1" +priority-queue = "1.4.0" +thiserror = "^1.0.57" +tokio = "^1.36.0" +uuid = "1.7.0" +bevy_log = "0.13.0" azalea-entity = { version = "0.9.0", path = "../azalea-entity" } -bevy_time = "0.12.1" +bevy_time = "0.13.0" rustc-hash = "1.1.0" rand = "0.8.5" @@ -57,3 +57,7 @@ log = ["azalea-client/log"] [[bench]] name = "pathfinder" harness = false + +[[bench]] +name = "physics" +harness = false diff --git a/azalea/README.md b/azalea/README.md index 339c57a2e..e7f52cd6b 100755 --- a/azalea/README.md +++ b/azalea/README.md @@ -53,7 +53,7 @@ async fn main() { ClientBuilder::new() .set_handler(handle) - .start(account.clone(), "localhost") + .start(account, "localhost") .await .unwrap(); } @@ -75,7 +75,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { # Swarms -Azalea lets you create "swarms", which are a group of bots in the same world that can perform actions together. See [testbot](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot.rs) for an example. Also, if you're using swarms, you should also `use` both `azalea::prelude::*` and `azalea::swarm::prelude::*`. +Azalea lets you create "swarms", which are a group of bots in the same world that can perform actions together. See [testbot](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot/main.rs) for an example. Also, if you're using swarms, you should also `use` both `azalea::prelude::*` and `azalea::swarm::prelude::*`. # Plugins @@ -99,7 +99,7 @@ Note: If you get a `SetLoggerError`, it's because you have multiple loggers. Aza ## Deadlocks -If your code is simply hanging, it might be a deadlock. Copy the deadlock block in [`azalea/examples/testbot.rs`](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot.rs) to the beginning of your code and it'll print a long backtrace if a deadlock is detected. +If your code is simply hanging, it might be a deadlock. Copy the deadlock block in [`azalea/examples/testbot.rs`](https://github.com/azalea-rs/azalea/blob/main/azalea/examples/testbot/main.rs) to the beginning of your code and it'll print a long backtrace if a deadlock is detected. ## Backtraces diff --git a/azalea/benches/pathfinder.rs b/azalea/benches/pathfinder.rs index 842c6b5e2..4e42c63a1 100644 --- a/azalea/benches/pathfinder.rs +++ b/azalea/benches/pathfinder.rs @@ -16,6 +16,7 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion}; use parking_lot::RwLock; use rand::{rngs::StdRng, Rng, SeedableRng}; +#[allow(dead_code)] fn generate_bedrock_world( partial_chunks: &mut PartialChunkStorage, size: u32, @@ -87,7 +88,7 @@ fn generate_mining_world( } } - let mut rng = StdRng::seed_from_u64(0); + // let mut rng = StdRng::seed_from_u64(0); for chunk_x in -size..size { for chunk_z in -size..size { diff --git a/azalea/benches/physics.rs b/azalea/benches/physics.rs new file mode 100644 index 000000000..0d4a3f2fa --- /dev/null +++ b/azalea/benches/physics.rs @@ -0,0 +1,96 @@ +use std::{hint::black_box, sync::Arc, time::Duration}; + +use azalea::{ + pathfinder::{ + astar::{self, a_star}, + goals::{BlockPosGoal, Goal}, + mining::MiningCache, + simulation::{SimulatedPlayerBundle, Simulation, SimulationSet}, + world::CachedWorld, + }, + BlockPos, Vec3, +}; +use azalea_core::position::{ChunkBlockPos, ChunkPos}; +use azalea_inventory::Menu; +use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; +use criterion::{criterion_group, criterion_main, Bencher, Criterion}; +use parking_lot::RwLock; + +#[allow(dead_code)] +fn generate_world(partial_chunks: &mut PartialChunkStorage, size: u32) -> ChunkStorage { + let size = size as i32; + + let mut chunks = ChunkStorage::default(); + for chunk_x in -size..size { + for chunk_z in -size..size { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); + } + } + + for chunk_x in -size..size { + for chunk_z in -size..size { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + let chunk = chunks.get(&chunk_pos).unwrap(); + let mut chunk = chunk.write(); + for x in 0..16_u8 { + for z in 0..16_u8 { + chunk.set( + &ChunkBlockPos::new(x, 1, z), + azalea_registry::Block::OakFence.into(), + chunks.min_y, + ); + } + } + } + } + + // let mut start = BlockPos::new(-64, 4, -64); + // // move start down until it's on a solid block + // while chunks.get_block_state(&start).unwrap().is_air() { + // start = start.down(1); + // } + // start = start.up(1); + + // let mut end = BlockPos::new(63, 4, 63); + // // move end down until it's on a solid block + // while chunks.get_block_state(&end).unwrap().is_air() { + // end = end.down(1); + // } + // end = end.up(1); + + chunks +} + +fn run_physics_benchmark(b: &mut Bencher<'_>) { + let mut partial_chunks = PartialChunkStorage::new(32); + + let world = generate_world(&mut partial_chunks, 4); + + let mut simulation_set = SimulationSet::new(world); + + // let entity = simulation_set.spawn(SimulatedPlayerBundle::new(Vec3::new(0.0, + // 4.0, 0.0))); for _ in 0..20 { + // simulation_set.tick(); + // println!("tick over"); + // } + // simulation_set.despawn(entity); + // std::process::exit(0); + + b.iter(|| { + let entity = simulation_set.spawn(SimulatedPlayerBundle::new(Vec3::new(0.5, 2.0, 0.5))); + for _ in 0..20 { + simulation_set.tick(); + } + simulation_set.despawn(entity); + }) +} + +fn bench_pathfinder(c: &mut Criterion) { + c.bench_function("physics", |b| { + run_physics_benchmark(b); + }); +} + +criterion_group!(benches, bench_pathfinder); +criterion_main!(benches); diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs index 408d7b9b4..c6ab46397 100644 --- a/azalea/examples/steal.rs +++ b/azalea/examples/steal.rs @@ -45,7 +45,7 @@ async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<( return Ok(()); }; // bot.goto(BlockPosGoal(chest_block)); - let Some(chest) = bot.open_container(chest_block).await else { + let Some(chest) = bot.open_container_at(chest_block).await else { println!("Couldn't open chest"); return Ok(()); }; diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs deleted file mode 100644 index 7e7b2ca08..000000000 --- a/azalea/examples/testbot.rs +++ /dev/null @@ -1,424 +0,0 @@ -//! a bot for testing new azalea features - -use azalea::ecs::query::With; -use azalea::entity::{metadata::Player, EyeHeight, Position}; -use azalea::interact::HitResultComponent; -use azalea::inventory::ItemSlot; -use azalea::pathfinder::goals::BlockPosGoal; -use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection}; -use azalea::{Account, Client, Event}; -use azalea_client::{InstanceHolder, SprintDirection}; -use azalea_core::position::{ChunkBlockPos, ChunkPos, Vec3}; -use azalea_protocol::packets::game::ClientboundGamePacket; -use azalea_world::heightmap::HeightmapKind; -use azalea_world::{InstanceName, MinecraftEntityId}; -use std::time::Duration; - -#[derive(Default, Clone, Component)] -struct State {} - -#[derive(Default, Clone, Resource)] -struct SwarmState {} - -#[tokio::main] -async fn main() { - { - use parking_lot::deadlock; - use std::thread; - use std::time::Duration; - // Create a background thread which checks for deadlocks every 10s - thread::spawn(move || loop { - thread::sleep(Duration::from_secs(10)); - let deadlocks = deadlock::check_deadlock(); - if deadlocks.is_empty() { - continue; - } - - println!("{} deadlocks detected", deadlocks.len()); - for (i, threads) in deadlocks.iter().enumerate() { - println!("Deadlock #{i}"); - for t in threads { - println!("Thread Id {:#?}", t.thread_id()); - println!("{:#?}", t.backtrace()); - } - } - }); - } - - let mut accounts = Vec::new(); - - for i in 0..1 { - accounts.push(Account::offline(&format!("bot{i}"))); - } - - SwarmBuilder::new() - .add_accounts(accounts.clone()) - .set_handler(handle) - .set_swarm_handler(swarm_handle) - .join_delay(Duration::from_millis(100)) - .start("localhost") - .await - .unwrap(); -} - -async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> { - match event { - Event::Init => { - // bot.set_client_information(azalea_client::ClientInformation { - // view_distance: 2, - // ..Default::default() - // }) - // .await?; - } - Event::Login => { - bot.chat("Hello world"); - } - Event::Chat(m) => { - // println!("client chat message: {}", m.content()); - if m.content() == bot.profile.name { - bot.chat("Bye"); - tokio::time::sleep(Duration::from_millis(50)).await; - bot.disconnect(); - } - let Some(sender) = m.username() else { - return Ok(()); - }; - // let mut ecs = bot.ecs.lock(); - // let entity = bot - // .ecs - // .lock() - // .query::<&Player>() - // .iter(&mut ecs) - // .find(|e| e.name() == Some(sender)); - // let entity = bot.entity_by::>(|name: &Name| name == sender); - let entity = bot.entity_by::, (&GameProfileComponent,)>( - |(profile,): &(&GameProfileComponent,)| profile.name == sender, - ); - match m.content().as_str() { - "whereami" => { - let Some(entity) = entity else { - bot.chat("I can't see you"); - return Ok(()); - }; - let pos = bot.entity_component::(entity); - bot.chat(&format!("You're at {pos:?}")); - } - "whereareyou" => { - let pos = bot.position(); - bot.chat(&format!("I'm at {pos:?}")); - } - "goto" => { - let Some(entity) = entity else { - bot.chat("I can't see you"); - return Ok(()); - }; - let entity_pos = bot.entity_component::(entity); - let target_pos: BlockPos = entity_pos.into(); - println!("going to {target_pos:?}"); - bot.goto(BlockPosGoal(target_pos)); - } - "worldborder" => { - bot.goto(BlockPosGoal(BlockPos::new(30_000_000, 70, 0))); - } - "look" => { - let Some(entity) = entity else { - bot.chat("I can't see you"); - return Ok(()); - }; - let entity_pos = bot - .entity_component::(entity) - .up(bot.entity_component::(entity).into()); - println!("entity_pos: {entity_pos:?}"); - bot.look_at(entity_pos); - } - "jump" => { - bot.set_jumping(true); - } - "walk" => { - bot.walk(WalkDirection::Forward); - } - "sprint" => { - bot.sprint(SprintDirection::Forward); - } - "stop" => { - bot.set_jumping(false); - bot.walk(WalkDirection::None); - } - "lag" => { - std::thread::sleep(Duration::from_millis(1000)); - } - "quit" => { - bot.disconnect(); - tokio::time::sleep(Duration::from_millis(1000)).await; - std::process::exit(0); - } - "inventory" => { - println!("inventory: {:?}", bot.menu()); - } - "findblock" => { - let target_pos = bot.world().read().find_block( - bot.position(), - &azalea::registry::Block::DiamondBlock.into(), - ); - bot.chat(&format!("target_pos: {target_pos:?}",)); - } - "gotoblock" => { - let target_pos = bot.world().read().find_block( - bot.position(), - &azalea::registry::Block::DiamondBlock.into(), - ); - if let Some(target_pos) = target_pos { - // +1 to stand on top of the block - bot.goto(BlockPosGoal(target_pos.up(1))); - } else { - bot.chat("no diamond block found"); - } - } - "mineblock" => { - let target_pos = bot.world().read().find_block( - bot.position(), - &azalea::registry::Block::DiamondBlock.into(), - ); - if let Some(target_pos) = target_pos { - // +1 to stand on top of the block - bot.chat("ok mining diamond block"); - bot.look_at(target_pos.center()); - bot.mine(target_pos).await; - bot.chat("finished mining"); - } else { - bot.chat("no diamond block found"); - } - } - "lever" => { - let target_pos = bot - .world() - .read() - .find_block(bot.position(), &azalea::registry::Block::Lever.into()); - let Some(target_pos) = target_pos else { - bot.chat("no lever found"); - return Ok(()); - }; - bot.goto(BlockPosGoal(target_pos)); - bot.look_at(target_pos.center()); - bot.block_interact(target_pos); - } - "hitresult" => { - let hit_result = bot.get_component::(); - bot.chat(&format!("hit_result: {hit_result:?}",)); - } - "chest" => { - let target_pos = bot - .world() - .read() - .find_block(bot.position(), &azalea::registry::Block::Chest.into()); - let Some(target_pos) = target_pos else { - bot.chat("no chest found"); - return Ok(()); - }; - bot.look_at(target_pos.center()); - let container = bot.open_container(target_pos).await; - println!("container: {container:?}"); - if let Some(container) = container { - if let Some(contents) = container.contents() { - for item in contents { - if let ItemSlot::Present(item) = item { - println!("item: {item:?}"); - } - } - } else { - println!("container was immediately closed"); - } - } else { - println!("no container found"); - } - } - "attack" => { - let mut nearest_entity = None; - let mut nearest_distance = f64::INFINITY; - let mut nearest_pos = Vec3::default(); - let bot_position = bot.position(); - let bot_entity = bot.entity; - let bot_instance_name = bot.component::(); - { - let mut ecs = bot.ecs.lock(); - let mut query = ecs.query_filtered::<( - azalea::ecs::entity::Entity, - &MinecraftEntityId, - &Position, - &InstanceName, - &EyeHeight, - ), With>(); - for (entity, &entity_id, position, instance_name, eye_height) in - query.iter(&ecs) - { - if entity == bot_entity { - continue; - } - if instance_name != &bot_instance_name { - continue; - } - - let distance = bot_position.distance_to(position); - if distance < 4.0 && distance < nearest_distance { - nearest_entity = Some(entity_id); - nearest_distance = distance; - nearest_pos = position.up(**eye_height as f64); - } - } - } - if let Some(nearest_entity) = nearest_entity { - bot.look_at(nearest_pos); - bot.attack(nearest_entity); - bot.chat("attacking"); - let mut ticks = bot.get_tick_broadcaster(); - while ticks.recv().await.is_ok() { - if !bot.has_attack_cooldown() { - break; - } - } - bot.chat("finished attacking"); - } else { - bot.chat("no entities found"); - } - } - "heightmap" => { - let position = bot.position(); - let chunk_pos = ChunkPos::from(position); - let chunk_block_pos = ChunkBlockPos::from(position); - let chunk = bot.world().read().chunks.get(&chunk_pos); - if let Some(chunk) = chunk { - let heightmaps = &chunk.read().heightmaps; - let Some(world_surface_heightmap) = - heightmaps.get(&HeightmapKind::WorldSurface) - else { - bot.chat("no world surface heightmap"); - return Ok(()); - }; - let highest_y = world_surface_heightmap - .get_highest_taken(chunk_block_pos.x, chunk_block_pos.z); - bot.chat(&format!("highest_y: {highest_y}",)); - } else { - bot.chat("no chunk found"); - } - } - "debugblock" => { - // send the block that we're standing on - let block_pos = BlockPos::from(bot.position().down(0.1)); - let block = bot.world().read().get_block_state(&block_pos); - bot.chat(&format!("block: {block:?}")); - } - "debugchunks" => { - { - println!("shared:"); - - let mut ecs = bot.ecs.lock(); - - let instance_holder = bot.query::<&InstanceHolder>(&mut ecs).clone(); - drop(ecs); - let local_chunk_storage = &instance_holder.partial_instance.read().chunks; - let shared_chunk_storage = instance_holder.instance.read(); - - let mut total_loaded_chunks_count = 0; - for (chunk_pos, chunk) in &shared_chunk_storage.chunks.map { - if let Some(chunk) = chunk.upgrade() { - let in_range = local_chunk_storage.in_range(chunk_pos); - println!( - "{chunk_pos:?} has {} references{}", - std::sync::Arc::strong_count(&chunk) - 1, - if in_range { "" } else { " (out of range)" } - ); - total_loaded_chunks_count += 1; - } - } - - println!("local:"); - println!("view range: {}", local_chunk_storage.view_range()); - println!("view center: {:?}", local_chunk_storage.view_center()); - - let mut local_loaded_chunks_count = 0; - for (i, chunk) in local_chunk_storage.chunks().enumerate() { - if let Some(chunk) = chunk { - let chunk_pos = local_chunk_storage.chunk_pos_from_index(i); - println!( - "{chunk_pos:?} (#{i}) has {} references", - std::sync::Arc::strong_count(&chunk) - ); - local_loaded_chunks_count += 1; - } - } - - println!("total loaded chunks: {total_loaded_chunks_count}"); - println!( - "local loaded chunks: {local_loaded_chunks_count}/{}", - local_chunk_storage.chunks().collect::>().len() - ); - } - { - let local_chunk_storage_lock = bot.partial_world(); - let local_chunk_storage = local_chunk_storage_lock.read(); - let current_chunk_loaded = local_chunk_storage - .chunks - .limited_get(&ChunkPos::from(bot.position())); - - bot.chat(&format!( - "current chunk loaded: {}", - current_chunk_loaded.is_some() - )); - } - } - _ => {} - } - } - Event::Packet(packet) => { - if let ClientboundGamePacket::Login(_) = *packet { - println!("login packet"); - } - } - Event::Disconnect(reason) => { - if let Some(reason) = reason { - println!("bot got kicked for reason: {}", reason.to_ansi()); - } else { - println!("bot got kicked"); - } - } - _ => {} - } - - Ok(()) -} - -async fn swarm_handle( - mut swarm: Swarm, - event: SwarmEvent, - _state: SwarmState, -) -> anyhow::Result<()> { - match &event { - SwarmEvent::Disconnect(account) => { - println!("bot got kicked! {}", account.username); - tokio::time::sleep(Duration::from_secs(5)).await; - swarm.add_and_retry_forever(account, State::default()).await; - } - SwarmEvent::Chat(m) => { - println!("swarm chat message: {}", m.message().to_ansi()); - if m.message().to_string() == " world" { - for (name, world) in &swarm.instance_container.read().instances { - println!("world name: {name}"); - if let Some(w) = world.upgrade() { - for chunk_pos in w.read().chunks.map.values() { - println!("chunk: {chunk_pos:?}"); - } - } else { - println!("nvm world is gone"); - } - } - } - if m.message().to_string() == " hi" { - for bot in swarm { - bot.chat("hello"); - } - } - } - _ => {} - } - Ok(()) -} diff --git a/azalea/examples/testbot/commands.rs b/azalea/examples/testbot/commands.rs new file mode 100644 index 000000000..9cdb3cb79 --- /dev/null +++ b/azalea/examples/testbot/commands.rs @@ -0,0 +1,45 @@ +pub mod combat; +pub mod debug; +pub mod movement; + +use azalea::brigadier::prelude::*; +use azalea::chat::ChatPacket; +use azalea::ecs::prelude::*; +use azalea::entity::metadata::Player; +use azalea::Client; +use azalea::GameProfileComponent; +use parking_lot::Mutex; + +use crate::State; + +pub type Ctx = CommandContext>; + +pub struct CommandSource { + pub bot: Client, + pub state: State, + pub chat: ChatPacket, +} + +impl CommandSource { + pub fn reply(&self, message: &str) { + if self.chat.is_whisper() { + self.bot + .chat(&format!("/w {} {}", self.chat.username().unwrap(), message)); + } else { + self.bot.chat(message); + } + } + + pub fn entity(&mut self) -> Option { + let username = self.chat.username()?; + self.bot.entity_by::, &GameProfileComponent>( + |profile: &&GameProfileComponent| profile.name == username, + ) + } +} + +pub fn register_commands(commands: &mut CommandDispatcher>) { + combat::register(commands); + debug::register(commands); + movement::register(commands); +} diff --git a/azalea/examples/testbot/commands/combat.rs b/azalea/examples/testbot/commands/combat.rs new file mode 100644 index 000000000..b440b3ace --- /dev/null +++ b/azalea/examples/testbot/commands/combat.rs @@ -0,0 +1,26 @@ +use azalea::brigadier::prelude::*; +use parking_lot::Mutex; + +use super::{CommandSource, Ctx}; + +pub fn register(commands: &mut CommandDispatcher>) { + commands.register( + literal("killaura").then(argument("enabled", bool()).executes(|ctx: &Ctx| { + let enabled = get_bool(ctx, "enabled").unwrap(); + let source = ctx.source.lock(); + let bot = source.bot.clone(); + { + let mut ecs = bot.ecs.lock(); + let mut entity = ecs.entity_mut(bot.entity); + let mut state = entity.get_mut::().unwrap(); + state.killaura = enabled + } + source.reply(if enabled { + "Enabled killaura" + } else { + "Disabled killaura" + }); + 1 + })), + ); +} diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs new file mode 100644 index 000000000..ae0808cb0 --- /dev/null +++ b/azalea/examples/testbot/commands/debug.rs @@ -0,0 +1,105 @@ +//! Commands for debugging and getting the current state of the bot. + +use azalea::{ + brigadier::prelude::*, + entity::{LookDirection, Position}, + interact::HitResultComponent, + world::MinecraftEntityId, +}; +use parking_lot::Mutex; + +use super::{CommandSource, Ctx}; + +pub fn register(commands: &mut CommandDispatcher>) { + commands.register(literal("ping").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + source.reply("pong!"); + 1 + })); + + commands.register(literal("whereami").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + let Some(entity) = source.entity() else { + source.reply("You aren't in render distance!"); + return 0; + }; + let position = source.bot.entity_component::(entity); + source.reply(&format!( + "You are at {}, {}, {}", + position.x, position.y, position.z + )); + 1 + })); + + commands.register(literal("entityid").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + let Some(entity) = source.entity() else { + source.reply("You aren't in render distance!"); + return 0; + }; + let entity_id = source.bot.entity_component::(entity); + source.reply(&format!( + "Your Minecraft ID is {} and your ECS id is {entity:?}", + *entity_id + )); + 1 + })); + + let whereareyou = |ctx: &Ctx| { + let source = ctx.source.lock(); + let position = source.bot.position(); + source.reply(&format!( + "I'm at {}, {}, {}", + position.x, position.y, position.z + )); + 1 + }; + commands.register(literal("whereareyou").executes(whereareyou)); + commands.register(literal("pos").executes(whereareyou)); + + commands.register(literal("whoareyou").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + source.reply(&format!( + "I am {} ({})", + source.bot.username(), + source.bot.uuid() + )); + 1 + })); + + commands.register(literal("getdirection").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + let direction = source.bot.component::(); + source.reply(&format!( + "I'm looking at {}, {}", + direction.y_rot, direction.x_rot + )); + 1 + })); + + commands.register(literal("health").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + + let health = source.bot.health(); + source.reply(&format!("I have {health} health")); + 1 + })); + + commands.register(literal("lookingat").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + + let hit_result = *source.bot.component::(); + + if hit_result.miss { + source.reply("I'm not looking at anything"); + return 1; + } + + let block_pos = hit_result.block_pos; + let block = source.bot.world().read().get_block_state(&block_pos); + + source.reply(&format!("I'm looking at {block:?} at {block_pos:?}")); + + 1 + })); +} diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs new file mode 100644 index 000000000..4957533f2 --- /dev/null +++ b/azalea/examples/testbot/commands/movement.rs @@ -0,0 +1,191 @@ +use std::time::Duration; + +use azalea::{ + brigadier::prelude::*, + entity::{EyeHeight, Position}, + pathfinder::goals::{BlockPosGoal, XZGoal}, + prelude::*, + BlockPos, SprintDirection, WalkDirection, +}; +use parking_lot::Mutex; + +use crate::BotTask; + +use super::{CommandSource, Ctx}; + +pub fn register(commands: &mut CommandDispatcher>) { + commands.register( + literal("goto") + .executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + println!("got goto"); + // look for the sender + let Some(entity) = source.entity() else { + source.reply("I can't see you!"); + return 0; + }; + let Some(position) = source.bot.get_entity_component::(entity) else { + source.reply("I can't see you!"); + return 0; + }; + source.reply("ok"); + source.bot.goto(BlockPosGoal(BlockPos::from(position))); + 1 + }) + .then(literal("xz").then(argument("x", integer()).then( + argument("z", integer()).executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + let x = get_integer(ctx, "x").unwrap(); + let z = get_integer(ctx, "z").unwrap(); + println!("goto xz {x} {z}"); + source.reply("ok"); + source.bot.goto(XZGoal { x, z }); + 1 + }), + ))) + .then(argument("x", integer()).then(argument("y", integer()).then( + argument("z", integer()).executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + let x = get_integer(ctx, "x").unwrap(); + let y = get_integer(ctx, "y").unwrap(); + let z = get_integer(ctx, "z").unwrap(); + println!("goto xyz {x} {y} {z}"); + source.reply("ok"); + source.bot.goto(BlockPosGoal(BlockPos::new(x, y, z))); + 1 + }), + ))), + ); + + commands.register(literal("down").executes(|ctx: &Ctx| { + let source = ctx.source.clone(); + tokio::spawn(async move { + let mut bot = source.lock().bot.clone(); + let position = BlockPos::from(bot.position()); + source.lock().reply("mining..."); + bot.mine(position.down(1)).await; + source.lock().reply("done"); + }); + 1 + })); + + commands.register( + literal("look") + .executes(|ctx: &Ctx| { + // look for the sender + let mut source = ctx.source.lock(); + let Some(entity) = source.entity() else { + source.reply("I can't see you!"); + return 0; + }; + let Some(position) = source.bot.get_entity_component::(entity) else { + source.reply("I can't see you!"); + return 0; + }; + let eye_height = source + .bot + .get_entity_component::(entity) + .map(|h| *h) + .unwrap_or_default(); + source.bot.look_at(position.up(eye_height as f64)); + 1 + }) + .then(argument("x", integer()).then(argument("y", integer()).then( + argument("z", integer()).executes(|ctx: &Ctx| { + let pos = BlockPos::new( + get_integer(ctx, "x").unwrap(), + get_integer(ctx, "y").unwrap(), + get_integer(ctx, "z").unwrap(), + ); + println!("{:?}", pos); + let mut source = ctx.source.lock(); + source.bot.look_at(pos.center()); + 1 + }), + ))), + ); + + commands.register( + literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| { + let mut seconds = get_float(ctx, "seconds").unwrap(); + let source = ctx.source.lock(); + let mut bot = source.bot.clone(); + + if seconds < 0. { + bot.walk(WalkDirection::Backward); + seconds = -seconds; + } else { + bot.walk(WalkDirection::Forward); + } + + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs_f32(seconds)).await; + bot.walk(WalkDirection::None); + }); + source.reply(&format!("ok, walking for {seconds} seconds")); + 1 + })), + ); + commands.register( + literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| { + let seconds = get_float(ctx, "seconds").unwrap(); + let source = ctx.source.lock(); + let mut bot = source.bot.clone(); + bot.sprint(SprintDirection::Forward); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs_f32(seconds)).await; + bot.walk(WalkDirection::None); + }); + source.reply(&format!("ok, spriting for {seconds} seconds")); + 1 + })), + ); + + commands.register(literal("north").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + source.bot.set_direction(180., 0.); + source.reply("ok"); + 1 + })); + commands.register(literal("south").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + source.bot.set_direction(0., 0.); + source.reply("ok"); + 1 + })); + commands.register(literal("east").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + source.bot.set_direction(-90., 0.); + source.reply("ok"); + 1 + })); + commands.register(literal("west").executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + source.bot.set_direction(90., 0.); + source.reply("ok"); + 1 + })); + commands.register( + literal("jump") + .executes(|ctx: &Ctx| { + let mut source = ctx.source.lock(); + source.bot.jump(); + source.reply("ok"); + 1 + }) + .then(argument("enabled", bool()).executes(|ctx: &Ctx| { + let jumping = get_bool(ctx, "enabled").unwrap(); + let mut source = ctx.source.lock(); + source.bot.set_jumping(jumping); + 1 + })), + ); + + commands.register(literal("stop").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + source.bot.stop_pathfinding(); + source.reply("ok"); + *source.state.task.lock() = BotTask::None; + 1 + })); +} diff --git a/azalea/examples/testbot/killaura.rs b/azalea/examples/testbot/killaura.rs new file mode 100644 index 000000000..d86356fe2 --- /dev/null +++ b/azalea/examples/testbot/killaura.rs @@ -0,0 +1,48 @@ +use azalea::{ + ecs::prelude::*, + entity::{metadata::AbstractMonster, Dead, LocalEntity, Position}, + prelude::*, + world::{InstanceName, MinecraftEntityId}, +}; + +use crate::State; + +pub fn tick(mut bot: Client, state: State) -> anyhow::Result<()> { + if !state.killaura { + return Ok(()); + } + if bot.has_attack_cooldown() { + return Ok(()); + } + let mut nearest_entity = None; + let mut nearest_distance = f64::INFINITY; + let bot_position = bot.eye_position(); + let bot_instance_name = bot.component::(); + { + let mut ecs = bot.ecs.lock(); + let mut query = ecs + .query_filtered::<(&MinecraftEntityId, &Position, &InstanceName), ( + With, + Without, + Without, + )>(); + for (&entity_id, position, instance_name) in query.iter(&ecs) { + if instance_name != &bot_instance_name { + continue; + } + + let distance = bot_position.distance_to(position); + if distance < 4. && distance < nearest_distance { + nearest_entity = Some(entity_id); + nearest_distance = distance; + } + } + } + if let Some(nearest_entity) = nearest_entity { + println!("attacking {:?}", nearest_entity); + println!("distance {:?}", nearest_distance); + bot.attack(nearest_entity); + } + + Ok(()) +} diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs new file mode 100644 index 000000000..86395b7e6 --- /dev/null +++ b/azalea/examples/testbot/main.rs @@ -0,0 +1,199 @@ +//! A relatively simple bot for demonstrating some of Azalea's capabilities. +//! +//! Usage: +//! - Modify the consts below if necessary. +//! - Run `cargo r --example testbot` +//! - Commands are prefixed with `!` in chat. You can send them either in public +//! chat or as a /msg. +//! - Some commands to try are `!goto`, `!killaura true`, `!down`. Check the +//! `commands` directory to see all of them. + +#![feature(async_closure)] +#![feature(trivial_bounds)] + +mod commands; +pub mod killaura; + +use azalea::pathfinder::PathfinderDebugParticles; +use azalea::ClientInformation; + +use azalea::brigadier::command_dispatcher::CommandDispatcher; +use azalea::ecs::prelude::*; +use azalea::prelude::*; +use azalea::swarm::prelude::*; +use commands::{register_commands, CommandSource}; +use parking_lot::Mutex; +use std::sync::Arc; +use std::time::Duration; + +const USERNAME: &str = "azalea"; +const ADDRESS: &str = "localhost"; +/// The bot will only listen to commands sent by the player with this username. +const OWNER_USERNAME: &str = "py5"; +/// Whether the bot should run /particle a ton of times to show where it's +/// pathfinding to. You should only have this on if the bot has operator +/// permissions, otherwise it'll just spam the server console unnecessarily. +const PATHFINDER_DEBUG_PARTICLES: bool = false; + +#[tokio::main] +async fn main() { + { + use parking_lot::deadlock; + use std::thread; + use std::time::Duration; + + // Create a background thread which checks for deadlocks every 10s + thread::spawn(move || loop { + thread::sleep(Duration::from_secs(10)); + let deadlocks = deadlock::check_deadlock(); + if deadlocks.is_empty() { + continue; + } + + println!("{} deadlocks detected", deadlocks.len()); + for (i, threads) in deadlocks.iter().enumerate() { + println!("Deadlock #{i}"); + for t in threads { + println!("Thread Id {:#?}", t.thread_id()); + println!("{:#?}", t.backtrace()); + } + } + }); + } + + let account = Account::offline(USERNAME); + + let mut commands = CommandDispatcher::new(); + register_commands(&mut commands); + let commands = Arc::new(commands); + + let builder = SwarmBuilder::new(); + builder + .set_handler(handle) + .set_swarm_handler(swarm_handle) + .add_account_with_state( + account, + State { + commands: commands.clone(), + ..Default::default() + }, + ) + .join_delay(Duration::from_millis(100)) + .start(ADDRESS) + .await + .unwrap(); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum BotTask { + #[default] + None, +} + +#[derive(Component, Clone)] +pub struct State { + pub commands: Arc>>, + pub killaura: bool, + pub task: Arc>, +} + +impl Default for State { + fn default() -> Self { + Self { + commands: Arc::new(CommandDispatcher::new()), + killaura: true, + task: Arc::new(Mutex::new(BotTask::None)), + } + } +} + +#[derive(Resource, Default, Clone)] +struct SwarmState; + +async fn handle(bot: Client, event: azalea::Event, state: State) -> anyhow::Result<()> { + match event { + azalea::Event::Init => { + bot.set_client_information(ClientInformation { + view_distance: 32, + ..Default::default() + }) + .await?; + if PATHFINDER_DEBUG_PARTICLES { + bot.ecs + .lock() + .entity_mut(bot.entity) + .insert(PathfinderDebugParticles); + } + } + azalea::Event::Chat(chat) => { + let (Some(username), content) = chat.split_sender_and_content() else { + return Ok(()); + }; + if username != OWNER_USERNAME { + return Ok(()); + } + + println!("{:?}", chat.message()); + + let command = if chat.is_whisper() { + Some(content) + } else { + content.strip_prefix('!').map(|s| s.to_owned()) + }; + if let Some(command) = command { + match state.commands.execute( + command, + Mutex::new(CommandSource { + bot: bot.clone(), + chat: chat.clone(), + state: state.clone(), + }), + ) { + Ok(_) => {} + Err(err) => { + eprintln!("{err:?}"); + let command_source = CommandSource { + bot, + chat: chat.clone(), + state: state.clone(), + }; + command_source.reply(&format!("{err:?}")); + } + } + } + } + azalea::Event::Tick => { + killaura::tick(bot.clone(), state.clone())?; + + let task = *state.task.lock(); + match task { + BotTask::None => {} + } + } + _ => {} + } + + Ok(()) +} +async fn swarm_handle( + mut swarm: Swarm, + event: SwarmEvent, + _state: SwarmState, +) -> anyhow::Result<()> { + match &event { + SwarmEvent::Disconnect(account) => { + println!("bot got kicked! {}", account.username); + tokio::time::sleep(Duration::from_secs(5)).await; + swarm.add_and_retry_forever(account, State::default()).await; + } + SwarmEvent::Chat(chat) => { + if chat.message().to_string() == "The particle was not visible for anybody" { + return Ok(()); + } + println!("{}", chat.message().to_ansi()); + } + _ => {} + } + + Ok(()) +} diff --git a/azalea/examples/todo/craft_dig_straight_down.rs b/azalea/examples/todo/craft_dig_straight_down.rs index 6672eaa49..7b75d2db9 100644 --- a/azalea/examples/todo/craft_dig_straight_down.rs +++ b/azalea/examples/todo/craft_dig_straight_down.rs @@ -38,7 +38,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))) .await; let chest = bot - .open_container(&bot.world().find_block(azalea::Block::Chest)) + .open_container_at(&bot.world().find_block(azalea::Block::Chest)) .await .unwrap(); bot.take_amount_from_container(&chest, 5, |i| i.id == "#minecraft:planks") diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 0f4170613..c354390ea 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -23,11 +23,12 @@ impl Plugin for ContainerPlugin { } pub trait ContainerClientExt { - fn open_container( + fn open_container_at( &mut self, pos: BlockPos, ) -> impl Future> + Send; fn open_inventory(&mut self) -> Option; + fn get_open_container(&self) -> Option; } impl ContainerClientExt for Client { @@ -45,10 +46,10 @@ impl ContainerClientExt for Client { /// bot.chat("no chest found"); /// return; /// }; - /// let container = bot.open_container(target_pos).await; + /// let container = bot.open_container_at(target_pos).await; /// # } /// ``` - async fn open_container(&mut self, pos: BlockPos) -> Option { + async fn open_container_at(&mut self, pos: BlockPos) -> Option { self.ecs .lock() .entity_mut(self.entity) @@ -70,10 +71,7 @@ impl ContainerClientExt for Client { if inventory.id == 0 { None } else { - Some(ContainerHandle { - id: inventory.id, - client: self.clone(), - }) + Some(ContainerHandle::new(inventory.id, self.clone())) } } @@ -94,40 +92,55 @@ impl ContainerClientExt for Client { .expect("no inventory"); if inventory.id == 0 { - Some(ContainerHandle { - id: 0, - client: self.clone(), - }) + Some(ContainerHandle::new(0, self.clone())) } else { None } } + + /// Get a handle to the open container. This will return None if no + /// container is open. This will not close the container when it's dropped. + /// + /// See [`Client::open_inventory`] or [`Client::menu`] if you want to open + /// your own inventory. + fn get_open_container(&self) -> Option { + let ecs = self.ecs.lock(); + let inventory = ecs + .get::(self.entity) + .expect("no inventory"); + + if inventory.id == 0 { + None + } else { + Some(ContainerHandleRef { + id: inventory.id, + client: self.clone(), + }) + } + } } -/// A handle to the open container. The container will be closed once this is -/// dropped. -pub struct ContainerHandle { - /// The id of the container. If this is 0, that means it's the player's - /// inventory. +/// A handle to a container that may be open. This does not close the container +/// when it's dropped. See [`ContainerHandle`] if that behavior is desired. +pub struct ContainerHandleRef { id: u8, client: Client, } -impl Drop for ContainerHandle { - fn drop(&mut self) { - self.client.ecs.lock().send_event(CloseContainerEvent { - entity: self.client.entity, - id: self.id, - }); - } -} -impl Debug for ContainerHandle { +impl Debug for ContainerHandleRef { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("ContainerHandle") - .field("id", &self.id) + .field("id", &self.id()) .finish() } } -impl ContainerHandle { +impl ContainerHandleRef { + pub fn close(&self) { + self.client.ecs.lock().send_event(CloseContainerEvent { + entity: self.client.entity, + id: self.id, + }); + } + /// Get the id of the container. If this is 0, that means it's the player's /// inventory. Otherwise, the number isn't really meaningful since only one /// container can be open at a time. @@ -175,6 +188,55 @@ impl ContainerHandle { } } +/// A handle to the open container. The container will be closed once this is +/// dropped. +pub struct ContainerHandle(ContainerHandleRef); + +impl Drop for ContainerHandle { + fn drop(&mut self) { + self.0.close(); + } +} +impl Debug for ContainerHandle { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ContainerHandle") + .field("id", &self.id()) + .finish() + } +} +impl ContainerHandle { + fn new(id: u8, client: Client) -> Self { + Self(ContainerHandleRef { id, client }) + } + + /// Get the id of the container. If this is 0, that means it's the player's + /// inventory. Otherwise, the number isn't really meaningful since only one + /// container can be open at a time. + pub fn id(&self) -> u8 { + self.0.id() + } + + /// Returns the menu of the container. If the container is closed, this + /// will return `None`. + /// + /// Note that any modifications you make to the `Menu` you're given will not + /// actually cause any packets to be sent. If you're trying to modify your + /// inventory, use [`ContainerHandle::click`] instead + pub fn menu(&self) -> Option { + self.0.menu() + } + + /// Returns the item slots in the container, not including the player's + /// inventory. If the container is closed, this will return `None`. + pub fn contents(&self) -> Option> { + self.0.contents() + } + + pub fn click(&self, operation: impl Into) { + self.0.click(operation); + } +} + #[derive(Component, Debug)] pub struct WaitingForInventoryOpen; diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index fd2cb83ac..6e18ff7d4 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -15,6 +15,8 @@ pub mod pathfinder; pub mod prelude; pub mod swarm; +use std::net::SocketAddr; + use app::Plugins; pub use azalea_auth as auth; pub use azalea_block as blocks; @@ -29,6 +31,7 @@ pub use azalea_core::{ resource_location::ResourceLocation, }; pub use azalea_entity as entity; +pub use azalea_physics as physics; pub use azalea_protocol as protocol; pub use azalea_registry as registry; pub use azalea_world as world; @@ -188,6 +191,25 @@ where } self.swarm.start(address).await } + + /// Do the same as [`Self::start`], but allow passing in a custom resolved + /// address. This is useful if the address you're connecting to doesn't + /// resolve to anything, like if the server uses the address field to pass + /// custom data (like Bungeecord or Forge). + pub async fn start_with_custom_resolved_address( + mut self, + account: Account, + address: impl TryInto, + resolved_address: SocketAddr, + ) -> Result { + self.swarm.accounts = vec![account]; + if self.swarm.states.is_empty() { + self.swarm.states = vec![S::default()]; + } + self.swarm + .start_with_custom_resolved_address(address, resolved_address) + .await + } } impl Default for ClientBuilder { fn default() -> Self { diff --git a/azalea/src/nearest_entity.rs b/azalea/src/nearest_entity.rs index 38c9886ea..363c8fb08 100644 --- a/azalea/src/nearest_entity.rs +++ b/azalea/src/nearest_entity.rs @@ -2,7 +2,7 @@ use azalea_entity::Position; use azalea_world::{InstanceName, MinecraftEntityId}; use bevy_ecs::{ prelude::Entity, - query::{ReadOnlyWorldQuery, With}, + query::{QueryFilter, With}, system::{Query, SystemParam}, }; @@ -44,7 +44,7 @@ use bevy_ecs::{ #[derive(SystemParam)] pub struct EntityFinder<'w, 's, F = ()> where - F: ReadOnlyWorldQuery + 'static, + F: QueryFilter + 'static, { all_entities: Query<'w, 's, (&'static Position, &'static InstanceName), With>, @@ -59,7 +59,7 @@ where impl<'w, 's, 'a, F> EntityFinder<'w, 's, F> where - F: ReadOnlyWorldQuery + 'static, + F: QueryFilter + 'static, { /// Gets the nearest entity to the given position and world instance name. /// This method will return `None` if there are no entities within range. If diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index cc1e2242d..9e48ba2df 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -78,7 +78,7 @@ where .get(&neighbor.movement.target) .map(|n| n.g_score) .unwrap_or(f32::INFINITY); - if tentative_g_score - neighbor_g_score < MIN_IMPROVEMENT { + if neighbor_g_score - tentative_g_score > MIN_IMPROVEMENT { let heuristic = heuristic(neighbor.movement.target); let f_score = tentative_g_score + heuristic; nodes.insert( diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 9fd769e64..a1bdaaad3 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -208,9 +208,11 @@ fn goto_listener( let thread_pool = AsyncComputeTaskPool::get(); for event in events.read() { - let (mut pathfinder, executing_path, position, instance_name, inventory) = query - .get_mut(event.entity) - .expect("Called goto on an entity that's not in the world"); + let Ok((mut pathfinder, executing_path, position, instance_name, inventory)) = + query.get_mut(event.entity) + else { + continue; + }; if event.goal.success(BlockPos::from(position)) { // we're already at the goal, nothing to do @@ -616,7 +618,7 @@ fn check_for_path_obstruction( } } -fn recalculate_near_end_of_path( +pub fn recalculate_near_end_of_path( mut query: Query<(Entity, &mut Pathfinder, &mut ExecutingPath)>, mut walk_events: EventWriter, mut goto_events: EventWriter, diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index ab3b340e8..2803b846f 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -7,7 +7,7 @@ use azalea_client::{ }; use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; use azalea_entity::{ - attributes::AttributeInstance, Attributes, EntityDimensions, Physics, Position, + attributes::AttributeInstance, Attributes, EntityDimensions, LookDirection, Physics, Position, }; use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance}; use bevy_app::App; @@ -20,6 +20,7 @@ pub struct SimulatedPlayerBundle { pub position: Position, pub physics: Physics, pub physics_state: PhysicsState, + pub look_direction: LookDirection, pub attributes: Attributes, pub inventory: InventoryComponent, } @@ -35,6 +36,7 @@ impl SimulatedPlayerBundle { position: Position::new(position), physics: Physics::new(dimensions, &position), physics_state: PhysicsState::default(), + look_direction: LookDirection::new(0.0, 0.0), attributes: Attributes { speed: AttributeInstance::new(0.1), attack_speed: AttributeInstance::new(4.0), @@ -44,6 +46,82 @@ impl SimulatedPlayerBundle { } } +fn simulation_instance_name() -> ResourceLocation { + ResourceLocation::new("azalea:simulation") +} + +fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc>) { + let instance_name = simulation_instance_name(); + + let instance = Arc::new(RwLock::new(Instance { + chunks, + ..Default::default() + })); + + let mut app = App::new(); + // we don't use all the default azalea plugins because we don't need all of them + app.add_plugins(( + azalea_physics::PhysicsPlugin, + azalea_entity::EntityPlugin, + azalea_client::movement::PlayerMovePlugin, + super::PathfinderPlugin, + crate::BotPlugin, + azalea_client::task_pool::TaskPoolPlugin::default(), + // for mining + azalea_client::inventory::InventoryPlugin, + azalea_client::mining::MinePlugin, + azalea_client::interact::InteractPlugin, + )) + .insert_resource(InstanceContainer { + instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] + .iter() + .cloned() + .collect(), + }) + .add_event::(); + + app.edit_schedule(bevy_app::Main, |schedule| { + schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); + }); + + (app, instance) +} + +fn create_simulation_player_complete_bundle( + instance: Arc>, + player: &SimulatedPlayerBundle, +) -> impl Bundle { + let instance_name = simulation_instance_name(); + + ( + MinecraftEntityId(0), + azalea_entity::LocalEntity, + azalea_entity::metadata::PlayerMetadataBundle::default(), + azalea_entity::EntityBundle::new( + Uuid::nil(), + *player.position, + azalea_registry::EntityKind::Player, + instance_name, + ), + azalea_client::InstanceHolder { + // partial_instance is never actually used by the pathfinder so + partial_instance: Arc::new(RwLock::new(PartialInstance::default())), + instance: instance.clone(), + }, + InventoryComponent::default(), + ) +} + +fn create_simulation_player( + ecs: &mut World, + instance: Arc>, + player: SimulatedPlayerBundle, +) -> Entity { + let mut entity = ecs.spawn(create_simulation_player_complete_bundle(instance, &player)); + entity.insert(player); + entity.id() +} + /// Simulate the Minecraft world to see if certain movements would be possible. pub struct Simulation { pub app: App, @@ -53,71 +131,47 @@ pub struct Simulation { impl Simulation { pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self { - let instance_name = ResourceLocation::new("azalea:simulation"); - - let instance = Arc::new(RwLock::new(Instance { - chunks, - ..Default::default() - })); - - let mut app = App::new(); - // we don't use all the default azalea plugins because we don't need all of them - app.add_plugins(( - azalea_physics::PhysicsPlugin, - azalea_entity::EntityPlugin, - azalea_client::movement::PlayerMovePlugin, - super::PathfinderPlugin, - crate::BotPlugin, - azalea_client::task_pool::TaskPoolPlugin::default(), - // for mining - azalea_client::inventory::InventoryPlugin, - azalea_client::mining::MinePlugin, - azalea_client::interact::InteractPlugin, - )) - .insert_resource(InstanceContainer { - instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] - .iter() - .cloned() - .collect(), - }) - .add_event::(); - - app.edit_schedule(bevy_app::Main, |schedule| { - schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); - }); - - let mut entity = app.world.spawn(( - MinecraftEntityId(0), - azalea_entity::LocalEntity, - azalea_entity::metadata::PlayerMetadataBundle::default(), - azalea_entity::EntityBundle::new( - Uuid::nil(), - *player.position, - azalea_registry::EntityKind::Player, - instance_name, - ), - azalea_client::InstanceHolder { - // partial_instance is never actually used by the pathfinder so - partial_instance: Arc::new(RwLock::new(PartialInstance::default())), - instance: instance.clone(), - }, - InventoryComponent::default(), - )); - entity.insert(player); - - let entity_id = entity.id(); - + let (mut app, instance) = create_simulation_instance(chunks); + let entity = create_simulation_player(&mut app.world, instance.clone(), player); Self { app, - entity: entity_id, + entity, _instance: instance, } } + pub fn tick(&mut self) { - self.app.world.run_schedule(GameTick); self.app.update(); + self.app.world.run_schedule(GameTick); } pub fn position(&self) -> Vec3 { **self.app.world.get::(self.entity).unwrap() } } + +/// A set of simulations, useful for efficiently doing multiple simulations. +pub struct SimulationSet { + pub app: App, + instance: Arc>, +} +impl SimulationSet { + pub fn new(chunks: ChunkStorage) -> Self { + let (app, instance) = create_simulation_instance(chunks); + Self { app, instance } + } + pub fn tick(&mut self) { + self.app.update(); + self.app.world.run_schedule(GameTick); + } + + pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity { + create_simulation_player(&mut self.app.world, self.instance.clone(), player) + } + pub fn despawn(&mut self, entity: Entity) { + self.app.world.despawn(entity); + } + + pub fn position(&self, entity: Entity) -> Vec3 { + **self.app.world.get::(entity).unwrap() + } +} diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs index a5a273fb2..2d8590266 100644 --- a/azalea/src/pathfinder/world.rs +++ b/azalea/src/pathfinder/world.rs @@ -157,9 +157,7 @@ impl CachedWorld { } let world = self.world_lock.read(); - let Some(chunk) = world.chunks.get(&chunk_pos) else { - return None; - }; + let chunk = world.chunks.get(&chunk_pos)?; let chunk = chunk.read(); let sections: Vec = chunk @@ -491,13 +489,9 @@ pub fn is_block_state_solid(block: BlockState) -> bool { #[cfg(test)] mod tests { - use std::sync::Arc; use super::*; - use azalea_block::BlockState; - use azalea_core::position::ChunkPos; use azalea_world::{Chunk, ChunkStorage, PartialInstance}; - use parking_lot::RwLock; #[test] fn test_is_passable() { diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index ff1a57029..3b056554b 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -312,6 +312,28 @@ where /// /// [`ServerAddress`]: azalea_protocol::ServerAddress pub async fn start(self, address: impl TryInto) -> Result { + // convert the TryInto into a ServerAddress + let address: ServerAddress = match address.try_into() { + Ok(address) => address, + Err(_) => return Err(StartError::InvalidAddress), + }; + + // resolve the address + let resolved_address = resolver::resolve_address(&address).await?; + + self.start_with_custom_resolved_address(address, resolved_address) + .await + } + + /// Do the same as [`Self::start`], but allow passing in a custom resolved + /// address. This is useful if the address you're connecting to doesn't + /// resolve to anything, like if the server uses the address field to pass + /// custom data (like Bungeecord or Forge). + pub async fn start_with_custom_resolved_address( + self, + address: impl TryInto, + resolved_address: SocketAddr, + ) -> Result { assert_eq!( self.accounts.len(), self.states.len(), @@ -324,9 +346,6 @@ where Err(_) => return Err(StartError::InvalidAddress), }; - // resolve the address - let resolved_address = resolver::resolve_address(&address).await?; - let instance_container = Arc::new(RwLock::new(InstanceContainer::default())); // we can't modify the swarm plugins after this @@ -546,6 +565,23 @@ impl Swarm { let address = self.address.read().clone(); let resolved_address = *self.resolved_address.read(); + self.add_with_custom_address(account, state, address, resolved_address) + .await + } + /// Add a new account to the swarm, using the given host and socket + /// address. This is useful if you want bots in the same swarm to connect to + /// different addresses. Usually you'll just want [`Self::add`] though. + /// + /// # Errors + /// + /// Returns an `Err` if the bot could not do a handshake successfully. + pub async fn add_with_custom_address( + &mut self, + account: &Account, + state: S, + address: ServerAddress, + resolved_address: SocketAddr, + ) -> Result { let (bot, mut rx) = Client::start_client( self.ecs_lock.clone(), account,