diff options
125 files changed, 17605 insertions, 12157 deletions
@@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -30,9 +30,20 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.2" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "getrandom", @@ -52,9 +63,20 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue 2.1.0", + "event-listener", + "futures-core", +] [[package]] name = "async-compression" @@ -70,10 +92,34 @@ dependencies = [ ] [[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue 2.1.0", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] name = "async-recursion" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" dependencies = [ "proc-macro2", "quote", @@ -81,10 +127,16 @@ dependencies = [ ] [[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" dependencies = [ "proc-macro2", "quote", @@ -118,11 +170,17 @@ dependencies = [ "azalea-chat", "azalea-client", "azalea-core", + "azalea-ecs", "azalea-physics", "azalea-protocol", + "azalea-registry", "azalea-world", + "bevy_tasks", + "derive_more", "env_logger 0.10.0", "futures", + "futures-lite", + "iyes_loopless", "log", "nohash-hasher", "num-traits", @@ -221,9 +279,17 @@ dependencies = [ "azalea-chat", "azalea-core", "azalea-crypto", + "azalea-ecs", "azalea-physics", "azalea-protocol", + "azalea-registry", "azalea-world", + "bevy_tasks", + "bevy_time", + "derive_more", + "env_logger 0.9.3", + "futures", + "iyes_loopless", "log", "nohash-hasher", "once_cell", @@ -242,6 +308,7 @@ dependencies = [ "azalea-buf", "azalea-chat", "azalea-nbt", + "bevy_ecs", "uuid", ] @@ -261,6 +328,27 @@ dependencies = [ ] [[package]] +name = "azalea-ecs" +version = "0.5.0" +dependencies = [ + "azalea-ecs-macros", + "bevy_app", + "bevy_ecs", + "iyes_loopless", + "tokio", +] + +[[package]] +name = "azalea-ecs-macros" +version = "0.5.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "toml 0.7.0", +] + +[[package]] name = "azalea-language" version = "0.5.0" dependencies = [ @@ -273,7 +361,7 @@ dependencies = [ name = "azalea-nbt" version = "0.5.0" dependencies = [ - "ahash", + "ahash 0.8.3", "azalea-buf", "byteorder", "criterion", @@ -290,7 +378,10 @@ version = "0.5.0" dependencies = [ "azalea-block", "azalea-core", + "azalea-ecs", + "azalea-registry", "azalea-world", + "iyes_loopless", "once_cell", "parking_lot", "uuid", @@ -314,6 +405,7 @@ dependencies = [ "azalea-protocol-macros", "azalea-registry", "azalea-world", + "bevy_ecs", "byteorder", "bytes", "flate2", @@ -347,6 +439,7 @@ version = "0.5.0" dependencies = [ "azalea-buf", "azalea-registry-macros", + "enum-as-inner", ] [[package]] @@ -366,11 +459,15 @@ dependencies = [ "azalea-buf", "azalea-chat", "azalea-core", + "azalea-ecs", "azalea-nbt", "azalea-registry", + "derive_more", "enum-as-inner", + "iyes_loopless", "log", "nohash-hasher", + "once_cell", "parking_lot", "thiserror", "uuid", @@ -378,24 +475,200 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.5.4", + "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.13.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bevy_app" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "downcast-rs", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7baf73c58d41c353c6fd08e6764a2e7420c9f19e8227b391c50981db6d0282a6" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + +[[package]] +name = "bevy_ecs" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c071d7c6bc9801253485e05d0c257284150de755391902746837ba21c0cf74" +dependencies = [ + "async-channel", + "bevy_ecs_macros", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "downcast-rs", + "event-listener", + "fixedbitset", + "fxhash", + "serde", + "thread_local", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15bd45438eeb681ad74f2d205bb07a5699f98f9524462a30ec764afab2742ce" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022bb69196deeea691b6997414af85bbd7f2b34a8914c4aa7a7ff4dfa44f7677" +dependencies = [ + "quote", + "syn", + "toml 0.5.11", +] + +[[package]] +name = "bevy_math" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434c77ab766c806ed9062ef8a7285b3b02b47df51f188d4496199c3ac062eaf" +dependencies = [ + "glam", + "serde", +] + +[[package]] +name = "bevy_ptr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec44f7655039546bc5d34d98de877083473f3e9b2b81d560c528d6d74d3eff4" + +[[package]] +name = "bevy_reflect" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6deae303a7f69dc243b2fa35b5e193cc920229f448942080c8eb2dbd9de6d37a" +dependencies = [ + "bevy_math", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "downcast-rs", + "erased-serde", + "glam", + "once_cell", + "parking_lot", + "serde", + "smallvec", + "thiserror", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bf4cb9cd5acb4193f890f36cb63679f1502e2de025e66a63b194b8b133d018" +dependencies = [ + "bevy_macro_utils", + "bit-set", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680b16b53df9c9f24681dd95f4d772d83760bd19adf8bca00f358a3aad997853" +dependencies = [ + "async-channel", + "async-executor", + "async-task", + "concurrent-queue 1.2.4", + "futures-lite", + "once_cell", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_time" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c38a6d3ea929c7f81e6adf5a6c62cf7e8c40f5106c2174d6057e9d8ea624d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "crossbeam-channel", +] + +[[package]] +name = "bevy_utils" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16750aae52cd35bd7b60eb61cee883420b250e11b4a290b8d44b2b2941795739" +dependencies = [ + "ahash 0.7.6", + "getrandom", + "hashbrown", + "instant", + "tracing", + "uuid", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" @@ -440,9 +713,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytemuck" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" [[package]] name = "byteorder" @@ -457,6 +736,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -464,9 +749,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfb8" @@ -515,6 +800,30 @@ dependencies = [ ] [[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -661,9 +970,22 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] [[package]] name = "digest" @@ -676,6 +998,12 @@ dependencies = [ ] [[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -729,6 +1057,15 @@ dependencies = [ ] [[package]] +name = "erased-serde" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" +dependencies = [ + "serde", +] + +[[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -750,6 +1087,12 @@ dependencies = [ ] [[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] name = "fastrand" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -771,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide", ] [[package]] @@ -853,6 +1196,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] name = "futures-macro" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -894,6 +1252,15 @@ dependencies = [ ] [[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] name = "generic-array" version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -910,15 +1277,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" + +[[package]] +name = "glam" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" +dependencies = [ + "bytemuck", + "serde", +] [[package]] name = "h2" @@ -950,6 +1329,10 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", + "serde", +] [[package]] name = "heck" @@ -983,7 +1366,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.4", + "itoa 1.0.5", ] [[package]] @@ -1030,7 +1413,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -1099,6 +1482,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1108,14 +1494,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" @@ -1126,7 +1512,7 @@ dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1146,9 +1532,21 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "iyes_loopless" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c47fd2cbdb1d7f295c25e6bfccfd78a84b6eef3055bc9f01b34ae861721b01ee" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_time", + "bevy_utils", +] [[package]] name = "js-sys" @@ -1167,9 +1565,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linked-hash-map" @@ -1213,9 +1611,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" @@ -1240,15 +1638,6 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" @@ -1265,7 +1654,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1293,6 +1682,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + +[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1329,9 +1727,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] @@ -1391,19 +1789,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] @@ -1422,9 +1820,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", @@ -1454,9 +1852,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -1472,6 +1870,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1483,9 +1887,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "backtrace", "cfg-if", @@ -1494,7 +1898,7 @@ dependencies = [ "redox_syscall", "smallvec", "thread-id", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1577,18 +1981,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1625,20 +2029,19 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1657,9 +2060,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1689,9 +2092,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ "base64", "bytes", @@ -1742,6 +2145,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] name = "rustix" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1752,14 +2164,14 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -1772,12 +2184,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1788,9 +2199,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" dependencies = [ "bitflags", "core-foundation", @@ -1801,19 +2212,25 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] name = "serde" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -1830,9 +2247,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1841,23 +2258,32 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] [[package]] +name = "serde_spanned" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8" +dependencies = [ + "serde", +] + +[[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.4", + "itoa 1.0.5", "ryu", "serde", ] @@ -1917,6 +2343,9 @@ name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -1930,9 +2359,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1955,9 +2384,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -1973,18 +2402,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2038,9 +2467,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.24.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -2053,7 +2482,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2092,6 +2521,49 @@ dependencies = [ ] [[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa" +dependencies = [ + "indexmap", + "nom8", + "serde", + "serde_spanned", + "toml_datetime", +] + +[[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2200,9 +2672,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typemap_rev" @@ -2218,15 +2690,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -2260,6 +2732,7 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ + "getrandom", "serde", ] @@ -2282,6 +2755,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2417,103 +2896,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winreg" @@ -16,6 +16,7 @@ members = [ "azalea-buf", "azalea-physics", "azalea-registry", + "azalea-ecs", ] [profile.release] @@ -24,19 +25,9 @@ debug = true # decoding packets takes forever if we don't do this [profile.dev.package.azalea-crypto] opt-level = 3 -[profile.dev.package.cipher] -opt-level = 3 [profile.dev.package.cfb8] opt-level = 3 [profile.dev.package.aes] opt-level = 3 -[profile.dev.package.crypto-common] -opt-level = 3 -[profile.dev.package.generic-array] -opt-level = 3 -[profile.dev.package.typenum] -opt-level = 3 -[profile.dev.package.inout] -opt-level = 3 [profile.dev.package.flate2] opt-level = 3 diff --git a/azalea-auth/README.md b/azalea-auth/README.md index 568a9f88..a35fee1f 100755 --- a/azalea-auth/README.md +++ b/azalea-auth/README.md @@ -4,7 +4,7 @@ A port of Mojang's Authlib and launcher authentication. # Examples -``` +```no_run use std::path::PathBuf; #[tokio::main] diff --git a/azalea-auth/src/game_profile.rs b/azalea-auth/src/game_profile.rs index 12815821..6a34a87b 100755 --- a/azalea-auth/src/game_profile.rs +++ b/azalea-auth/src/game_profile.rs @@ -5,7 +5,9 @@ use uuid::Uuid; #[derive(McBuf, Debug, Clone, Default, Eq, PartialEq)] pub struct GameProfile { + /// The UUID of the player. pub uuid: Uuid, + /// The username of the player. pub name: String, pub properties: HashMap<String, ProfilePropertyValue>, } diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 8b26122c..bbd98619 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -73,7 +73,7 @@ impl Parse for PropertyWithNameAndDefault { is_enum = true; property_type = first_ident; let variant = input.parse::<Ident>()?; - property_default.extend(quote! { ::#variant }) + property_default.extend(quote! { ::#variant }); } else if first_ident_string == "true" || first_ident_string == "false" { property_type = Ident::new("bool", first_ident.span()); } else { @@ -388,7 +388,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // Ident::new(&property.to_string(), proc_macro2::Span::call_site()); block_struct_fields.extend(quote! { pub #name: #struct_name, - }) + }); } let block_name_pascal_case = Ident::new( @@ -420,8 +420,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { combination .iter() .map(|v| v[0..1].to_uppercase() + &v[1..]) - .collect::<Vec<String>>() - .join("") + .collect::<String>() ), proc_macro2::Span::call_site(), ); @@ -509,20 +508,20 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { .. } in properties_with_name { - block_default_fields.extend(quote! {#name: #property_default,}) + block_default_fields.extend(quote! { #name: #property_default, }); } let block_behavior = &block.behavior; let block_id = block.name.to_string(); - let from_block_to_state_match = if !block.properties_and_defaults.is_empty() { + let from_block_to_state_match = if block.properties_and_defaults.is_empty() { + quote! { BlockState::#block_name_pascal_case } + } else { quote! { match b { #from_block_to_state_match_inner } } - } else { - quote! { BlockState::#block_name_pascal_case } }; let block_struct = quote! { diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index bb9bfb28..273b1681 100755 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -136,7 +136,7 @@ impl<S> CommandDispatcher<S> { return Ordering::Greater; }; Ordering::Equal - }) + }); } let best_potential = potentials.into_iter().next().unwrap(); return Ok(best_potential); @@ -195,7 +195,7 @@ impl<S> CommandDispatcher<S> { let mut node = self.root.clone(); for name in path { if let Some(child) = node.clone().borrow().child(name) { - node = child + node = child; } else { return None; } @@ -228,7 +228,7 @@ impl<S> CommandDispatcher<S> { let mut next: Vec<CommandContext<S>> = vec![]; while !contexts.is_empty() { - for context in contexts.iter() { + for context in &contexts { let child = &context.child; if let Some(child) = child { forked |= child.forks; diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs index b96b37bf..e60c697c 100755 --- a/azalea-brigadier/src/exceptions/builtin_exceptions.rs +++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs @@ -88,7 +88,7 @@ impl fmt::Debug for BuiltInExceptions { BuiltInExceptions::ReaderInvalidBool { value } => { write!( f, - "Invalid bool, expected true or false but found '{value}'", + "Invalid bool, expected true or false but found '{value}'" ) } BuiltInExceptions::ReaderInvalidInt { value } => { diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs index 114a4c47..592c674c 100755 --- a/azalea-brigadier/src/suggestion/mod.rs +++ b/azalea-brigadier/src/suggestion/mod.rs @@ -4,7 +4,7 @@ use crate::context::StringRange; #[cfg(feature = "azalea-buf")] use azalea_buf::McBufWritable; #[cfg(feature = "azalea-buf")] -use azalea_chat::Component; +use azalea_chat::FormattedText; #[cfg(feature = "azalea-buf")] use std::io::Write; pub use suggestions::*; @@ -31,7 +31,7 @@ impl<M: Clone> Suggestion<M> { } result.push_str(&self.text); if self.range.end() < input.len() { - result.push_str(&input[self.range.end()..]) + result.push_str(&input[self.range.end()..]); } result @@ -58,7 +58,7 @@ impl<M: Clone> Suggestion<M> { } #[cfg(feature = "azalea-buf")] -impl McBufWritable for Suggestion<Component> { +impl McBufWritable for Suggestion<FormattedText> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { self.text.write_into(buf)?; self.tooltip.write_into(buf)?; diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index 06ef9661..2a8b5e9e 100755 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -5,7 +5,7 @@ use azalea_buf::{ BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, }; #[cfg(feature = "azalea-buf")] -use azalea_chat::Component; +use azalea_chat::FormattedText; #[cfg(feature = "azalea-buf")] use std::io::{Cursor, Write}; use std::{collections::HashSet, hash::Hash}; @@ -68,12 +68,12 @@ impl<M> Default for Suggestions<M> { } #[cfg(feature = "azalea-buf")] -impl McBufReadable for Suggestions<Component> { +impl McBufReadable for Suggestions<FormattedText> { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { #[derive(McBuf)] struct StandaloneSuggestion { pub text: String, - pub tooltip: Option<Component>, + pub tooltip: Option<FormattedText>, } let start = u32::var_read_from(buf)? as usize; @@ -97,7 +97,7 @@ impl McBufReadable for Suggestions<Component> { } #[cfg(feature = "azalea-buf")] -impl McBufWritable for Suggestions<Component> { +impl McBufWritable for Suggestions<FormattedText> { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { (self.range.start() as u32).var_write_into(buf)?; (self.range.length() as u32).var_write_into(buf)?; diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs index 59b79b5d..bb6af68d 100755 --- a/azalea-brigadier/src/tree/mod.rs +++ b/azalea-brigadier/src/tree/mod.rs @@ -65,7 +65,9 @@ impl<S> CommandNode<S> { pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Rc<RefCell<CommandNode<S>>>> { let literals = &self.literals; - if !literals.is_empty() { + if literals.is_empty() { + self.arguments.values().cloned().collect() + } else { let cursor = input.cursor(); while input.can_read() && input.peek() != ' ' { input.skip(); @@ -83,8 +85,6 @@ impl<S> CommandNode<S> { } else { self.arguments.values().cloned().collect() } - } else { - self.arguments.values().cloned().collect() } } diff --git a/azalea-buf/azalea-buf-macros/src/read.rs b/azalea-buf/azalea-buf-macros/src/read.rs index b72e2bf5..82fd1ce8 100644 --- a/azalea-buf/azalea-buf-macros/src/read.rs +++ b/azalea-buf/azalea-buf-macros/src/read.rs @@ -39,9 +39,8 @@ fn read_named_fields( pub fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { - let FieldsNamed { named, .. } = match fields { - syn::Fields::Named(f) => f, - _ => panic!("#[derive(McBuf)] can only be used on structs with named fields"), + let syn::Fields::Named(FieldsNamed { named, .. }) = fields else { + panic!("#[derive(McBuf)] can only be used on structs with named fields") }; let (read_fields, read_field_names) = read_named_fields(named); @@ -69,7 +68,7 @@ pub fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::Tok variant_discrim = match &d.1 { syn::Expr::Lit(e) => match &e.lit { syn::Lit::Int(i) => i.base10_parse().unwrap(), - _ => panic!("Error parsing enum discriminant as int (is {e:?})",), + _ => panic!("Error parsing enum discriminant as int (is {e:?})"), }, syn::Expr::Unary(_) => { panic!("Negative enum discriminants are not supported") @@ -102,11 +101,11 @@ pub fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::Tok if f.attrs.iter().any(|attr| attr.path.is_ident("var")) { reader_code.extend(quote! { Self::#variant_name(azalea_buf::McBufVarReadable::var_read_from(buf)?), - }) + }); } else { reader_code.extend(quote! { Self::#variant_name(azalea_buf::McBufReadable::read_from(buf)?), - }) + }); } } quote! { Ok(#reader_code) } diff --git a/azalea-buf/azalea-buf-macros/src/write.rs b/azalea-buf/azalea-buf-macros/src/write.rs index 67647c06..b491a550 100644 --- a/azalea-buf/azalea-buf-macros/src/write.rs +++ b/azalea-buf/azalea-buf-macros/src/write.rs @@ -40,9 +40,8 @@ fn write_named_fields( pub fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenStream { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => { - let FieldsNamed { named, .. } = match fields { - syn::Fields::Named(f) => f, - _ => panic!("#[derive(McBuf)] can only be used on structs with named fields"), + let syn::Fields::Named(FieldsNamed { named, .. }) = fields else { + panic!("#[derive(McBuf)] can only be used on structs with named fields") }; let write_fields = @@ -137,11 +136,11 @@ pub fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::Tok if f.attrs.iter().any(|attr| attr.path.is_ident("var")) { writers_code.extend(quote! { azalea_buf::McBufVarWritable::var_write_into(#param_ident, buf)?; - }) + }); } else { writers_code.extend(quote! { azalea_buf::McBufWritable::write_into(#param_ident, buf)?; - }) + }); } } match_arms.extend(quote! { diff --git a/azalea-chat/README.md b/azalea-chat/README.md index 1cf52878..e79d273a 100755 --- a/azalea-chat/README.md +++ b/azalea-chat/README.md @@ -5,9 +5,9 @@ Things for working with Minecraft formatted text components. # Examples ``` -// convert a Minecraft component JSON into colored text that can be printed to the terminal. +// convert a Minecraft formatted text JSON into colored text that can be printed to the terminal. -use azalea_chat::Component; +use azalea_chat::FormattedText; use serde_json::Value; use serde::Deserialize; @@ -15,9 +15,9 @@ let j: Value = serde_json::from_str( r#"{"text": "hello","color": "red","bold": true}"# ) .unwrap(); -let component = Component::deserialize(&j).unwrap(); +let text = FormattedText::deserialize(&j).unwrap(); assert_eq!( - component.to_ansi(), + text.to_ansi(), "\u{1b}[1m\u{1b}[38;2;255;85;85mhello\u{1b}[m" ); ``` diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs index ab4f5e5d..43b35aef 100755 --- a/azalea-chat/src/base_component.rs +++ b/azalea-chat/src/base_component.rs @@ -1,11 +1,11 @@ -use crate::{style::Style, Component}; +use crate::{style::Style, FormattedText}; use serde::Serialize; #[derive(Clone, Debug, PartialEq, Serialize)] pub struct BaseComponent { // implements mutablecomponent #[serde(skip_serializing_if = "Vec::is_empty")] - pub siblings: Vec<Component>, + pub siblings: Vec<FormattedText>, #[serde(flatten)] pub style: Style, } diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 14ec0d0c..59e11bbf 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -17,7 +17,7 @@ use std::{ /// A chat component, basically anything you can see in chat. #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] -pub enum Component { +pub enum FormattedText { Text(TextComponent), Translatable(TranslatableComponent), } @@ -28,7 +28,7 @@ pub static DEFAULT_STYLE: Lazy<Style> = Lazy::new(|| Style { }); /// A chat component -impl Component { +impl FormattedText { pub fn get_base_mut(&mut self) -> &mut BaseComponent { match self { Self::Text(c) => &mut c.base, @@ -44,14 +44,16 @@ impl Component { } /// Add a component as a sibling of this one - fn append(&mut self, sibling: Component) { + fn append(&mut self, sibling: FormattedText) { self.get_base_mut().siblings.push(sibling); } /// Get the "separator" component from the json - fn parse_separator(json: &serde_json::Value) -> Result<Option<Component>, serde_json::Error> { + fn parse_separator( + json: &serde_json::Value, + ) -> Result<Option<FormattedText>, serde_json::Error> { if json.get("separator").is_some() { - return Ok(Some(Component::deserialize( + return Ok(Some(FormattedText::deserialize( json.get("separator").unwrap(), )?)); } @@ -62,16 +64,17 @@ impl Component { /// [ANSI string](https://en.wikipedia.org/wiki/ANSI_escape_code), so you /// can print it to your terminal and get styling. /// - /// This is technically a shortcut for [`Component::to_ansi_custom_style`] - /// with a default [`Style`] colored white. + /// This is technically a shortcut for + /// [`FormattedText::to_ansi_custom_style`] with a default [`Style`] + /// colored white. /// /// # Examples /// /// ```rust - /// use azalea_chat::Component; + /// use azalea_chat::FormattedText; /// use serde::de::Deserialize; /// - /// let component = Component::deserialize(&serde_json::json!({ + /// let component = FormattedText::deserialize(&serde_json::json!({ /// "text": "Hello, world!", /// "color": "red", /// })).unwrap(); @@ -86,7 +89,7 @@ impl Component { /// Convert this component into an /// [ANSI string](https://en.wikipedia.org/wiki/ANSI_escape_code). /// - /// This is the same as [`Component::to_ansi`], but you can specify a + /// This is the same as [`FormattedText::to_ansi`], but you can specify a /// default [`Style`] to use. pub fn to_ansi_custom_style(&self, default_style: &Style) -> String { // this contains the final string will all the ansi escape codes @@ -117,12 +120,12 @@ impl Component { } } -impl IntoIterator for Component { +impl IntoIterator for FormattedText { /// Recursively call the function for every component in this component fn into_iter(self) -> Self::IntoIter { let base = self.get_base(); let siblings = base.siblings.clone(); - let mut v: Vec<Component> = Vec::with_capacity(siblings.len() + 1); + let mut v: Vec<FormattedText> = Vec::with_capacity(siblings.len() + 1); v.push(self); for sibling in siblings { v.extend(sibling.into_iter()); @@ -131,11 +134,11 @@ impl IntoIterator for Component { v.into_iter() } - type Item = Component; + type Item = FormattedText; type IntoIter = std::vec::IntoIter<Self::Item>; } -impl<'de> Deserialize<'de> for Component { +impl<'de> Deserialize<'de> for FormattedText { fn deserialize<D>(de: D) -> Result<Self, D::Error> where D: Deserializer<'de>, @@ -143,11 +146,11 @@ impl<'de> Deserialize<'de> for Component { let json: serde_json::Value = serde::Deserialize::deserialize(de)?; // we create a component that we might add siblings to - let mut component: Component; + let mut component: FormattedText; // if it's primitive, make it a text component if !json.is_array() && !json.is_object() { - return Ok(Component::Text(TextComponent::new( + return Ok(FormattedText::Text(TextComponent::new( json.as_str().unwrap_or("").to_string(), ))); } @@ -155,7 +158,7 @@ impl<'de> Deserialize<'de> for Component { else if json.is_object() { if let Some(text) = json.get("text") { let text = text.as_str().unwrap_or("").to_string(); - component = Component::Text(TextComponent::new(text)); + component = FormattedText::Text(TextComponent::new(text)); } else if let Some(translate) = json.get("translate") { let translate = translate .as_str() @@ -170,8 +173,8 @@ impl<'de> Deserialize<'de> for Component { // 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 = Component::deserialize(item).map_err(de::Error::custom)?; - if let Component::Text(text_component) = c { + let c = FormattedText::deserialize(item).map_err(de::Error::custom)?; + if let FormattedText::Text(text_component) = c { if text_component.base.siblings.is_empty() && text_component.base.style.is_empty() { @@ -179,16 +182,19 @@ impl<'de> Deserialize<'de> for Component { continue; } } - with_array.push(StringOrComponent::Component( - Component::deserialize(item).map_err(de::Error::custom)?, + with_array.push(StringOrComponent::FormattedText( + FormattedText::deserialize(item).map_err(de::Error::custom)?, )); } - component = - Component::Translatable(TranslatableComponent::new(translate, with_array)); + component = FormattedText::Translatable(TranslatableComponent::new( + translate, with_array, + )); } else { // if it doesn't have a "with", just have the with_array be empty - component = - Component::Translatable(TranslatableComponent::new(translate, Vec::new())); + component = FormattedText::Translatable(TranslatableComponent::new( + translate, + Vec::new(), + )); } } else if let Some(score) = json.get("score") { // object = GsonHelper.getAsJsonObject(jsonObject, "score"); @@ -210,14 +216,13 @@ impl<'de> Deserialize<'de> for Component { "keybind text components aren't yet supported", )); } else { - let _nbt = if let Some(nbt) = json.get("nbt") { - nbt - } else { + let Some(_nbt) = json.get("nbt") else { return Err(de::Error::custom( - format!("Don't know how to turn {json} into a Component").as_str(), + format!("Don't know how to turn {json} into a FormattedText").as_str(), )); }; - let _separator = Component::parse_separator(&json).map_err(de::Error::custom)?; + let _separator = + FormattedText::parse_separator(&json).map_err(de::Error::custom)?; let _interpret = match json.get("interpret") { Some(v) => v.as_bool().ok_or(Some(false)).unwrap(), @@ -229,16 +234,15 @@ impl<'de> Deserialize<'de> for Component { )); } if let Some(extra) = json.get("extra") { - let extra = match extra.as_array() { - Some(r) => r, - None => return Err(de::Error::custom("Extra isn't an array")), + let Some(extra) = extra.as_array() else { + return Err(de::Error::custom("Extra isn't an array")); }; if extra.is_empty() { return Err(de::Error::custom("Unexpected empty array of components")); } for extra_component in extra { let sibling = - Component::deserialize(extra_component).map_err(de::Error::custom)?; + FormattedText::deserialize(extra_component).map_err(de::Error::custom)?; component.append(sibling); } } @@ -251,16 +255,18 @@ impl<'de> Deserialize<'de> for Component { // ok so it's not an object, if it's an array deserialize every item else if !json.is_array() { return Err(de::Error::custom( - format!("Don't know how to turn {json} into a Component").as_str(), + format!("Don't know how to turn {json} into a FormattedText").as_str(), )); } let json_array = json.as_array().unwrap(); // the first item in the array is the one that we're gonna return, the others // are siblings - let mut component = Component::deserialize(&json_array[0]).map_err(de::Error::custom)?; + let mut component = + FormattedText::deserialize(&json_array[0]).map_err(de::Error::custom)?; for i in 1..json_array.len() { component.append( - Component::deserialize(json_array.get(i).unwrap()).map_err(de::Error::custom)?, + FormattedText::deserialize(json_array.get(i).unwrap()) + .map_err(de::Error::custom)?, ); } Ok(component) @@ -268,18 +274,18 @@ impl<'de> Deserialize<'de> for Component { } #[cfg(feature = "azalea-buf")] -impl McBufReadable for Component { +impl McBufReadable for FormattedText { fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { let string = String::read_from(buf)?; - debug!("Component string: {}", string); + debug!("FormattedText string: {}", string); let json: serde_json::Value = serde_json::from_str(string.as_str())?; - let component = Component::deserialize(json)?; + let component = FormattedText::deserialize(json)?; Ok(component) } } #[cfg(feature = "azalea-buf")] -impl McBufWritable for Component { +impl McBufWritable for FormattedText { fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { let json = serde_json::to_string(self).unwrap(); json.write_into(buf)?; @@ -287,31 +293,31 @@ impl McBufWritable for Component { } } -impl From<String> for Component { +impl From<String> for FormattedText { fn from(s: String) -> Self { - Component::Text(TextComponent { + FormattedText::Text(TextComponent { text: s, base: BaseComponent::default(), }) } } -impl From<&str> for Component { +impl From<&str> for FormattedText { fn from(s: &str) -> Self { Self::from(s.to_string()) } } -impl Display for Component { +impl Display for FormattedText { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Component::Text(c) => c.fmt(f), - Component::Translatable(c) => c.fmt(f), + FormattedText::Text(c) => c.fmt(f), + FormattedText::Translatable(c) => c.fmt(f), } } } -impl Default for Component { +impl Default for FormattedText { fn default() -> Self { - Component::Text(TextComponent::default()) + FormattedText::Text(TextComponent::default()) } } diff --git a/azalea-chat/src/lib.rs b/azalea-chat/src/lib.rs index 97a2580d..d6ff7285 100755 --- a/azalea-chat/src/lib.rs +++ b/azalea-chat/src/lib.rs @@ -6,4 +6,4 @@ pub mod style; pub mod text_component; pub mod translatable_component; -pub use component::Component; +pub use component::FormattedText; diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index 44bcbcf1..42932d0e 100755 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -1,4 +1,4 @@ -use crate::{base_component::BaseComponent, style::ChatFormatting, Component}; +use crate::{base_component::BaseComponent, style::ChatFormatting, FormattedText}; use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer}; use std::fmt::Display; @@ -26,7 +26,7 @@ impl Serialize for TextComponent { const LEGACY_FORMATTING_CODE_SYMBOL: char = '§'; -/// Convert a legacy color code string into a Component +/// Convert a legacy color code string into a FormattedText /// Technically in Minecraft this is done when displaying the text, but AFAIK /// it's the same as just doing it in TextComponent pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextComponent { @@ -41,12 +41,9 @@ pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextCompo while i < legacy_color_code.chars().count() { if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL { let formatting_code = legacy_color_code.chars().nth(i + 1); - let formatting_code = match formatting_code { - Some(formatting_code) => formatting_code, - None => { - i += 1; - continue; - } + let Some(formatting_code) = formatting_code else { + i += 1; + continue; }; if let Some(formatter) = ChatFormatting::from_code(formatting_code) { if components.is_empty() || !components.last().unwrap().text.is_empty() { @@ -98,18 +95,18 @@ impl TextComponent { } } - fn get(self) -> Component { - Component::Text(self) + fn get(self) -> FormattedText { + FormattedText::Text(self) } } impl Display for TextComponent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // this contains the final string will all the ansi escape codes - for component in Component::Text(self.clone()).into_iter() { + for component in FormattedText::Text(self.clone()).into_iter() { let component_text = match &component { - Component::Text(c) => c.text.to_string(), - Component::Translatable(c) => c.read()?.to_string(), + FormattedText::Text(c) => c.text.to_string(), + FormattedText::Translatable(c) => c.read()?.to_string(), }; f.write_str(&component_text)?; diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs index 7819d5ff..edbb5a6d 100755 --- a/azalea-chat/src/translatable_component.rs +++ b/azalea-chat/src/translatable_component.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Formatter}; use crate::{ - base_component::BaseComponent, style::Style, text_component::TextComponent, Component, + base_component::BaseComponent, style::Style, text_component::TextComponent, FormattedText, }; use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer}; @@ -9,7 +9,7 @@ use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSer #[serde(untagged)] pub enum StringOrComponent { String(String), - Component(Component), + FormattedText(FormattedText), } /// A message whose content depends on the client's language. @@ -42,7 +42,7 @@ impl TranslatableComponent { } } - /// Convert the key and args to a Component. + /// Convert the key and args to a FormattedText. pub fn read(&self) -> Result<TextComponent, fmt::Error> { let template = azalea_language::get(&self.key).unwrap_or(&self.key); // decode the % things @@ -57,12 +57,9 @@ impl TranslatableComponent { while i < template.len() { if template.chars().nth(i).unwrap() == '%' { - let char_after = match template.chars().nth(i + 1) { - Some(c) => c, - None => { - built_text.push(template.chars().nth(i).unwrap()); - break; - } + let Some(char_after) = template.chars().nth(i + 1) else { + built_text.push(template.chars().nth(i).unwrap()); + break; }; i += 1; match char_after { @@ -111,7 +108,7 @@ impl TranslatableComponent { built_text.push(template.chars().nth(i).unwrap()); } - i += 1 + i += 1; } if components.is_empty() { @@ -122,7 +119,7 @@ impl TranslatableComponent { Ok(TextComponent { base: BaseComponent { - siblings: components.into_iter().map(Component::Text).collect(), + siblings: components.into_iter().map(FormattedText::Text).collect(), style: Style::default(), }, text: "".to_string(), @@ -133,10 +130,10 @@ impl TranslatableComponent { impl Display for TranslatableComponent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // this contains the final string will all the ansi escape codes - for component in Component::Translatable(self.clone()).into_iter() { + for component in FormattedText::Translatable(self.clone()).into_iter() { let component_text = match &component { - Component::Text(c) => c.text.to_string(), - Component::Translatable(c) => c.read()?.to_string(), + FormattedText::Text(c) => c.text.to_string(), + FormattedText::Translatable(c) => c.read()?.to_string(), }; f.write_str(&component_text)?; @@ -150,7 +147,7 @@ impl Display for StringOrComponent { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { match self { StringOrComponent::String(s) => write!(f, "{s}"), - StringOrComponent::Component(c) => write!(f, "{c}"), + StringOrComponent::FormattedText(c) => write!(f, "{c}"), } } } @@ -159,7 +156,7 @@ impl From<StringOrComponent> for TextComponent { fn from(soc: StringOrComponent) -> Self { match soc { StringOrComponent::String(s) => TextComponent::new(s), - StringOrComponent::Component(c) => TextComponent::new(c.to_string()), + StringOrComponent::FormattedText(c) => TextComponent::new(c.to_string()), } } } diff --git a/azalea-chat/tests/integration_test.rs b/azalea-chat/tests/integration_test.rs index 35fab8d3..122df538 100755 --- a/azalea-chat/tests/integration_test.rs +++ b/azalea-chat/tests/integration_test.rs @@ -1,6 +1,6 @@ use azalea_chat::{ style::{Ansi, ChatFormatting, TextColor}, - Component, + FormattedText, }; use serde::Deserialize; use serde_json::Value; @@ -15,7 +15,7 @@ fn basic_ansi_test() { }"#, ) .unwrap(); - let component = Component::deserialize(&j).unwrap(); + let component = FormattedText::deserialize(&j).unwrap(); assert_eq!( component.to_ansi(), "\u{1b}[1m\u{1b}[38;2;255;85;85mhello\u{1b}[m" @@ -51,7 +51,7 @@ fn complex_ansi_test() { ]"##, ) .unwrap(); - let component = Component::deserialize(&j).unwrap(); + let component = FormattedText::deserialize(&j).unwrap(); assert_eq!( component.to_ansi(), format!( @@ -70,6 +70,6 @@ fn complex_ansi_test() { #[test] fn component_from_string() { let j: Value = serde_json::from_str("\"foo\"").unwrap(); - let component = Component::deserialize(&j).unwrap(); + let component = FormattedText::deserialize(&j).unwrap(); assert_eq!(component.to_ansi(), "foo"); } diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 01ffc601..8a807212 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -11,14 +11,21 @@ version = "0.5.0" [dependencies] anyhow = "1.0.59" async-trait = "0.1.58" -azalea-auth = {path = "../azalea-auth", version = "0.5.0" } -azalea-block = {path = "../azalea-block", version = "0.5.0" } -azalea-chat = {path = "../azalea-chat", version = "0.5.0" } -azalea-core = {path = "../azalea-core", version = "0.5.0" } -azalea-crypto = {path = "../azalea-crypto", version = "0.5.0" } -azalea-physics = {path = "../azalea-physics", version = "0.5.0" } -azalea-protocol = {path = "../azalea-protocol", version = "0.5.0" } -azalea-world = {path = "../azalea-world", version = "0.5.0" } +azalea-auth = {path = "../azalea-auth", version = "0.5.0"} +azalea-block = {path = "../azalea-block", version = "0.5.0"} +azalea-chat = {path = "../azalea-chat", version = "0.5.0"} +azalea-core = {path = "../azalea-core", version = "0.5.0"} +azalea-crypto = {path = "../azalea-crypto", version = "0.5.0"} +azalea-ecs = {path = "../azalea-ecs", version = "0.5.0"} +azalea-physics = {path = "../azalea-physics", version = "0.5.0"} +azalea-protocol = {path = "../azalea-protocol", version = "0.5.0"} +azalea-registry = {path = "../azalea-registry", version = "0.5.0"} +azalea-world = {path = "../azalea-world", version = "0.5.0"} +bevy_tasks = "0.9.1" +bevy_time = "0.9.1" +derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} +futures = "0.3.25" +iyes_loopless = "0.9.1" log = "0.4.17" nohash-hasher = "0.2.0" once_cell = "1.16.0" @@ -28,3 +35,6 @@ thiserror = "^1.0.34" tokio = {version = "^1.24.2", features = ["sync"]} typemap_rev = "0.3.0" uuid = "^1.1.2" + +[dev-dependencies] +env_logger = "0.9.1" diff --git a/azalea-client/examples/echo.rs b/azalea-client/examples/echo.rs new file mode 100644 index 00000000..f37cd904 --- /dev/null +++ b/azalea-client/examples/echo.rs @@ -0,0 +1,49 @@ +//! A simple bot that repeats chat messages sent by other players. + +use azalea_client::{Account, Client, Event}; + +#[tokio::main] +async fn main() { + env_logger::init(); + // deadlock detection, you can safely delete this block if you're not trying to + // debug deadlocks in azalea + { + use parking_lot::deadlock; + use std::thread; + use std::time::Duration; + 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("bot"); + // or let account = Account::microsoft("email").await; + + let (client, mut rx) = Client::join(&account, "localhost").await.unwrap(); + + while let Some(event) = rx.recv().await { + match &event { + Event::Chat(m) => { + if let (Some(sender), content) = m.split_sender_and_content() { + if sender == client.profile.name { + continue; // ignore our own messages + } + client.chat(&content); + }; + } + _ => {} + } + } +} diff --git a/azalea-client/src/account.rs b/azalea-client/src/account.rs index 79feb1a7..3c2c7d1b 100755 --- a/azalea-client/src/account.rs +++ b/azalea-client/src/account.rs @@ -28,7 +28,7 @@ pub struct Account { /// The access token for authentication. You can obtain one of these /// manually from azalea-auth. /// - /// This is an Arc<Mutex> so it can be modified by [`Self::refresh`]. + /// This is an `Arc<Mutex>` so it can be modified by [`Self::refresh`]. pub access_token: Option<Arc<Mutex<String>>>, /// Only required for online-mode accounts. pub uuid: Option<Uuid>, diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index 91dcf63e..3fa0ceec 100755 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -1,7 +1,6 @@ //! Implementations of chat-related features. -use crate::Client; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol::packets::game::{ clientbound_player_chat_packet::ClientboundPlayerChatPacket, clientbound_system_chat_packet::ClientboundSystemChatPacket, @@ -14,6 +13,8 @@ use std::{ }; use uuid::Uuid; +use crate::client::Client; + /// A chat packet, either a system message or a chat message. #[derive(Debug, Clone, PartialEq)] pub enum ChatPacket { @@ -30,7 +31,7 @@ macro_rules! regex { impl ChatPacket { /// Get the message shown in chat for this packet. - pub fn message(&self) -> Component { + pub fn message(&self) -> FormattedText { match self { ChatPacket::System(p) => p.content.clone(), ChatPacket::Player(p) => p.message(), @@ -94,7 +95,7 @@ impl ChatPacket { /// convenience function for testing. pub fn new(message: &str) -> Self { ChatPacket::System(Arc::new(ClientboundSystemChatPacket { - content: Component::from(message), + content: FormattedText::from(message), overlay: false, })) } @@ -105,7 +106,7 @@ impl Client { /// not the command packet. The [`Client::chat`] function handles checking /// whether the message is a command and using the proper packet for you, /// so you should use that instead. - pub async fn send_chat_packet(&self, message: &str) -> Result<(), std::io::Error> { + pub fn send_chat_packet(&self, message: &str) { // TODO: chat signing // let signature = sign_message(); let packet = ServerboundChatPacket { @@ -121,12 +122,12 @@ impl Client { last_seen_messages: LastSeenMessagesUpdate::default(), } .get(); - self.write_packet(packet).await + self.write_packet(packet); } /// Send a command packet to the server. The `command` argument should not /// include the slash at the front. - pub async fn send_command_packet(&self, command: &str) -> Result<(), std::io::Error> { + pub fn send_command_packet(&self, command: &str) { // TODO: chat signing let packet = ServerboundChatCommandPacket { command: command.to_string(), @@ -141,7 +142,7 @@ impl Client { last_seen_messages: LastSeenMessagesUpdate::default(), } .get(); - self.write_packet(packet).await + self.write_packet(packet); } /// Send a message in chat. @@ -149,15 +150,15 @@ impl Client { /// ```rust,no_run /// # use azalea_client::{Client, Event}; /// # async fn handle(bot: Client, event: Event) -> anyhow::Result<()> { - /// bot.chat("Hello, world!").await.unwrap(); + /// bot.chat("Hello, world!"); /// # Ok(()) /// # } /// ``` - pub async fn chat(&self, message: &str) -> Result<(), std::io::Error> { + pub fn chat(&self, message: &str) { if let Some(command) = message.strip_prefix('/') { - self.send_command_packet(command).await + self.send_command_packet(command); } else { - self.send_chat_packet(message).await + self.send_chat_packet(message); } } } diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 125facda..bc1d8d62 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,18 +1,32 @@ pub use crate::chat::ChatPacket; -use crate::{movement::WalkDirection, plugins::PluginStates, Account, PlayerInfo}; +use crate::{ + events::{Event, EventPlugin, LocalPlayerEvents}, + local_player::{ + death_event, update_in_loaded_chunk, GameProfileComponent, LocalPlayer, PhysicsState, + }, + movement::{local_player_ai_step, send_position, sprint_listener, walk_listener}, + packet_handling::{self, PacketHandlerPlugin}, + player::retroactively_add_game_profile_component, + task_pool::TaskPoolPlugin, + Account, PlayerInfo, StartSprintEvent, StartWalkEvent, +}; + use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError}; -use azalea_chat::Component; -use azalea_core::{ChunkPos, ResourceLocation, Vec3}; +use azalea_chat::FormattedText; +use azalea_ecs::{ + app::{App, Plugin, PluginGroup, PluginGroupBuilder}, + component::Component, + entity::Entity, + schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet}, + AppTickExt, +}; +use azalea_ecs::{ecs::Ecs, TickPlugin}; +use azalea_physics::PhysicsPlugin; use azalea_protocol::{ - connect::{Connection, ConnectionError, ReadConnection, WriteConnection}, + connect::{Connection, ConnectionError}, packets::{ game::{ - clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, - serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket, serverbound_client_information_packet::ServerboundClientInformationPacket, - serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, - serverbound_keep_alive_packet::ServerboundKeepAlivePacket, - serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, ClientboundGamePacket, ServerboundGamePacket, }, handshake::{ @@ -26,109 +40,40 @@ use azalea_protocol::{ }, ConnectionProtocol, PROTOCOL_VERSION, }, - read::ReadPacketError, resolver, ServerAddress, }; -use azalea_world::{ - entity::{metadata, Entity, EntityData, EntityMetadata}, - PartialWorld, WeakWorld, WeakWorldContainer, -}; -use log::{debug, error, info, trace, warn}; +use azalea_world::{EntityPlugin, Local, PartialWorld, World, WorldContainer}; +use log::{debug, error}; use parking_lot::{Mutex, RwLock}; -use std::{ - any, - backtrace::Backtrace, - collections::HashMap, - fmt::Debug, - io::{self, Cursor}, - sync::Arc, -}; +use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc}; use thiserror::Error; -use tokio::{ - sync::mpsc::{self, Receiver, Sender}, - task::JoinHandle, - time::{self}, -}; +use tokio::{sync::mpsc, time}; use uuid::Uuid; pub type ClientInformation = ServerboundClientInformationPacket; -/// Something that happened in-game, such as a tick passing or chat message -/// being sent. -/// -/// Note: Events are sent before they're processed, so for example game ticks -/// happen at the beginning of a tick before anything has happened. -#[derive(Debug, Clone)] -pub enum Event { - /// Happens right after the bot switches into the Game state, but before - /// it's actually spawned. This can be useful for setting the client - /// information with `Client::set_client_information`, so the packet - /// doesn't have to be sent twice. - Init, - /// The client is now in the world. Fired when we receive a login packet. - Login, - /// A chat message was sent in the game chat. - Chat(ChatPacket), - /// Happens 20 times per second, but only when the world is loaded. - Tick, - Packet(Arc<ClientboundGamePacket>), - /// A player joined the game (or more specifically, was added to the tab - /// list). - AddPlayer(PlayerInfo), - /// A player left the game (or maybe is still in the game and was just - /// removed from the tab list). - RemovePlayer(PlayerInfo), - /// A player was updated in the tab list (gamemode, display - /// name, or latency changed). - UpdatePlayer(PlayerInfo), - /// The client player died in-game. - Death(Option<Arc<ClientboundPlayerCombatKillPacket>>), -} - -/// A player that you control that is currently in a Minecraft server. +/// Client has the things that a user interacting with the library will want. +/// Things that a player in the world will want to know are in [`LocalPlayer`]. #[derive(Clone)] pub struct Client { + /// The [`GameProfile`] for our client. This contains your username, UUID, + /// and skin data. + /// + /// This is immutable; the server cannot change it. To get the username and + /// skin the server chose for you, get your player from + /// [`Self::players`]. pub profile: GameProfile, - pub read_conn: Arc<tokio::sync::Mutex<ReadConnection<ClientboundGamePacket>>>, - pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>, - pub entity_id: Arc<RwLock<u32>>, - /// The world that this client has access to. This supports shared worlds. + /// The entity for this client in the ECS. + pub entity: Entity, + /// The world that this client is in. pub world: Arc<RwLock<PartialWorld>>, - /// A container of world names to worlds. If we're not using a shared world - /// (i.e. not a swarm), then this will only contain data about the world - /// we're currently in. - world_container: Arc<RwLock<WeakWorldContainer>>, - pub world_name: Arc<RwLock<Option<ResourceLocation>>>, - pub physics_state: Arc<Mutex<PhysicsState>>, - pub client_information: Arc<RwLock<ClientInformation>>, - pub dead: Arc<Mutex<bool>>, - /// Plugins are a way for other crates to add custom functionality to the - /// client and keep state. If you're not making a plugin and you're using - /// the `azalea` crate. you can ignore this field. - pub plugins: Arc<PluginStates>, - /// A map of player uuids to their information in the tab list - pub players: Arc<RwLock<HashMap<Uuid, PlayerInfo>>>, - tasks: Arc<Mutex<Vec<JoinHandle<()>>>>, -} - -#[derive(Default)] -pub struct PhysicsState { - /// Minecraft only sends a movement packet either after 20 ticks or if the - /// player moved enough. This is that tick counter. - pub position_remainder: u32, - pub was_sprinting: bool, - // Whether we're going to try to start sprinting this tick. Equivalent to - // holding down ctrl for a tick. - pub trying_to_sprint: bool, - pub move_direction: WalkDirection, - pub forward_impulse: f32, - pub left_impulse: f32, + /// The entity component system. You probably don't need to access this + /// directly. Note that if you're using a shared world (i.e. a swarm), this + /// will contain all entities in all worlds. + pub ecs: Arc<Mutex<Ecs>>, } -/// Whether we should ignore errors when decoding packets. -const IGNORE_ERRORS: bool = !cfg!(debug_assertions); - /// An error that happened while joining the server. #[derive(Error, Debug)] pub enum JoinError { @@ -147,54 +92,21 @@ pub enum JoinError { #[error("Couldn't refresh access token: {0}")] Auth(#[from] azalea_auth::AuthError), #[error("Disconnected: {reason}")] - Disconnect { reason: Component }, -} - -#[derive(Error, Debug)] -pub enum HandleError { - #[error("{0}")] - Poison(String), - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), - #[error("{0}")] - Send(#[from] mpsc::error::SendError<Event>), + Disconnect { reason: FormattedText }, } impl Client { /// Create a new client from the given GameProfile, Connection, and World. /// You should only use this if you want to change these fields from the /// defaults, otherwise use [`Client::join`]. - pub fn new( - profile: GameProfile, - conn: Connection<ClientboundGamePacket, ServerboundGamePacket>, - world_container: Option<Arc<RwLock<WeakWorldContainer>>>, - ) -> Self { - let (read_conn, write_conn) = conn.into_split(); - let (read_conn, write_conn) = ( - Arc::new(tokio::sync::Mutex::new(read_conn)), - Arc::new(tokio::sync::Mutex::new(write_conn)), - ); - + pub fn new(profile: GameProfile, entity: Entity, ecs: Arc<Mutex<Ecs>>) -> Self { Self { profile, - read_conn, - write_conn, // default our id to 0, it'll be set later - entity_id: Arc::new(RwLock::new(0)), + entity, world: Arc::new(RwLock::new(PartialWorld::default())), - world_container: world_container - .unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))), - world_name: Arc::new(RwLock::new(None)), - physics_state: Arc::new(Mutex::new(PhysicsState::default())), - client_information: Arc::new(RwLock::new(ClientInformation::default())), - dead: Arc::new(Mutex::new(false)), - // The plugins can be modified by the user by replacing the plugins - // field right after this. No Mutex so the user doesn't need to .lock(). - plugins: Arc::new(PluginStates::default()), - players: Arc::new(RwLock::new(HashMap::new())), - tasks: Arc::new(Mutex::new(Vec::new())), + + ecs, } } @@ -213,34 +125,90 @@ impl Client { /// async fn main() -> Result<(), Box<dyn std::error::Error>> { /// let account = Account::offline("bot"); /// let (client, rx) = Client::join(&account, "localhost").await?; - /// client.chat("Hello, world!").await?; - /// client.disconnect().await?; + /// client.chat("Hello, world!"); + /// client.disconnect(); /// Ok(()) /// } /// ``` pub async fn join( account: &Account, address: impl TryInto<ServerAddress>, - ) -> Result<(Self, Receiver<Event>), JoinError> { + ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> { let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?; let resolved_address = resolver::resolve_address(&address).await?; - let conn = Connection::new(&resolved_address).await?; - let (conn, game_profile) = Self::handshake(conn, account, &address).await?; + // An event that causes the schedule to run. This is only used internally. + let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1); + let app = init_ecs_app(); + let ecs_lock = start_ecs(app, run_schedule_receiver, run_schedule_sender.clone()); + + Self::start_client( + ecs_lock, + account, + &address, + &resolved_address, + run_schedule_sender, + ) + .await + } + + /// Create a [`Client`] when you already have the ECS made with + /// [`start_ecs`]. You'd usually want to use [`Self::join`] instead. + pub async fn start_client( + ecs_lock: Arc<Mutex<Ecs>>, + account: &Account, + address: &ServerAddress, + resolved_address: &SocketAddr, + run_schedule_sender: mpsc::Sender<()>, + ) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> { + let conn = Connection::new(resolved_address).await?; + let (conn, game_profile) = Self::handshake(conn, account, address).await?; + let (read_conn, write_conn) = conn.into_split(); - // The buffer has to be 1 to avoid a bug where if it lags events are - // received a bit later instead of the instant they were fired. - // That bug especially causes issues with the pathfinder. - let (tx, rx) = mpsc::channel(1); + let (tx, rx) = mpsc::unbounded_channel(); + + let mut ecs = ecs_lock.lock(); + + // Make the ecs entity for this client + let entity_mut = ecs.spawn_empty(); + let entity = entity_mut.id(); // we got the GameConnection, so the server is now connected :) - let client = Client::new(game_profile, conn, None); + let client = Client::new(game_profile.clone(), entity, ecs_lock.clone()); - tx.send(Event::Init).await.expect("Failed to send event"); + let (packet_writer_sender, packet_writer_receiver) = mpsc::unbounded_channel(); - // just start up the game loop and we're ready! + let mut local_player = crate::local_player::LocalPlayer::new( + entity, + packet_writer_sender, + // default to an empty world, it'll be set correctly later when we + // get the login packet + Arc::new(RwLock::new(World::default())), + ); - client.start_tasks(tx); + // start receiving packets + let packet_receiver = packet_handling::PacketReceiver { + packets: Arc::new(Mutex::new(Vec::new())), + run_schedule_sender: run_schedule_sender.clone(), + }; + + let read_packets_task = tokio::spawn(packet_receiver.clone().read_task(read_conn)); + let write_packets_task = tokio::spawn( + packet_receiver + .clone() + .write_task(write_conn, packet_writer_receiver), + ); + local_player.tasks.push(read_packets_task); + local_player.tasks.push(write_packets_task); + + ecs.entity_mut(entity).insert(( + local_player, + packet_receiver, + GameProfileComponent(game_profile), + PhysicsState::default(), + Local, + LocalPlayerEvents(tx), + )); Ok((client, rx)) } @@ -369,712 +337,61 @@ impl Client { } /// Write a packet directly to the server. - pub async fn write_packet(&self, packet: ServerboundGamePacket) -> Result<(), std::io::Error> { - self.write_conn.lock().await.write(packet).await?; - Ok(()) + pub fn write_packet(&self, packet: ServerboundGamePacket) { + self.local_player_mut(&mut self.ecs.lock()) + .write_packet(packet); } - /// Disconnect this client from the server, ending all tasks. - pub async fn disconnect(&self) -> Result<(), std::io::Error> { - if let Err(e) = self.write_conn.lock().await.shutdown().await { - warn!( - "Error shutting down connection, but it might be fine: {}", - e - ); - } - let tasks = self.tasks.lock(); - for task in tasks.iter() { - task.abort(); - } - Ok(()) + /// Disconnect this client from the server by ending all tasks. + /// + /// The OwnedReadHalf for the TCP connection is in one of the tasks, so it + /// automatically closes the connection when that's dropped. + pub fn disconnect(&self) { + self.local_player_mut(&mut self.ecs.lock()).disconnect(); } - /// Start the protocol and game tick loop. - #[doc(hidden)] - pub fn start_tasks(&self, tx: Sender<Event>) { - // if you get an error right here that means you're doing something with locks - // wrong read the error to see where the issue is - // you might be able to just drop the lock or put it in its own scope to fix - - let mut tasks = self.tasks.lock(); - tasks.push(tokio::spawn(Client::protocol_loop( - self.clone(), - tx.clone(), - ))); - tasks.push(tokio::spawn(Client::game_tick_loop(self.clone(), tx))); + pub fn local_player<'a>(&'a self, ecs: &'a mut Ecs) -> &'a LocalPlayer { + self.query::<&LocalPlayer>(ecs) } - - async fn protocol_loop(client: Client, tx: Sender<Event>) { - loop { - let r = client.read_conn.lock().await.read().await; - match r { - Ok(packet) => match Self::handle(&packet, &client, &tx).await { - Ok(_) => {} - Err(e) => { - error!("Error handling packet: {}", e); - if !IGNORE_ERRORS { - panic!("Error handling packet: {e}"); - } - } - }, - Err(e) => { - let e = *e; - if let ReadPacketError::ConnectionClosed = e { - info!("Connection closed"); - if let Err(e) = client.disconnect().await { - error!("Error shutting down connection: {:?}", e); - } - break; - } - let default_backtrace = Backtrace::capture(); - if IGNORE_ERRORS { - let backtrace = - any::request_ref::<Backtrace>(&e).unwrap_or(&default_backtrace); - warn!("{e}\n{backtrace}"); - match e { - ReadPacketError::FrameSplitter { .. } => panic!("Error: {e:?}"), - _ => continue, - } - } else { - let backtrace = - any::request_ref::<Backtrace>(&e).unwrap_or(&default_backtrace); - panic!("{e}\n{backtrace}") - } - } - }; - } + pub fn local_player_mut<'a>( + &'a self, + ecs: &'a mut Ecs, + ) -> azalea_ecs::ecs::Mut<'a, LocalPlayer> { + self.query::<&mut LocalPlayer>(ecs) } - async fn handle( - packet: &ClientboundGamePacket, - client: &Client, - tx: &Sender<Event>, - ) -> Result<(), HandleError> { - let packet = Arc::new(packet.clone()); - tx.send(Event::Packet(packet.clone())).await?; - match &*packet { - ClientboundGamePacket::Login(p) => { - debug!("Got login packet"); - - { - // // write p into login.txt - // std::io::Write::write_all( - // &mut std::fs::File::create("login.txt").unwrap(), - // format!("{:#?}", p).as_bytes(), - // ) - // .unwrap(); - - // TODO: have registry_holder be a struct because this sucks rn - // best way would be to add serde support to azalea-nbt - - let registry_holder = p - .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types - .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) - .unwrap_or_else(|| { - panic!("No dimension_type with name {}", p.dimension_type) - }) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = *dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int"); - - let world_name = p.dimension.clone(); - - *client.world_name.write() = Some(world_name.clone()); - // add this world to the world_container (or don't if it's already there) - let weak_world = client - .world_container - .write() - .insert(world_name, height, min_y); - // set the loaded_world to an empty world - // (when we add chunks or entities those will be in the world_container) - let mut world_lock = client.world.write(); - *world_lock = PartialWorld::new( - client.client_information.read().view_distance.into(), - weak_world, - Some(p.player_id), - ); - - let entity = EntityData::new( - client.profile.uuid, - Vec3::default(), - EntityMetadata::Player(metadata::Player::default()), - ); - // make it so other entities don't update this entity in a shared world - world_lock.add_entity(p.player_id, entity); - - *client.entity_id.write() = p.player_id; - } - - // send the client information that we have set - let client_information_packet: ClientInformation = - client.client_information.read().clone(); - log::debug!( - "Sending client information because login: {:?}", - client_information_packet - ); - client.write_packet(client_information_packet.get()).await?; - - // brand - client - .write_packet( - ServerboundCustomPayloadPacket { - identifier: ResourceLocation::new("brand").unwrap(), - // they don't have to know :) - data: "vanilla".into(), - } - .get(), - ) - .await?; - - tx.send(Event::Login).await?; - } - ClientboundGamePacket::SetChunkCacheRadius(p) => { - debug!("Got set chunk cache radius packet {:?}", p); - } - ClientboundGamePacket::CustomPayload(p) => { - debug!("Got custom payload packet {:?}", p); - } - ClientboundGamePacket::ChangeDifficulty(p) => { - debug!("Got difficulty packet {:?}", p); - } - ClientboundGamePacket::Commands(_p) => { - debug!("Got declare commands packet"); - } - ClientboundGamePacket::PlayerAbilities(p) => { - debug!("Got player abilities packet {:?}", p); - } - ClientboundGamePacket::SetCarriedItem(p) => { - debug!("Got set carried item packet {:?}", p); - } - ClientboundGamePacket::UpdateTags(_p) => { - debug!("Got update tags packet"); - } - ClientboundGamePacket::Disconnect(p) => { - debug!("Got disconnect packet {:?}", p); - client.disconnect().await?; - } - ClientboundGamePacket::UpdateRecipes(_p) => { - debug!("Got update recipes packet"); - } - ClientboundGamePacket::EntityEvent(_p) => { - // debug!("Got entity event packet {:?}", p); - } - ClientboundGamePacket::Recipe(_p) => { - debug!("Got recipe packet"); - } - ClientboundGamePacket::PlayerPosition(p) => { - // TODO: reply with teleport confirm - debug!("Got player position packet {:?}", p); - - let (new_pos, y_rot, x_rot) = { - let player_entity_id = *client.entity_id.read(); - - let mut world_lock = client.world.write(); - - let mut player_entity = world_lock.entity_mut(player_entity_id).unwrap(); - - let delta_movement = player_entity.delta; - - let is_x_relative = p.relative_arguments.x; - let is_y_relative = p.relative_arguments.y; - let is_z_relative = p.relative_arguments.z; - - let (delta_x, new_pos_x) = if is_x_relative { - player_entity.last_pos.x += p.x; - (delta_movement.x, player_entity.pos().x + p.x) - } else { - player_entity.last_pos.x = p.x; - (0.0, p.x) - }; - let (delta_y, new_pos_y) = if is_y_relative { - player_entity.last_pos.y += p.y; - (delta_movement.y, player_entity.pos().y + p.y) - } else { - player_entity.last_pos.y = p.y; - (0.0, p.y) - }; - let (delta_z, new_pos_z) = if is_z_relative { - player_entity.last_pos.z += p.z; - (delta_movement.z, player_entity.pos().z + p.z) - } else { - player_entity.last_pos.z = p.z; - (0.0, p.z) - }; - - let mut y_rot = p.y_rot; - let mut x_rot = p.x_rot; - if p.relative_arguments.x_rot { - x_rot += player_entity.x_rot; - } - if p.relative_arguments.y_rot { - y_rot += player_entity.y_rot; - } - - player_entity.delta = Vec3 { - x: delta_x, - y: delta_y, - z: delta_z, - }; - player_entity.set_rotation(y_rot, x_rot); - // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means - // so investigate that ig - let new_pos = Vec3 { - x: new_pos_x, - y: new_pos_y, - z: new_pos_z, - }; - world_lock - .set_entity_pos(player_entity_id, new_pos) - .expect("The player entity should always exist"); - - (new_pos, y_rot, x_rot) - }; - - client - .write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()) - .await?; - client - .write_packet( - ServerboundMovePlayerPosRotPacket { - x: new_pos.x, - y: new_pos.y, - z: new_pos.z, - y_rot, - x_rot, - // this is always false - on_ground: false, - } - .get(), - ) - .await?; - } - ClientboundGamePacket::PlayerInfoUpdate(p) => { - debug!("Got player info packet {:?}", p); - let mut events = Vec::new(); - { - let mut players_lock = client.players.write(); - for updated_info in &p.entries { - // add the new player maybe - if p.actions.add_player { - let player_info = PlayerInfo { - profile: updated_info.profile.clone(), - uuid: updated_info.profile.uuid, - gamemode: updated_info.game_mode, - latency: updated_info.latency, - display_name: updated_info.display_name.clone(), - }; - players_lock.insert(updated_info.profile.uuid, player_info.clone()); - events.push(Event::AddPlayer(player_info)); - } else if let Some(info) = players_lock.get_mut(&updated_info.profile.uuid) - { - // `else if` because the block for add_player above - // already sets all the fields - if p.actions.update_game_mode { - info.gamemode = updated_info.game_mode; - } - if p.actions.update_latency { - info.latency = updated_info.latency; - } - if p.actions.update_display_name { - info.display_name = updated_info.display_name.clone(); - } - events.push(Event::UpdatePlayer(info.clone())); - } else { - warn!( - "Ignoring PlayerInfoUpdate for unknown player {}", - updated_info.profile.uuid - ); - } - } - } - for event in events { - tx.send(event).await?; - } - } - ClientboundGamePacket::PlayerInfoRemove(p) => { - let mut events = Vec::new(); - { - let mut players_lock = client.players.write(); - for uuid in &p.profile_ids { - if let Some(info) = players_lock.remove(uuid) { - events.push(Event::RemovePlayer(info)); - } - } - } - for event in events { - tx.send(event).await?; - } - } - ClientboundGamePacket::SetChunkCacheCenter(p) => { - debug!("Got chunk cache center packet {:?}", p); - client - .world - .write() - .update_view_center(&ChunkPos::new(p.x, p.z)); - } - ClientboundGamePacket::LevelChunkWithLight(p) => { - // debug!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_has_chunk = client.world.read().get_chunk(&pos).is_some(); - let this_client_has_chunk = client - .world - .read() - .chunk_storage - .limited_get(&pos) - .is_some(); - if shared_has_chunk && !this_client_has_chunk { - trace!( - "Skipping parsing chunk {:?} because we already know about it", - pos - ); - return Ok(()); - } - - // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); - // debug("chunk {:?}") - if let Err(e) = client - .world - .write() - .replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data)) - { - error!("Couldn't set chunk data: {}", e); - } - } - ClientboundGamePacket::LightUpdate(_p) => { - // debug!("Got light update packet {:?}", p); - } - ClientboundGamePacket::AddEntity(p) => { - debug!("Got add entity packet {:?}", p); - let entity = EntityData::from(p); - client.world.write().add_entity(p.id, entity); - } - ClientboundGamePacket::SetEntityData(p) => { - debug!("Got set entity data packet {:?}", p); - let mut world = client.world.write(); - if let Some(mut entity) = world.entity_mut(p.id) { - entity.apply_metadata(&p.packed_items.0); - } else { - // warn!("Server sent an entity data packet for an entity id - // ({}) that we don't know about", p.id); - } - } - ClientboundGamePacket::UpdateAttributes(_p) => { - // debug!("Got update attributes packet {:?}", p); - } - ClientboundGamePacket::SetEntityMotion(_p) => { - // debug!("Got entity velocity packet {:?}", p); - } - ClientboundGamePacket::SetEntityLink(p) => { - debug!("Got set entity link packet {:?}", p); - } - ClientboundGamePacket::AddPlayer(p) => { - debug!("Got add player packet {:?}", p); - let entity = EntityData::from(p); - client.world.write().add_entity(p.id, entity); - } - ClientboundGamePacket::InitializeBorder(p) => { - debug!("Got initialize border packet {:?}", p); - } - ClientboundGamePacket::SetTime(p) => { - debug!("Got set time packet {:?}", p); - } - ClientboundGamePacket::SetDefaultSpawnPosition(p) => { - debug!("Got set default spawn position packet {:?}", p); - } - ClientboundGamePacket::ContainerSetContent(p) => { - debug!("Got container set content packet {:?}", p); - } - ClientboundGamePacket::SetHealth(p) => { - debug!("Got set health packet {:?}", p); - if p.health == 0.0 { - // we can't define a variable here with client.dead.lock() - // because of https://github.com/rust-lang/rust/issues/57478 - if !*client.dead.lock() { - *client.dead.lock() = true; - tx.send(Event::Death(None)).await?; - } - } - } - ClientboundGamePacket::SetExperience(p) => { - debug!("Got set experience packet {:?}", p); - } - ClientboundGamePacket::TeleportEntity(p) => { - let mut world_lock = client.world.write(); - let _ = world_lock.set_entity_pos( - p.id, - Vec3 { - x: p.x, - y: p.y, - z: p.z, - }, - ); - } - ClientboundGamePacket::UpdateAdvancements(p) => { - debug!("Got update advancements packet {:?}", p); - } - ClientboundGamePacket::RotateHead(_p) => { - // debug!("Got rotate head packet {:?}", p); - } - ClientboundGamePacket::MoveEntityPos(p) => { - let mut world_lock = client.world.write(); - - let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta); - } - ClientboundGamePacket::MoveEntityPosRot(p) => { - let mut world_lock = client.world.write(); - - let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta); - } - ClientboundGamePacket::MoveEntityRot(_p) => { - // debug!("Got move entity rot packet {:?}", p); - } - ClientboundGamePacket::KeepAlive(p) => { - debug!("Got keep alive packet {:?}", p); - client - .write_packet(ServerboundKeepAlivePacket { id: p.id }.get()) - .await?; - } - ClientboundGamePacket::RemoveEntities(p) => { - debug!("Got remove entities packet {:?}", p); - } - ClientboundGamePacket::PlayerChat(p) => { - debug!("Got player chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone())))) - .await?; - } - ClientboundGamePacket::SystemChat(p) => { - debug!("Got system chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone())))) - .await?; - } - ClientboundGamePacket::Sound(_p) => { - // debug!("Got sound packet {:?}", p); - } - ClientboundGamePacket::LevelEvent(p) => { - debug!("Got level event packet {:?}", p); - } - ClientboundGamePacket::BlockUpdate(p) => { - debug!("Got block update packet {:?}", p); - let mut world = client.world.write(); - world.set_block_state(&p.pos, p.block_state); - } - ClientboundGamePacket::Animate(p) => { - debug!("Got animate packet {:?}", p); - } - ClientboundGamePacket::SectionBlocksUpdate(p) => { - debug!("Got section blocks update packet {:?}", p); - let mut world = client.world.write(); - for state in &p.states { - world.set_block_state(&(p.section_pos + state.pos.clone()), state.state); - } - } - ClientboundGamePacket::GameEvent(p) => { - debug!("Got game event packet {:?}", p); - } - ClientboundGamePacket::LevelParticles(p) => { - debug!("Got level particles packet {:?}", p); - } - ClientboundGamePacket::ServerData(p) => { - debug!("Got server data packet {:?}", p); - } - ClientboundGamePacket::SetEquipment(p) => { - debug!("Got set equipment packet {:?}", p); - } - ClientboundGamePacket::UpdateMobEffect(p) => { - debug!("Got update mob effect packet {:?}", p); - } - ClientboundGamePacket::AddExperienceOrb(_) => {} - ClientboundGamePacket::AwardStats(_) => {} - ClientboundGamePacket::BlockChangedAck(_) => {} - ClientboundGamePacket::BlockDestruction(_) => {} - ClientboundGamePacket::BlockEntityData(_) => {} - ClientboundGamePacket::BlockEvent(_) => {} - ClientboundGamePacket::BossEvent(_) => {} - ClientboundGamePacket::CommandSuggestions(_) => {} - ClientboundGamePacket::ContainerSetData(_) => {} - ClientboundGamePacket::ContainerSetSlot(_) => {} - ClientboundGamePacket::Cooldown(_) => {} - ClientboundGamePacket::CustomChatCompletions(_) => {} - ClientboundGamePacket::DeleteChat(_) => {} - ClientboundGamePacket::Explode(_) => {} - ClientboundGamePacket::ForgetLevelChunk(_) => {} - ClientboundGamePacket::HorseScreenOpen(_) => {} - ClientboundGamePacket::MapItemData(_) => {} - ClientboundGamePacket::MerchantOffers(_) => {} - ClientboundGamePacket::MoveVehicle(_) => {} - ClientboundGamePacket::OpenBook(_) => {} - ClientboundGamePacket::OpenScreen(_) => {} - ClientboundGamePacket::OpenSignEditor(_) => {} - ClientboundGamePacket::Ping(_) => {} - ClientboundGamePacket::PlaceGhostRecipe(_) => {} - ClientboundGamePacket::PlayerCombatEnd(_) => {} - ClientboundGamePacket::PlayerCombatEnter(_) => {} - ClientboundGamePacket::PlayerCombatKill(p) => { - debug!("Got player kill packet {:?}", p); - if *client.entity_id.read() == p.player_id { - // we can't define a variable here with client.dead.lock() - // because of https://github.com/rust-lang/rust/issues/57478 - if !*client.dead.lock() { - *client.dead.lock() = true; - tx.send(Event::Death(Some(Arc::new(p.clone())))).await?; - } - } - } - ClientboundGamePacket::PlayerLookAt(_) => {} - ClientboundGamePacket::RemoveMobEffect(_) => {} - ClientboundGamePacket::ResourcePack(_) => {} - ClientboundGamePacket::Respawn(p) => { - debug!("Got respawn packet {:?}", p); - // Sets clients dead state to false. - let mut dead_lock = client.dead.lock(); - *dead_lock = false; - } - ClientboundGamePacket::SelectAdvancementsTab(_) => {} - ClientboundGamePacket::SetActionBarText(_) => {} - ClientboundGamePacket::SetBorderCenter(_) => {} - ClientboundGamePacket::SetBorderLerpSize(_) => {} - ClientboundGamePacket::SetBorderSize(_) => {} - ClientboundGamePacket::SetBorderWarningDelay(_) => {} - ClientboundGamePacket::SetBorderWarningDistance(_) => {} - ClientboundGamePacket::SetCamera(_) => {} - ClientboundGamePacket::SetDisplayObjective(_) => {} - ClientboundGamePacket::SetObjective(_) => {} - ClientboundGamePacket::SetPassengers(_) => {} - ClientboundGamePacket::SetPlayerTeam(_) => {} - ClientboundGamePacket::SetScore(_) => {} - ClientboundGamePacket::SetSimulationDistance(_) => {} - ClientboundGamePacket::SetSubtitleText(_) => {} - ClientboundGamePacket::SetTitleText(_) => {} - ClientboundGamePacket::SetTitlesAnimation(_) => {} - ClientboundGamePacket::SoundEntity(_) => {} - ClientboundGamePacket::StopSound(_) => {} - ClientboundGamePacket::TabList(_) => {} - ClientboundGamePacket::TagQuery(_) => {} - ClientboundGamePacket::TakeItemEntity(_) => {} - ClientboundGamePacket::DisguisedChat(_) => {} - ClientboundGamePacket::UpdateEnabledFeatures(_) => {} - ClientboundGamePacket::ContainerClose(_) => {} - } - - Ok(()) - } - - /// Runs game_tick every 50 milliseconds. - async fn game_tick_loop(mut client: Client, tx: Sender<Event>) { - let mut game_tick_interval = time::interval(time::Duration::from_millis(50)); - // TODO: Minecraft bursts up to 10 ticks and then skips, we should too - game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst); - loop { - game_tick_interval.tick().await; - Self::game_tick(&mut client, &tx).await; - } - } - - /// Runs every 50 milliseconds. - async fn game_tick(client: &mut Client, tx: &Sender<Event>) { - // return if there's no chunk at the player's position - - { - let world_lock = client.world(); - let player_entity_id = *client.entity_id.read(); - let player_entity = world_lock.entity(player_entity_id); - let Some(player_entity) = player_entity else { - return; - }; - let player_chunk_pos: ChunkPos = player_entity.pos().into(); - if world_lock.get_chunk(&player_chunk_pos).is_none() { - return; - } - } - - tx.send(Event::Tick) - .await - .expect("Sending tick event should never fail"); - - // TODO: if we're a passenger, send the required packets - - if let Err(e) = client.send_position().await { - warn!("Error sending position: {:?}", e); - } - client.ai_step(); - - // TODO: minecraft does ambient sounds here + /// Get a component from this client. This will clone the component and + /// return it. + pub fn component<T: Component + Clone>(&self) -> T { + self.query::<&T>(&mut self.ecs.lock()).clone() } /// Get a reference to our (potentially shared) world. /// - /// This gets the [`WeakWorld`] from our world container. If it's a normal + /// This gets the [`World`] from our world container. If it's a normal /// client, then it'll be the same as the world the client has loaded. /// If the client using a shared world, then the shared world will be a /// superset of the client's world. - pub fn world(&self) -> Arc<WeakWorld> { - self.world.read().shared.clone() - } - - /// Returns the entity associated to the player. - pub fn entity(&self) -> Entity<Arc<WeakWorld>> { - let entity_id = *self.entity_id.read(); + pub fn world(&self) -> Arc<RwLock<World>> { + let mut ecs = self.ecs.lock(); + + let world_name = { + let local_player = self.local_player(&mut ecs); + local_player + .world_name + .as_ref() + .expect("World name must be known if we're doing Client::world") + .clone() + }; - let world = self.world(); - let entity_data = world - .entity_storage - .read() - .get_by_id(entity_id) - .expect("Player entity should be in the given world"); - let entity_ptr = unsafe { entity_data.as_ptr() }; - Entity::new(world, entity_id, entity_ptr) + let world_container = ecs.resource::<WorldContainer>(); + world_container.get(&world_name).unwrap() } /// Returns whether we have a received the login packet yet. pub fn logged_in(&self) -> bool { // the login packet tells us the world name - self.world_name.read().is_some() + self.local_player(&mut self.ecs.lock()).world_name.is_some() } /// Tell the server we changed our game options (i.e. render distance, main @@ -1097,34 +414,156 @@ impl Client { client_information: ServerboundClientInformationPacket, ) -> Result<(), std::io::Error> { { - let mut client_information_lock = self.client_information.write(); - *client_information_lock = client_information; + self.local_player_mut(&mut self.ecs.lock()) + .client_information = client_information; } if self.logged_in() { - let client_information_packet = { - let client_information = self.client_information.read(); - client_information.clone().get() - }; + let client_information_packet = self + .local_player(&mut self.ecs.lock()) + .client_information + .clone() + .get(); log::debug!( "Sending client information (already logged in): {:?}", client_information_packet ); - self.write_packet(client_information_packet).await?; + self.write_packet(client_information_packet); } Ok(()) } - /// Get your player entity's metadata. You can use this to get your health, - /// xp score, and other useful information. - pub fn metadata(&self) -> metadata::Player { - self.entity().metadata.clone().into_player().unwrap() + /// Get a HashMap of all the players in the tab list. + pub fn players(&mut self) -> HashMap<Uuid, PlayerInfo> { + self.local_player(&mut self.ecs.lock()).players.clone() + } +} + +pub struct AzaleaPlugin; +impl Plugin for AzaleaPlugin { + fn build(&self, app: &mut App) { + app.add_event::<StartWalkEvent>() + .add_event::<StartSprintEvent>(); + + app.add_plugins(DefaultPlugins); + + app.add_tick_system_set( + SystemSet::new() + .with_system(send_position) + .with_system(update_in_loaded_chunk) + .with_system( + local_player_ai_step + .before("ai_step") + .after("sprint_listener"), + ), + ); + + // fire the Death event when the player dies. + app.add_system(death_event.after("tick").after("packet")); + + // walk and sprint event listeners + app.add_system(walk_listener.label("walk_listener").before("travel")) + .add_system( + sprint_listener + .label("sprint_listener") + .before("travel") + .before("walk_listener"), + ); + + // add GameProfileComponent when we get an AddPlayerEvent + app.add_system( + retroactively_add_game_profile_component + .after("tick") + .after("packet"), + ); + + app.init_resource::<WorldContainer>(); + } +} + +/// Create the [`App`]. This won't actually run anything yet. +/// +/// Note that you usually only need this if you're creating a client manually, +/// otherwise use [`Client::join`]. +/// +/// Use [`start_ecs`] to actually start running the app and then +/// [`Client::start_client`] to add a client to the ECS and make it join a +/// server. +#[doc(hidden)] +pub fn init_ecs_app() -> App { + // if you get an error right here that means you're doing something with locks + // wrong read the error to see where the issue is + // you might be able to just drop the lock or put it in its own scope to fix + + let mut app = App::new(); + app.add_plugin(AzaleaPlugin); + app +} + +/// Start running the ECS loop! You must create your `App` from [`init_ecs_app`] +/// first. +#[doc(hidden)] +pub fn start_ecs( + app: App, + run_schedule_receiver: mpsc::Receiver<()>, + run_schedule_sender: mpsc::Sender<()>, +) -> Arc<Mutex<Ecs>> { + // all resources should have been added by now so we can take the ecs from the + // app + let ecs = Arc::new(Mutex::new(app.world)); + + tokio::spawn(run_schedule_loop( + ecs.clone(), + app.schedule, + run_schedule_receiver, + )); + tokio::spawn(tick_run_schedule_loop(run_schedule_sender)); + + ecs +} + +async fn run_schedule_loop( + ecs: Arc<Mutex<Ecs>>, + mut schedule: Schedule, + mut run_schedule_receiver: mpsc::Receiver<()>, +) { + loop { + // whenever we get an event from run_schedule_receiver, run the schedule + run_schedule_receiver.recv().await; + schedule.run(&mut ecs.lock()); + } +} + +/// Send an event to run the schedule every 50 milliseconds. It will stop when +/// the receiver is dropped. +pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::Sender<()>) { + let mut game_tick_interval = time::interval(time::Duration::from_millis(50)); + // TODO: Minecraft bursts up to 10 ticks and then skips, we should too + game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst); + + loop { + game_tick_interval.tick().await; + if let Err(e) = run_schedule_sender.send(()).await { + println!("tick_run_schedule_loop error: {e}"); + // the sender is closed so end the task + return; + } } } -impl<T> From<std::sync::PoisonError<T>> for HandleError { - fn from(e: std::sync::PoisonError<T>) -> Self { - HandleError::Poison(e.to_string()) +/// This plugin group will add all the default plugins necessary for Azalea to +/// work. +pub struct DefaultPlugins; + +impl PluginGroup for DefaultPlugins { + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::start::<Self>() + .add(TickPlugin::default()) + .add(PacketHandlerPlugin) + .add(EntityPlugin) + .add(PhysicsPlugin) + .add(EventPlugin) + .add(TaskPoolPlugin::default()) } } diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs new file mode 100644 index 00000000..0e486741 --- /dev/null +++ b/azalea-client/src/entity_query.rs @@ -0,0 +1,100 @@ +use std::sync::Arc; + +use azalea_ecs::{ + component::Component, + ecs::Ecs, + entity::Entity, + query::{ROQueryItem, ReadOnlyWorldQuery, WorldQuery}, +}; +use parking_lot::Mutex; + +use crate::Client; + +impl Client { + /// A convenience function for getting components of our player's entity. + pub fn query<'w, Q: WorldQuery>(&self, ecs: &'w mut Ecs) -> <Q as WorldQuery>::Item<'w> { + ecs.query::<Q>() + .get_mut(ecs, self.entity) + .expect("Our client is missing a required component.") + } + + /// Return a lightweight [`Entity`] for the entity that matches the given + /// predicate function. + /// + /// You can then use [`Self::entity_component`] to get components from this + /// entity. + /// + /// # Example + /// Note that this will very likely change in the future. + /// ``` + /// use azalea_client::{Client, GameProfileComponent}; + /// use azalea_ecs::query::With; + /// use azalea_world::entity::{Position, metadata::Player}; + /// + /// # fn example(mut bot: Client, sender_name: String) { + /// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>( + /// |profile: &&GameProfileComponent| profile.name == sender_name, + /// ); + /// if let Some(entity) = entity { + /// let position = bot.entity_component::<Position>(entity); + /// // ... + /// } + /// # } + /// ``` + pub fn entity_by<F: ReadOnlyWorldQuery, Q: ReadOnlyWorldQuery>( + &mut self, + predicate: impl EntityPredicate<Q, F>, + ) -> Option<Entity> { + predicate.find(self.ecs.clone()) + } + + /// Get a component from an entity. Note that this will return an owned type + /// (i.e. not a reference) so it may be expensive for larger types. + /// + /// If you're trying to get a component for this client, use + /// [`Self::component`]. + pub fn entity_component<Q: Component + Clone>(&mut self, entity: Entity) -> Q { + let mut ecs = self.ecs.lock(); + let mut q = ecs.query::<&Q>(); + let components = q + .get(&ecs, entity) + .expect("Entity components must be present in Client::entity)components."); + components.clone() + } +} + +pub trait EntityPredicate<Q: ReadOnlyWorldQuery, Filter: ReadOnlyWorldQuery> { + fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity>; +} +impl<F, Q, Filter> EntityPredicate<(Q,), Filter> for F +where + F: Fn(&ROQueryItem<Q>) -> bool, + Q: ReadOnlyWorldQuery, + Filter: ReadOnlyWorldQuery, +{ + fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity> { + let mut ecs = ecs_lock.lock(); + let mut query = ecs.query_filtered::<(Entity, Q), Filter>(); + let entity = query.iter(&ecs).find(|(_, q)| (self)(q)).map(|(e, _)| e); + + entity + } +} + +// impl<'a, F, Q1, Q2> EntityPredicate<'a, (Q1, Q2)> for F +// where +// F: Fn(&<Q1 as WorldQuery>::Item<'_>, &<Q2 as WorldQuery>::Item<'_>) -> +// bool, Q1: ReadOnlyWorldQuery, +// Q2: ReadOnlyWorldQuery, +// { +// fn find(&self, ecs: &mut Ecs) -> Option<Entity> { +// // (self)(query) +// let mut query = ecs.query_filtered::<(Entity, Q1, Q2), ()>(); +// let entity = query +// .iter(ecs) +// .find(|(_, q1, q2)| (self)(q1, q2)) +// .map(|(e, _, _)| e); + +// entity +// } +// } diff --git a/azalea-client/src/events.rs b/azalea-client/src/events.rs new file mode 100644 index 00000000..f8b9f434 --- /dev/null +++ b/azalea-client/src/events.rs @@ -0,0 +1,189 @@ +//! Defines the [`Event`] enum and makes those events trigger when they're sent +//! in the ECS. + +use std::sync::Arc; + +use azalea_ecs::{ + app::{App, Plugin}, + component::Component, + event::EventReader, + query::{Added, Changed}, + system::Query, + AppTickExt, +}; +use azalea_protocol::packets::game::{ + clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, ClientboundGamePacket, +}; +use azalea_world::entity::MinecraftEntityId; +use derive_more::{Deref, DerefMut}; +use tokio::sync::mpsc; + +use crate::{ + packet_handling::{ + AddPlayerEvent, ChatReceivedEvent, DeathEvent, PacketReceiver, RemovePlayerEvent, + UpdatePlayerEvent, + }, + ChatPacket, PlayerInfo, +}; + +// (for contributors): +// HOW TO ADD A NEW (packet based) EVENT: +// - make a struct that contains an entity field and a data field (look in +// packet_handling.rs for examples, also you should end the struct name with +// "Event") +// - the entity field is the local player entity that's receiving the event +// - in packet_handling, you always have a variable called player_entity that +// you can use +// - add the event struct in the `impl Plugin for PacketHandlerPlugin` +// - to get the event writer, you have to get an +// EventWriter<SomethingHappenedEvent> from the SystemState (the convention is +// to end your variable with the word "events", like "something_events") +// +// - then here in this file, add it to the Event enum +// - and make an event listener system/function like the other ones and put the +// function in the `impl Plugin for EventPlugin` + +/// Something that happened in-game, such as a tick passing or chat message +/// being sent. +/// +/// Note: Events are sent before they're processed, so for example game ticks +/// happen at the beginning of a tick before anything has happened. +#[derive(Debug, Clone)] +pub enum Event { + /// Happens right after the bot switches into the Game state, but before + /// it's actually spawned. This can be useful for setting the client + /// information with `Client::set_client_information`, so the packet + /// doesn't have to be sent twice. + Init, + /// The client is now in the world. Fired when we receive a login packet. + Login, + /// A chat message was sent in the game chat. + Chat(ChatPacket), + /// Happens 20 times per second, but only when the world is loaded. + Tick, + Packet(Arc<ClientboundGamePacket>), + /// A player joined the game (or more specifically, was added to the tab + /// list). + AddPlayer(PlayerInfo), + /// A player left the game (or maybe is still in the game and was just + /// removed from the tab list). + RemovePlayer(PlayerInfo), + /// A player was updated in the tab list (gamemode, display + /// name, or latency changed). + UpdatePlayer(PlayerInfo), + /// The client player died in-game. + Death(Option<Arc<ClientboundPlayerCombatKillPacket>>), +} + +/// A component that contains an event sender for events that are only +/// received by local players. The receiver for this is returned by +/// [`Client::start_client`]. +/// +/// [`Client::start_client`]: crate::Client::start_client +#[derive(Component, Deref, DerefMut)] +pub struct LocalPlayerEvents(pub mpsc::UnboundedSender<Event>); + +pub struct EventPlugin; +impl Plugin for EventPlugin { + fn build(&self, app: &mut App) { + app.add_system(chat_listener) + .add_system(login_listener) + .add_system(init_listener) + .add_system(packet_listener) + .add_system(add_player_listener) + .add_system(update_player_listener) + .add_system(remove_player_listener) + .add_system(death_listener) + .add_tick_system(tick_listener); + } +} + +// when LocalPlayerEvents is added, it means the client just started +fn init_listener(query: Query<&LocalPlayerEvents, Added<LocalPlayerEvents>>) { + for local_player_events in &query { + local_player_events.send(Event::Init).unwrap(); + } +} + +// when MinecraftEntityId is added, it means the player is now in the world +fn login_listener(query: Query<&LocalPlayerEvents, Added<MinecraftEntityId>>) { + for local_player_events in &query { + local_player_events.send(Event::Login).unwrap(); + } +} + +fn chat_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<ChatReceivedEvent>) { + for event in events.iter() { + let local_player_events = query + .get(event.entity) + .expect("Non-localplayer entities shouldn't be able to receive chat events"); + local_player_events + .send(Event::Chat(event.packet.clone())) + .unwrap(); + } +} + +fn tick_listener(query: Query<&LocalPlayerEvents>) { + for local_player_events in &query { + local_player_events.send(Event::Tick).unwrap(); + } +} + +fn packet_listener(query: Query<(&LocalPlayerEvents, &PacketReceiver), Changed<PacketReceiver>>) { + for (local_player_events, packet_receiver) in &query { + for packet in packet_receiver.packets.lock().iter() { + local_player_events + .send(Event::Packet(packet.clone().into())) + .unwrap(); + } + } +} + +fn add_player_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<AddPlayerEvent>) { + for event in events.iter() { + let local_player_events = query + .get(event.entity) + .expect("Non-localplayer entities shouldn't be able to receive add player events"); + local_player_events + .send(Event::AddPlayer(event.info.clone())) + .unwrap(); + } +} + +fn update_player_listener( + query: Query<&LocalPlayerEvents>, + mut events: EventReader<UpdatePlayerEvent>, +) { + for event in events.iter() { + let local_player_events = query + .get(event.entity) + .expect("Non-localplayer entities shouldn't be able to receive add player events"); + local_player_events + .send(Event::UpdatePlayer(event.info.clone())) + .unwrap(); + } +} + +fn remove_player_listener( + query: Query<&LocalPlayerEvents>, + mut events: EventReader<RemovePlayerEvent>, +) { + for event in events.iter() { + let local_player_events = query + .get(event.entity) + .expect("Non-localplayer entities shouldn't be able to receive add player events"); + local_player_events + .send(Event::RemovePlayer(event.info.clone())) + .unwrap(); + } +} + +fn death_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<DeathEvent>) { + for event in events.iter() { + if let Ok(local_player_events) = query.get(event.entity) { + local_player_events + .send(Event::Death(event.packet.clone().map(|p| p.into()))) + .unwrap(); + } + } +} diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index f2952248..d46516be 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -9,27 +9,25 @@ #![allow(incomplete_features)] #![feature(trait_upcasting)] #![feature(error_generic_member_access)] +#![feature(type_alias_impl_trait)] mod account; mod chat; mod client; +mod entity_query; +mod events; mod get_mc_dir; +mod local_player; mod movement; +pub mod packet_handling; pub mod ping; mod player; -mod plugins; +mod task_pool; pub use account::Account; -pub use client::{ChatPacket, Client, ClientInformation, Event, JoinError, PhysicsState}; -pub use movement::{SprintDirection, WalkDirection}; +pub use azalea_ecs as ecs; +pub use client::{init_ecs_app, start_ecs, ChatPacket, Client, ClientInformation, JoinError}; +pub use events::Event; +pub use local_player::{GameProfileComponent, LocalPlayer}; +pub use movement::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection}; pub use player::PlayerInfo; -pub use plugins::{Plugin, PluginState, PluginStates, Plugins}; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs new file mode 100644 index 00000000..0165a5f5 --- /dev/null +++ b/azalea-client/src/local_player.rs @@ -0,0 +1,164 @@ +use std::{collections::HashMap, io, sync::Arc}; + +use azalea_auth::game_profile::GameProfile; +use azalea_core::{ChunkPos, ResourceLocation}; +use azalea_ecs::component::Component; +use azalea_ecs::entity::Entity; +use azalea_ecs::{query::Added, system::Query}; +use azalea_protocol::packets::game::ServerboundGamePacket; +use azalea_world::{ + entity::{self, Dead}, + PartialWorld, World, +}; +use derive_more::{Deref, DerefMut}; +use parking_lot::RwLock; +use thiserror::Error; +use tokio::{sync::mpsc, task::JoinHandle}; +use uuid::Uuid; + +use crate::{ + events::{Event, LocalPlayerEvents}, + ClientInformation, PlayerInfo, WalkDirection, +}; + +/// A player that you control that is currently in a Minecraft server. +#[derive(Component)] +pub struct LocalPlayer { + pub packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>, + + pub client_information: ClientInformation, + /// A map of player uuids to their information in the tab list + pub players: HashMap<Uuid, PlayerInfo>, + + /// The partial world is the world this client currently has loaded. It has + /// a limited render distance. + pub partial_world: Arc<RwLock<PartialWorld>>, + /// The world is the combined [`PartialWorld`]s of all clients in the same + /// world. (Only relevant if you're using a shared world, i.e. a swarm) + pub world: Arc<RwLock<World>>, + pub world_name: Option<ResourceLocation>, + + /// A list of async tasks that are running and will stop running when this + /// LocalPlayer is dropped or disconnected with [`Self::disconnect`] + pub(crate) tasks: Vec<JoinHandle<()>>, +} + +/// Component for entities that can move and sprint. Usually only in +/// [`LocalPlayer`] entities. +#[derive(Default, Component)] +pub struct PhysicsState { + /// Minecraft only sends a movement packet either after 20 ticks or if the + /// player moved enough. This is that tick counter. + pub position_remainder: u32, + pub was_sprinting: bool, + // Whether we're going to try to start sprinting this tick. Equivalent to + // holding down ctrl for a tick. + pub trying_to_sprint: bool, + + pub move_direction: WalkDirection, + pub forward_impulse: f32, + pub left_impulse: f32, +} + +/// A component only present in players that contains the [`GameProfile`] (which +/// you can use to get a player's name). +/// +/// Note that it's possible for this to be missing in a player if the server +/// never sent the player info for them (though this is uncommon). +#[derive(Component, Clone, Debug, Deref, DerefMut)] +pub struct GameProfileComponent(pub GameProfile); + +/// Marks a [`LocalPlayer`] that's in a loaded chunk. This is updated at the +/// beginning of every tick. +#[derive(Component)] +pub struct LocalPlayerInLoadedChunk; + +impl LocalPlayer { + /// Create a new `LocalPlayer`. + pub fn new( + entity: Entity, + packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>, + world: Arc<RwLock<World>>, + ) -> Self { + let client_information = ClientInformation::default(); + + LocalPlayer { + packet_writer, + + client_information: ClientInformation::default(), + players: HashMap::new(), + + world, + partial_world: Arc::new(RwLock::new(PartialWorld::new( + client_information.view_distance.into(), + Some(entity), + ))), + world_name: None, + + tasks: Vec::new(), + } + } + + /// Spawn a task to write a packet directly to the server. + pub fn write_packet(&mut self, packet: ServerboundGamePacket) { + self.packet_writer + .send(packet) + .expect("write_packet shouldn't be able to be called if the connection is closed"); + } + + /// Disconnect this client from the server by ending all tasks. + /// + /// The OwnedReadHalf for the TCP connection is in one of the tasks, so it + /// automatically closes the connection when that's dropped. + pub fn disconnect(&self) { + for task in &self.tasks { + task.abort(); + } + } +} + +/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s. +pub fn update_in_loaded_chunk( + mut commands: azalea_ecs::system::Commands, + query: Query<(Entity, &LocalPlayer, &entity::Position)>, +) { + for (entity, local_player, position) in &query { + let player_chunk_pos = ChunkPos::from(position); + let in_loaded_chunk = local_player + .world + .read() + .chunks + .get(&player_chunk_pos) + .is_some(); + if in_loaded_chunk { + commands.entity(entity).insert(LocalPlayerInLoadedChunk); + } else { + commands.entity(entity).remove::<LocalPlayerInLoadedChunk>(); + } + } +} + +/// Send the "Death" event for [`LocalPlayer`]s that died with no reason. +pub fn death_event(query: Query<&LocalPlayerEvents, Added<Dead>>) { + for local_player_events in &query { + local_player_events.send(Event::Death(None)).unwrap(); + } +} + +#[derive(Error, Debug)] +pub enum HandlePacketError { + #[error("{0}")] + Poison(String), + #[error(transparent)] + Io(#[from] io::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), + #[error("{0}")] + Send(#[from] mpsc::error::SendError<Event>), +} + +impl<T> From<std::sync::PoisonError<T>> for HandlePacketError { + fn from(e: std::sync::PoisonError<T>) -> Self { + HandlePacketError::Poison(e.to_string()) + } +} diff --git a/azalea-client/src/movement.rs b/azalea-client/src/movement.rs index d9cab1d4..8d6faabe 100644 --- a/azalea-client/src/movement.rs +++ b/azalea-client/src/movement.rs @@ -1,9 +1,7 @@ -use std::backtrace::Backtrace; - -use crate::Client; -use azalea_core::Vec3; -use azalea_physics::collision::{MovableEntity, MoverType}; -use azalea_physics::HasPhysics; +use crate::client::Client; +use crate::local_player::{LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState}; +use azalea_ecs::entity::Entity; +use azalea_ecs::{event::EventReader, query::With, system::Query}; use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket; use azalea_protocol::packets::game::{ serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, @@ -11,7 +9,11 @@ use azalea_protocol::packets::game::{ serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket, serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket, }; -use azalea_world::MoveEntityError; +use azalea_world::{ + entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId}, + MoveEntityError, +}; +use std::backtrace::Backtrace; use thiserror::Error; #[derive(Error, Debug)] @@ -33,24 +35,72 @@ impl From<MoveEntityError> for MovePlayerError { } impl Client { - /// This gets called automatically every tick. - pub(crate) async fn send_position(&mut self) -> Result<(), MovePlayerError> { + /// Set whether we're jumping. This acts as if you held space in + /// vanilla. If you want to jump once, use the `jump` function. + /// + /// If you're making a realistic client, calling this function every tick is + /// recommended. + pub fn set_jumping(&mut self, jumping: bool) { + let mut ecs = self.ecs.lock(); + let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs); + **jumping_mut = jumping; + } + + /// 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 + } + + /// Sets your rotation. `y_rot` is yaw (looking to the side), `x_rot` is + /// pitch (looking up and down). You can get these numbers from the vanilla + /// f3 screen. + /// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90. + pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { + let mut ecs = self.ecs.lock(); + let mut physics = self.query::<&mut entity::Physics>(&mut ecs); + + entity::set_rotation(&mut physics, y_rot, x_rot); + } +} + +#[allow(clippy::type_complexity)] +pub(crate) fn send_position( + mut query: Query< + ( + &MinecraftEntityId, + &mut LocalPlayer, + &mut PhysicsState, + &entity::Position, + &mut entity::LastSentPosition, + &mut entity::Physics, + &entity::metadata::Sprinting, + ), + &LocalPlayerInLoadedChunk, + >, +) { + for ( + id, + mut local_player, + mut physics_state, + position, + mut last_sent_position, + mut physics, + sprinting, + ) in query.iter_mut() + { + local_player.send_sprinting_if_needed(id, sprinting, &mut physics_state); + let packet = { - self.send_sprinting_if_needed().await?; // TODO: the camera being able to be controlled by other entities isn't // implemented yet if !self.is_controlled_camera() { return }; - let mut physics_state = self.physics_state.lock(); - - let player_entity = self.entity(); - let player_pos = player_entity.pos(); - let player_old_pos = player_entity.last_pos; - - let x_delta = player_pos.x - player_old_pos.x; - let y_delta = player_pos.y - player_old_pos.y; - let z_delta = player_pos.z - player_old_pos.z; - let y_rot_delta = (player_entity.y_rot - player_entity.y_rot_last) as f64; - let x_rot_delta = (player_entity.x_rot - player_entity.x_rot_last) as f64; + let x_delta = position.x - last_sent_position.x; + let y_delta = position.y - last_sent_position.y; + let z_delta = position.z - last_sent_position.z; + let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64; + let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64; physics_state.position_remainder += 1; @@ -67,38 +117,38 @@ impl Client { let packet = if sending_position && sending_rotation { Some( ServerboundMovePlayerPosRotPacket { - x: player_pos.x, - y: player_pos.y, - z: player_pos.z, - x_rot: player_entity.x_rot, - y_rot: player_entity.y_rot, - on_ground: player_entity.on_ground, + x: position.x, + y: position.y, + z: position.z, + x_rot: physics.x_rot, + y_rot: physics.y_rot, + on_ground: physics.on_ground, } .get(), ) } else if sending_position { Some( ServerboundMovePlayerPosPacket { - x: player_pos.x, - y: player_pos.y, - z: player_pos.z, - on_ground: player_entity.on_ground, + x: position.x, + y: position.y, + z: position.z, + on_ground: physics.on_ground, } .get(), ) } else if sending_rotation { Some( ServerboundMovePlayerRotPacket { - x_rot: player_entity.x_rot, - y_rot: player_entity.y_rot, - on_ground: player_entity.on_ground, + x_rot: physics.x_rot, + y_rot: physics.y_rot, + on_ground: physics.on_ground, } .get(), ) - } else if player_entity.last_on_ground != player_entity.on_ground { + } else if physics.last_on_ground != physics.on_ground { Some( ServerboundMovePlayerStatusOnlyPacket { - on_ground: player_entity.on_ground, + on_ground: physics.on_ground, } .get(), ) @@ -106,131 +156,56 @@ impl Client { None }; - drop(player_entity); - let mut player_entity = self.entity(); - if sending_position { - player_entity.last_pos = *player_entity.pos(); + **last_sent_position = **position; physics_state.position_remainder = 0; } if sending_rotation { - player_entity.y_rot_last = player_entity.y_rot; - player_entity.x_rot_last = player_entity.x_rot; + physics.y_rot_last = physics.y_rot; + physics.x_rot_last = physics.x_rot; } - player_entity.last_on_ground = player_entity.on_ground; + physics.last_on_ground = physics.on_ground; // minecraft checks for autojump here, but also autojump is bad so packet }; if let Some(packet) = packet { - self.write_packet(packet).await?; + local_player.write_packet(packet); } - - Ok(()) } +} - async fn send_sprinting_if_needed(&mut self) -> Result<(), MovePlayerError> { - let is_sprinting = self.entity().metadata.sprinting; - let was_sprinting = self.physics_state.lock().was_sprinting; - if is_sprinting != was_sprinting { - let sprinting_action = if is_sprinting { +impl LocalPlayer { + fn send_sprinting_if_needed( + &mut self, + id: &MinecraftEntityId, + sprinting: &entity::metadata::Sprinting, + physics_state: &mut PhysicsState, + ) { + let was_sprinting = physics_state.was_sprinting; + if **sprinting != was_sprinting { + let sprinting_action = if **sprinting { azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting } else { azalea_protocol::packets::game::serverbound_player_command_packet::Action::StopSprinting }; - let player_entity_id = self.entity().id; self.write_packet( ServerboundPlayerCommandPacket { - id: player_entity_id, + id: **id, action: sprinting_action, data: 0, } .get(), - ) - .await?; - self.physics_state.lock().was_sprinting = is_sprinting; - } - - Ok(()) - } - - // Set our current position to the provided Vec3, potentially clipping through - // blocks. - pub async fn set_position(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> { - let player_entity_id = *self.entity_id.read(); - let mut world_lock = self.world.write(); - - world_lock.set_entity_pos(player_entity_id, new_pos)?; - - Ok(()) - } - - pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> { - let mut world_lock = self.world.write(); - let player_entity_id = *self.entity_id.read(); - - let mut entity = world_lock - .entity_mut(player_entity_id) - .ok_or(MovePlayerError::PlayerNotInWorld(Backtrace::capture()))?; - log::trace!( - "move entity bounding box: {} {:?}", - entity.id, - entity.bounding_box - ); - - entity.move_colliding(&MoverType::Own, movement)?; - - Ok(()) - } - - /// Makes the bot do one physics tick. Note that this is already handled - /// automatically by the client. - pub fn ai_step(&mut self) { - self.tick_controls(None); - - // server ai step - { - let mut player_entity = self.entity(); - - let physics_state = self.physics_state.lock(); - player_entity.xxa = physics_state.left_impulse; - player_entity.zza = physics_state.forward_impulse; - } - - // TODO: food data and abilities - // let has_enough_food_to_sprint = self.food_data().food_level || - // self.abilities().may_fly; - let has_enough_food_to_sprint = true; - - // TODO: double tapping w to sprint i think - - let trying_to_sprint = self.physics_state.lock().trying_to_sprint; - - if !self.sprinting() - && ( - // !self.is_in_water() - // || self.is_underwater() && - self.has_enough_impulse_to_start_sprinting() - && has_enough_food_to_sprint - // && !self.using_item() - // && !self.has_effect(MobEffects.BLINDNESS) - && trying_to_sprint - ) - { - self.set_sprinting(true); + ); + physics_state.was_sprinting = **sprinting; } - - let mut player_entity = self.entity(); - player_entity.ai_step(); } /// Update the impulse from self.move_direction. The multipler is used for /// sneaking. - pub(crate) fn tick_controls(&mut self, multiplier: Option<f32>) { - let mut physics_state = self.physics_state.lock(); - + pub(crate) fn tick_controls(multiplier: Option<f32>, physics_state: &mut PhysicsState) { let mut forward_impulse: f32 = 0.; let mut left_impulse: f32 = 0.; let move_direction = physics_state.move_direction; @@ -262,7 +237,54 @@ impl Client { physics_state.left_impulse *= multiplier; } } +} + +/// Makes the bot do one physics tick. Note that this is already handled +/// automatically by the client. +pub fn local_player_ai_step( + mut query: Query< + ( + &mut PhysicsState, + &mut entity::Physics, + &mut entity::metadata::Sprinting, + &mut entity::Attributes, + ), + With<LocalPlayerInLoadedChunk>, + >, +) { + for (mut physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() { + LocalPlayer::tick_controls(None, &mut physics_state); + + // server ai step + physics.xxa = physics_state.left_impulse; + physics.zza = physics_state.forward_impulse; + + // TODO: food data and abilities + // let has_enough_food_to_sprint = self.food_data().food_level || + // self.abilities().may_fly; + let has_enough_food_to_sprint = true; + + // TODO: double tapping w to sprint i think + + let trying_to_sprint = physics_state.trying_to_sprint; + + if !**sprinting + && ( + // !self.is_in_water() + // || self.is_underwater() && + has_enough_impulse_to_start_sprinting(&physics_state) + && has_enough_food_to_sprint + // && !self.using_item() + // && !self.has_effect(MobEffects.BLINDNESS) + && trying_to_sprint + ) + { + set_sprinting(true, &mut sprinting, &mut attributes); + } + } +} +impl Client { /// Start walking in the given direction. To sprint, use /// [`Client::sprint`]. To stop walking, call walk with /// `WalkDirection::None`. @@ -280,12 +302,11 @@ impl Client { /// # } /// ``` pub fn walk(&mut self, direction: WalkDirection) { - { - let mut physics_state = self.physics_state.lock(); - physics_state.move_direction = direction; - } - - self.set_sprinting(false); + let mut ecs = self.ecs.lock(); + ecs.send_event(StartWalkEvent { + entity: self.entity, + direction, + }); } /// Start sprinting in the given direction. To stop moving, call @@ -304,71 +325,81 @@ impl Client { /// # } /// ``` pub fn sprint(&mut self, direction: SprintDirection) { - let mut physics_state = self.physics_state.lock(); - physics_state.move_direction = WalkDirection::from(direction); - physics_state.trying_to_sprint = true; + let mut ecs = self.ecs.lock(); + ecs.send_event(StartSprintEvent { + entity: self.entity, + direction, + }); } +} - // Whether we're currently sprinting. - pub fn sprinting(&self) -> bool { - self.entity().metadata.sprinting - } +pub struct StartWalkEvent { + pub entity: Entity, + pub direction: WalkDirection, +} - /// Change whether we're sprinting by adding an attribute modifier to the - /// player. You should use the [`walk`] and [`sprint`] methods instead. - /// Returns if the operation was successful. - fn set_sprinting(&mut self, sprinting: bool) -> bool { - let mut player_entity = self.entity(); - player_entity.metadata.sprinting = sprinting; - if sprinting { - player_entity - .attributes - .speed - .insert(azalea_world::entity::attributes::sprinting_modifier()) - .is_ok() - } else { - player_entity - .attributes - .speed - .remove(&azalea_world::entity::attributes::sprinting_modifier().uuid) - .is_none() +/// Start walking in the given direction. To sprint, use +/// [`Client::sprint`]. To stop walking, call walk with +/// `WalkDirection::None`. +pub fn walk_listener( + mut events: EventReader<StartWalkEvent>, + mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>, +) { + for event in events.iter() { + if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity) + { + physics_state.move_direction = event.direction; + set_sprinting(false, &mut sprinting, &mut attributes); } } +} - /// Set whether we're jumping. This acts as if you held space in - /// vanilla. If you want to jump once, use the `jump` function. - /// - /// If you're making a realistic client, calling this function every tick is - /// recommended. - pub fn set_jumping(&mut self, jumping: bool) { - let mut player_entity = self.entity(); - player_entity.jumping = jumping; - } - - /// Returns whether the player will try to jump next tick. - pub fn jumping(&self) -> bool { - let player_entity = self.entity(); - player_entity.jumping +pub struct StartSprintEvent { + pub entity: Entity, + pub direction: SprintDirection, +} +/// Start sprinting in the given direction. +pub fn sprint_listener( + mut query: Query<&mut PhysicsState>, + mut events: EventReader<StartSprintEvent>, +) { + for event in events.iter() { + if let Ok(mut physics_state) = query.get_mut(event.entity) { + physics_state.move_direction = WalkDirection::from(event.direction); + physics_state.trying_to_sprint = true; + } } +} - /// Sets your rotation. `y_rot` is yaw (looking to the side), `x_rot` is - /// pitch (looking up and down). You can get these numbers from the vanilla - /// f3 screen. - /// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90. - pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { - let mut player_entity = self.entity(); - player_entity.set_rotation(y_rot, x_rot); +/// Change whether we're sprinting by adding an attribute modifier to the +/// player. You should use the [`walk`] and [`sprint`] methods instead. +/// Returns if the operation was successful. +fn set_sprinting( + sprinting: bool, + currently_sprinting: &mut Sprinting, + attributes: &mut Attributes, +) -> bool { + **currently_sprinting = sprinting; + if sprinting { + attributes + .speed + .insert(entity::attributes::sprinting_modifier()) + .is_ok() + } else { + attributes + .speed + .remove(&entity::attributes::sprinting_modifier().uuid) + .is_none() } +} - // Whether the player is moving fast enough to be able to start sprinting. - fn has_enough_impulse_to_start_sprinting(&self) -> bool { - // if self.underwater() { - // self.has_forward_impulse() - // } else { - let physics_state = self.physics_state.lock(); - physics_state.forward_impulse > 0.8 - // } - } +// Whether the player is moving fast enough to be able to start sprinting. +fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool { + // if self.underwater() { + // self.has_forward_impulse() + // } else { + physics_state.forward_impulse > 0.8 + // } } #[derive(Clone, Copy, Debug, Default)] diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs new file mode 100644 index 00000000..db2c3c45 --- /dev/null +++ b/azalea-client/src/packet_handling.rs @@ -0,0 +1,935 @@ +use std::{collections::HashSet, io::Cursor, sync::Arc}; + +use azalea_core::{ChunkPos, ResourceLocation, Vec3}; +use azalea_ecs::{ + app::{App, Plugin}, + component::Component, + ecs::Ecs, + entity::Entity, + event::EventWriter, + query::Changed, + schedule::{IntoSystemDescriptor, SystemSet}, + system::{Commands, Query, ResMut, SystemState}, +}; +use azalea_protocol::{ + connect::{ReadConnection, WriteConnection}, + packets::game::{ + clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, + serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket, + serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, + serverbound_keep_alive_packet::ServerboundKeepAlivePacket, + serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, + ClientboundGamePacket, ServerboundGamePacket, + }, +}; +use azalea_world::{ + entity::{ + metadata::{apply_metadata, Health, PlayerMetadataBundle}, + set_rotation, Dead, EntityBundle, EntityKind, LastSentPosition, MinecraftEntityId, Physics, + PlayerBundle, Position, + }, + LoadedBy, PartialWorld, RelativeEntityUpdate, WorldContainer, +}; +use log::{debug, error, trace, warn}; +use parking_lot::Mutex; +use tokio::sync::mpsc; + +use crate::{ + local_player::{GameProfileComponent, LocalPlayer}, + ChatPacket, ClientInformation, PlayerInfo, +}; + +pub struct PacketHandlerPlugin; + +impl Plugin for PacketHandlerPlugin { + fn build(&self, app: &mut App) { + app.add_system_set( + SystemSet::new().with_system(handle_packets.label("packet").before("tick")), + ) + .add_event::<AddPlayerEvent>() + .add_event::<RemovePlayerEvent>() + .add_event::<UpdatePlayerEvent>() + .add_event::<ChatReceivedEvent>() + .add_event::<DeathEvent>(); + } +} + +/// A player joined the game (or more specifically, was added to the tab +/// list of a local player). +#[derive(Debug)] +pub struct AddPlayerEvent { + /// The local player entity that received this event. + pub entity: Entity, + pub info: PlayerInfo, +} +/// A player left the game (or maybe is still in the game and was just +/// removed from the tab list of a local player). +#[derive(Debug)] +pub struct RemovePlayerEvent { + /// The local player entity that received this event. + pub entity: Entity, + pub info: PlayerInfo, +} +/// A player was updated in the tab list of a local player (gamemode, display +/// name, or latency changed). +#[derive(Debug)] +pub struct UpdatePlayerEvent { + /// The local player entity that received this event. + pub entity: Entity, + pub info: PlayerInfo, +} + +/// A client received a chat message packet. +#[derive(Debug)] +pub struct ChatReceivedEvent { + pub entity: Entity, + pub packet: ChatPacket, +} + +/// Event for when an entity dies. dies. If it's a local player and there's a +/// reason in the death screen, the [`ClientboundPlayerCombatKillPacket`] will +/// be included. +pub struct DeathEvent { + pub entity: Entity, + pub packet: Option<ClientboundPlayerCombatKillPacket>, +} + +/// Something that receives packets from the server. +#[derive(Component, Clone)] +pub struct PacketReceiver { + pub packets: Arc<Mutex<Vec<ClientboundGamePacket>>>, + pub run_schedule_sender: mpsc::Sender<()>, +} + +fn handle_packets(ecs: &mut Ecs) { + let mut events_owned = Vec::new(); + + { + let mut system_state: SystemState< + Query<(Entity, &PacketReceiver), Changed<PacketReceiver>>, + > = SystemState::new(ecs); + let query = system_state.get(ecs); + for (player_entity, packet_events) in &query { + let mut packets = packet_events.packets.lock(); + if !packets.is_empty() { + events_owned.push((player_entity, packets.clone())); + // clear the packets right after we read them + packets.clear(); + } + } + } + + for (player_entity, packets) in events_owned { + for packet in &packets { + match packet { + ClientboundGamePacket::Login(p) => { + debug!("Got login packet"); + + #[allow(clippy::type_complexity)] + let mut system_state: SystemState<( + Commands, + Query<(&mut LocalPlayer, &GameProfileComponent)>, + ResMut<WorldContainer>, + )> = SystemState::new(ecs); + let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs); + let (mut local_player, game_profile) = query.get_mut(player_entity).unwrap(); + + { + // TODO: have registry_holder be a struct because this sucks rn + // best way would be to add serde support to azalea-nbt + + let registry_holder = p + .registry_holder + .as_compound() + .expect("Registry holder is not a compound") + .get("") + .expect("No \"\" tag") + .as_compound() + .expect("\"\" tag is not a compound"); + let dimension_types = registry_holder + .get("minecraft:dimension_type") + .expect("No dimension_type tag") + .as_compound() + .expect("dimension_type is not a compound") + .get("value") + .expect("No dimension_type value") + .as_list() + .expect("dimension_type value is not a list"); + let dimension_type = dimension_types + .iter() + .find(|t| { + t.as_compound() + .expect("dimension_type value is not a compound") + .get("name") + .expect("No name tag") + .as_string() + .expect("name is not a string") + == p.dimension_type.to_string() + }) + .unwrap_or_else(|| { + panic!("No dimension_type with name {}", p.dimension_type) + }) + .as_compound() + .unwrap() + .get("element") + .expect("No element tag") + .as_compound() + .expect("element is not a compound"); + let height = (*dimension_type + .get("height") + .expect("No height tag") + .as_int() + .expect("height tag is not an int")) + .try_into() + .expect("height is not a u32"); + let min_y = *dimension_type + .get("min_y") + .expect("No min_y tag") + .as_int() + .expect("min_y tag is not an int"); + + let world_name = p.dimension.clone(); + + local_player.world_name = Some(world_name.clone()); + // add this world to the world_container (or don't if it's already + // there) + let weak_world = world_container.insert(world_name.clone(), height, min_y); + // set the partial_world to an empty world + // (when we add chunks or entities those will be in the + // world_container) + + *local_player.partial_world.write() = PartialWorld::new( + local_player.client_information.view_distance.into(), + // this argument makes it so other clients don't update this + // player entity + // in a shared world + Some(player_entity), + ); + local_player.world = weak_world; + + let player_bundle = PlayerBundle { + entity: EntityBundle::new( + game_profile.uuid, + Vec3::default(), + azalea_registry::EntityKind::Player, + world_name, + ), + metadata: PlayerMetadataBundle::default(), + }; + // insert our components into the ecs :) + commands + .entity(player_entity) + .insert((MinecraftEntityId(p.player_id), player_bundle)); + } + + // send the client information that we have set + let client_information_packet: ClientInformation = + local_player.client_information.clone(); + log::debug!( + "Sending client information because login: {:?}", + client_information_packet + ); + local_player.write_packet(client_information_packet.get()); + + // brand + local_player.write_packet( + ServerboundCustomPayloadPacket { + identifier: ResourceLocation::new("brand").unwrap(), + // they don't have to know :) + data: "vanilla".into(), + } + .get(), + ); + + system_state.apply(ecs); + } + ClientboundGamePacket::SetChunkCacheRadius(p) => { + debug!("Got set chunk cache radius packet {:?}", p); + } + ClientboundGamePacket::CustomPayload(p) => { + debug!("Got custom payload packet {:?}", p); + } + ClientboundGamePacket::ChangeDifficulty(p) => { + debug!("Got difficulty packet {:?}", p); + } + ClientboundGamePacket::Commands(_p) => { + debug!("Got declare commands packet"); + } + ClientboundGamePacket::PlayerAbilities(p) => { + debug!("Got player abilities packet {:?}", p); + } + ClientboundGamePacket::SetCarriedItem(p) => { + debug!("Got set carried item packet {:?}", p); + } + ClientboundGamePacket::UpdateTags(_p) => { + debug!("Got update tags packet"); + } + ClientboundGamePacket::Disconnect(p) => { + debug!("Got disconnect packet {:?}", p); + let mut system_state: SystemState<Query<&LocalPlayer>> = SystemState::new(ecs); + let query = system_state.get(ecs); + let local_player = query.get(player_entity).unwrap(); + local_player.disconnect(); + } + ClientboundGamePacket::UpdateRecipes(_p) => { + debug!("Got update recipes packet"); + } + ClientboundGamePacket::EntityEvent(_p) => { + // debug!("Got entity event packet {:?}", p); + } + ClientboundGamePacket::Recipe(_p) => { + debug!("Got recipe packet"); + } + ClientboundGamePacket::PlayerPosition(p) => { + // TODO: reply with teleport confirm + debug!("Got player position packet {:?}", p); + + let mut system_state: SystemState< + Query<( + &mut LocalPlayer, + &mut Physics, + &mut Position, + &mut LastSentPosition, + )>, + > = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let Ok((mut local_player, mut physics, mut position, mut last_sent_position)) = + query.get_mut(player_entity) else { + continue; + }; + + let delta_movement = physics.delta; + + let is_x_relative = p.relative_arguments.x; + let is_y_relative = p.relative_arguments.y; + let is_z_relative = p.relative_arguments.z; + + let (delta_x, new_pos_x) = if is_x_relative { + last_sent_position.x += p.x; + (delta_movement.x, position.x + p.x) + } else { + last_sent_position.x = p.x; + (0.0, p.x) + }; + let (delta_y, new_pos_y) = if is_y_relative { + last_sent_position.y += p.y; + (delta_movement.y, position.y + p.y) + } else { + last_sent_position.y = p.y; + (0.0, p.y) + }; + let (delta_z, new_pos_z) = if is_z_relative { + last_sent_position.z += p.z; + (delta_movement.z, position.z + p.z) + } else { + last_sent_position.z = p.z; + (0.0, p.z) + }; + + let mut y_rot = p.y_rot; + let mut x_rot = p.x_rot; + if p.relative_arguments.x_rot { + x_rot += physics.x_rot; + } + if p.relative_arguments.y_rot { + y_rot += physics.y_rot; + } + + physics.delta = Vec3 { + x: delta_x, + y: delta_y, + z: delta_z, + }; + // we call a function instead of setting the fields ourself since the + // function makes sure the rotations stay in their + // ranges + set_rotation(&mut physics, y_rot, x_rot); + // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means + // so investigate that ig + let new_pos = Vec3 { + x: new_pos_x, + y: new_pos_y, + z: new_pos_z, + }; + + **position = new_pos; + + local_player + .write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()); + local_player.write_packet( + ServerboundMovePlayerPosRotPacket { + x: new_pos.x, + y: new_pos.y, + z: new_pos.z, + y_rot, + x_rot, + // this is always false + on_ground: false, + } + .get(), + ); + } + ClientboundGamePacket::PlayerInfoUpdate(p) => { + debug!("Got player info packet {:?}", p); + + let mut system_state: SystemState<( + Query<&mut LocalPlayer>, + EventWriter<AddPlayerEvent>, + EventWriter<UpdatePlayerEvent>, + )> = SystemState::new(ecs); + let (mut query, mut add_player_events, mut update_player_events) = + system_state.get_mut(ecs); + let mut local_player = query.get_mut(player_entity).unwrap(); + + for updated_info in &p.entries { + // add the new player maybe + if p.actions.add_player { + let info = PlayerInfo { + profile: updated_info.profile.clone(), + uuid: updated_info.profile.uuid, + gamemode: updated_info.game_mode, + latency: updated_info.latency, + display_name: updated_info.display_name.clone(), + }; + local_player + .players + .insert(updated_info.profile.uuid, info.clone()); + add_player_events.send(AddPlayerEvent { + entity: player_entity, + info: info.clone(), + }); + } else if let Some(info) = + local_player.players.get_mut(&updated_info.profile.uuid) + { + // `else if` because the block for add_player above + // already sets all the fields + if p.actions.update_game_mode { + info.gamemode = updated_info.game_mode; + } + if p.actions.update_latency { + info.latency = updated_info.latency; + } + if p.actions.update_display_name { + info.display_name = updated_info.display_name.clone(); + } + update_player_events.send(UpdatePlayerEvent { + entity: player_entity, + info: info.clone(), + }); + } else { + warn!( + "Ignoring PlayerInfoUpdate for unknown player {}", + updated_info.profile.uuid + ); + } + } + } + ClientboundGamePacket::PlayerInfoRemove(p) => { + let mut system_state: SystemState<( + Query<&mut LocalPlayer>, + EventWriter<RemovePlayerEvent>, + )> = SystemState::new(ecs); + let (mut query, mut remove_player_events) = system_state.get_mut(ecs); + let mut local_player = query.get_mut(player_entity).unwrap(); + + for uuid in &p.profile_ids { + if let Some(info) = local_player.players.remove(uuid) { + remove_player_events.send(RemovePlayerEvent { + entity: player_entity, + info, + }); + } + } + } + ClientboundGamePacket::SetChunkCacheCenter(p) => { + debug!("Got chunk cache center packet {:?}", p); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + let mut partial_world = local_player.partial_world.write(); + + partial_world.chunks.view_center = ChunkPos::new(p.x, p.z); + } + ClientboundGamePacket::LevelChunkWithLight(p) => { + debug!("Got chunk with light packet {} {}", p.x, p.z); + let pos = ChunkPos::new(p.x, p.z); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + // OPTIMIZATION: if we already know about the chunk from the + // shared world (and not ourselves), then we don't need to + // parse it again. This is only used when we have a shared + // world, since we check that the chunk isn't currently owned + // by this client. + let shared_chunk = local_player.world.read().chunks.get(&pos); + let this_client_has_chunk = local_player + .partial_world + .read() + .chunks + .limited_get(&pos) + .is_some(); + + let mut world = local_player.world.write(); + let mut partial_world = local_player.partial_world.write(); + + if !this_client_has_chunk { + if let Some(shared_chunk) = shared_chunk { + trace!( + "Skipping parsing chunk {:?} because we already know about it", + pos + ); + partial_world.chunks.set_with_shared_reference( + &pos, + Some(shared_chunk.clone()), + &mut world.chunks, + ); + continue; + } + } + + if let Err(e) = partial_world.chunks.replace_with_packet_data( + &pos, + &mut Cursor::new(&p.chunk_data.data), + &mut world.chunks, + ) { + error!("Couldn't set chunk data: {}", e); + } + } + ClientboundGamePacket::LightUpdate(_p) => { + // debug!("Got light update packet {:?}", p); + } + ClientboundGamePacket::AddEntity(p) => { + debug!("Got add entity packet {:?}", p); + + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + if let Some(world_name) = &local_player.world_name { + let bundle = p.as_entity_bundle(world_name.clone()); + let mut entity_commands = commands.spawn(( + MinecraftEntityId(p.id), + LoadedBy(HashSet::from([player_entity])), + bundle, + )); + // the bundle doesn't include the default entity metadata so we add that + // separately + p.apply_metadata(&mut entity_commands); + } else { + warn!("got add player packet but we haven't gotten a login packet yet"); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::SetEntityData(p) => { + debug!("Got set entity data packet {:?}", p); + + let mut system_state: SystemState<( + Commands, + Query<&mut LocalPlayer>, + Query<&EntityKind>, + )> = SystemState::new(ecs); + let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.id)); + drop(world); + + if let Some(entity) = entity { + let entity_kind = entity_kind_query.get(entity).unwrap(); + let mut entity_commands = commands.entity(entity); + if let Err(e) = apply_metadata( + &mut entity_commands, + **entity_kind, + (*p.packed_items).clone(), + ) { + warn!("{e}"); + } + } else { + warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::UpdateAttributes(_p) => { + // debug!("Got update attributes packet {:?}", p); + } + ClientboundGamePacket::SetEntityMotion(_p) => { + // debug!("Got entity velocity packet {:?}", p); + } + ClientboundGamePacket::SetEntityLink(p) => { + debug!("Got set entity link packet {:?}", p); + } + ClientboundGamePacket::AddPlayer(p) => { + debug!("Got add player packet {:?}", p); + + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + if let Some(world_name) = &local_player.world_name { + let bundle = p.as_player_bundle(world_name.clone()); + let mut spawned = commands.spawn(( + MinecraftEntityId(p.id), + LoadedBy(HashSet::from([player_entity])), + bundle, + )); + + if let Some(player_info) = local_player.players.get(&p.uuid) { + spawned.insert(GameProfileComponent(player_info.profile.clone())); + } + } else { + warn!("got add player packet but we haven't gotten a login packet yet"); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::InitializeBorder(p) => { + debug!("Got initialize border packet {:?}", p); + } + ClientboundGamePacket::SetTime(_p) => { + // debug!("Got set time packet {:?}", p); + } + ClientboundGamePacket::SetDefaultSpawnPosition(p) => { + debug!("Got set default spawn position packet {:?}", p); + } + ClientboundGamePacket::ContainerSetContent(p) => { + debug!("Got container set content packet {:?}", p); + } + ClientboundGamePacket::SetHealth(p) => { + debug!("Got set health packet {:?}", p); + + let mut system_state: SystemState<( + Query<&mut Health>, + EventWriter<DeathEvent>, + )> = SystemState::new(ecs); + let (mut query, mut death_events) = system_state.get_mut(ecs); + let mut health = query.get_mut(player_entity).unwrap(); + + if p.health == 0. && **health != 0. { + death_events.send(DeathEvent { + entity: player_entity, + packet: None, + }); + } + + **health = p.health; + + // the `Dead` component is added by the `update_dead` system + // in azalea-world and then the `dead_event` system fires + // the Death event. + } + ClientboundGamePacket::SetExperience(p) => { + debug!("Got set experience packet {:?}", p); + } + ClientboundGamePacket::TeleportEntity(p) => { + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.id)); + drop(world); + + if let Some(entity) = entity { + let new_position = p.position; + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity| { + let mut position = entity.get_mut::<Position>().unwrap(); + **position = new_position; + }), + }); + } else { + warn!("Got teleport entity packet for unknown entity id {}", p.id); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::UpdateAdvancements(p) => { + debug!("Got update advancements packet {:?}", p); + } + ClientboundGamePacket::RotateHead(_p) => { + // debug!("Got rotate head packet {:?}", p); + } + ClientboundGamePacket::MoveEntityPos(p) => { + let mut system_state: SystemState<(Commands, Query<&LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); + drop(world); + + if let Some(entity) = entity { + let delta = p.delta.clone(); + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity_mut| { + let mut position = entity_mut.get_mut::<Position>().unwrap(); + **position = position.with_delta(&delta); + }), + }); + } else { + warn!( + "Got move entity pos packet for unknown entity id {}", + p.entity_id + ); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::MoveEntityPosRot(p) => { + let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> = + SystemState::new(ecs); + let (mut commands, mut query) = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.read(); + let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id)); + drop(world); + + if let Some(entity) = entity { + let delta = p.delta.clone(); + commands.add(RelativeEntityUpdate { + entity, + partial_world: local_player.partial_world.clone(), + update: Box::new(move |entity_mut| { + let mut position = entity_mut.get_mut::<Position>().unwrap(); + **position = position.with_delta(&delta); + }), + }); + } else { + warn!( + "Got move entity pos rot packet for unknown entity id {}", + p.entity_id + ); + } + + system_state.apply(ecs); + } + + ClientboundGamePacket::MoveEntityRot(_p) => { + // debug!("Got move entity rot packet {:?}", p); + } + ClientboundGamePacket::KeepAlive(p) => { + debug!("Got keep alive packet {p:?} for {player_entity:?}"); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let mut local_player = query.get_mut(player_entity).unwrap(); + + local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get()); + debug!("Sent keep alive packet {p:?} for {player_entity:?}"); + } + ClientboundGamePacket::RemoveEntities(p) => { + debug!("Got remove entities packet {:?}", p); + } + ClientboundGamePacket::PlayerChat(p) => { + debug!("Got player chat packet {:?}", p); + + let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> = + SystemState::new(ecs); + let mut chat_events = system_state.get_mut(ecs); + + chat_events.send(ChatReceivedEvent { + entity: player_entity, + packet: ChatPacket::Player(Arc::new(p.clone())), + }); + } + ClientboundGamePacket::SystemChat(p) => { + debug!("Got system chat packet {:?}", p); + + let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> = + SystemState::new(ecs); + let mut chat_events = system_state.get_mut(ecs); + + chat_events.send(ChatReceivedEvent { + entity: player_entity, + packet: ChatPacket::System(Arc::new(p.clone())), + }); + } + ClientboundGamePacket::Sound(_p) => { + // debug!("Got sound packet {:?}", p); + } + ClientboundGamePacket::LevelEvent(p) => { + debug!("Got level event packet {:?}", p); + } + ClientboundGamePacket::BlockUpdate(p) => { + debug!("Got block update packet {:?}", p); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.write(); + + world.chunks.set_block_state(&p.pos, p.block_state); + } + ClientboundGamePacket::Animate(p) => { + debug!("Got animate packet {:?}", p); + } + ClientboundGamePacket::SectionBlocksUpdate(p) => { + debug!("Got section blocks update packet {:?}", p); + let mut system_state: SystemState<Query<&mut LocalPlayer>> = + SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + let local_player = query.get_mut(player_entity).unwrap(); + + let world = local_player.world.write(); + + for state in &p.states { + world + .chunks + .set_block_state(&(p.section_pos + state.pos.clone()), state.state); + } + } + ClientboundGamePacket::GameEvent(p) => { + debug!("Got game event packet {:?}", p); + } + ClientboundGamePacket::LevelParticles(p) => { + debug!("Got level particles packet {:?}", p); + } + ClientboundGamePacket::ServerData(p) => { + debug!("Got server data packet {:?}", p); + } + ClientboundGamePacket::SetEquipment(p) => { + debug!("Got set equipment packet {:?}", p); + } + ClientboundGamePacket::UpdateMobEffect(p) => { + debug!("Got update mob effect packet {:?}", p); + } + ClientboundGamePacket::AddExperienceOrb(_) => {} + ClientboundGamePacket::AwardStats(_) => {} + ClientboundGamePacket::BlockChangedAck(_) => {} + ClientboundGamePacket::BlockDestruction(_) => {} + ClientboundGamePacket::BlockEntityData(_) => {} + ClientboundGamePacket::BlockEvent(_) => {} + ClientboundGamePacket::BossEvent(_) => {} + ClientboundGamePacket::CommandSuggestions(_) => {} + ClientboundGamePacket::ContainerSetData(_) => {} + ClientboundGamePacket::ContainerSetSlot(_) => {} + ClientboundGamePacket::Cooldown(_) => {} + ClientboundGamePacket::CustomChatCompletions(_) => {} + ClientboundGamePacket::DeleteChat(_) => {} + ClientboundGamePacket::Explode(_) => {} + ClientboundGamePacket::ForgetLevelChunk(_) => {} + ClientboundGamePacket::HorseScreenOpen(_) => {} + ClientboundGamePacket::MapItemData(_) => {} + ClientboundGamePacket::MerchantOffers(_) => {} + ClientboundGamePacket::MoveVehicle(_) => {} + ClientboundGamePacket::OpenBook(_) => {} + ClientboundGamePacket::OpenScreen(_) => {} + ClientboundGamePacket::OpenSignEditor(_) => {} + ClientboundGamePacket::Ping(_) => {} + ClientboundGamePacket::PlaceGhostRecipe(_) => {} + ClientboundGamePacket::PlayerCombatEnd(_) => {} + ClientboundGamePacket::PlayerCombatEnter(_) => {} + ClientboundGamePacket::PlayerCombatKill(p) => { + debug!("Got player kill packet {:?}", p); + + #[allow(clippy::type_complexity)] + let mut system_state: SystemState<( + Commands, + Query<(&MinecraftEntityId, Option<&Dead>)>, + EventWriter<DeathEvent>, + )> = SystemState::new(ecs); + let (mut commands, mut query, mut death_events) = system_state.get_mut(ecs); + let (entity_id, dead) = query.get_mut(player_entity).unwrap(); + + if **entity_id == p.player_id && dead.is_none() { + commands.entity(player_entity).insert(Dead); + death_events.send(DeathEvent { + entity: player_entity, + packet: Some(p.clone()), + }); + } + + system_state.apply(ecs); + } + ClientboundGamePacket::PlayerLookAt(_) => {} + ClientboundGamePacket::RemoveMobEffect(_) => {} + ClientboundGamePacket::ResourcePack(_) => {} + ClientboundGamePacket::Respawn(p) => { + debug!("Got respawn packet {:?}", p); + + let mut system_state: SystemState<Commands> = SystemState::new(ecs); + let mut commands = system_state.get(ecs); + + // Remove the Dead marker component from the player. + commands.entity(player_entity).remove::<Dead>(); + + system_state.apply(ecs); + } + ClientboundGamePacket::SelectAdvancementsTab(_) => {} + ClientboundGamePacket::SetActionBarText(_) => {} + ClientboundGamePacket::SetBorderCenter(_) => {} + ClientboundGamePacket::SetBorderLerpSize(_) => {} + ClientboundGamePacket::SetBorderSize(_) => {} + ClientboundGamePacket::SetBorderWarningDelay(_) => {} + ClientboundGamePacket::SetBorderWarningDistance(_) => {} + ClientboundGamePacket::SetCamera(_) => {} + ClientboundGamePacket::SetDisplayObjective(_) => {} + ClientboundGamePacket::SetObjective(_) => {} + ClientboundGamePacket::SetPassengers(_) => {} + ClientboundGamePacket::SetPlayerTeam(_) => {} + ClientboundGamePacket::SetScore(_) => {} + ClientboundGamePacket::SetSimulationDistance(_) => {} + ClientboundGamePacket::SetSubtitleText(_) => {} + ClientboundGamePacket::SetTitleText(_) => {} + ClientboundGamePacket::SetTitlesAnimation(_) => {} + ClientboundGamePacket::SoundEntity(_) => {} + ClientboundGamePacket::StopSound(_) => {} + ClientboundGamePacket::TabList(_) => {} + ClientboundGamePacket::TagQuery(_) => {} + ClientboundGamePacket::TakeItemEntity(_) => {} + ClientboundGamePacket::DisguisedChat(_) => {} + ClientboundGamePacket::UpdateEnabledFeatures(_) => {} + ClientboundGamePacket::ContainerClose(_) => {} + } + } + } +} + +impl PacketReceiver { + /// Loop that reads from the connection and adds the packets to the queue + + /// runs the schedule. + pub async fn read_task(self, mut read_conn: ReadConnection<ClientboundGamePacket>) { + while let Ok(packet) = read_conn.read().await { + self.packets.lock().push(packet); + // tell the client to run all the systems + self.run_schedule_sender.send(()).await.unwrap(); + } + } + + /// Consume the [`ServerboundGamePacket`] queue and actually write the + /// packets to the server. It's like this so writing packets doesn't need to + /// be awaited. + pub async fn write_task( + self, + mut write_conn: WriteConnection<ServerboundGamePacket>, + mut write_receiver: mpsc::UnboundedReceiver<ServerboundGamePacket>, + ) { + while let Some(packet) = write_receiver.recv().await { + if let Err(err) = write_conn.write(packet).await { + error!("Disconnecting because we couldn't write a packet: {err}."); + break; + }; + } + // receiver is automatically closed when it's dropped + } +} diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index 650fb58c..00b6b25e 100755 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -1,13 +1,14 @@ use azalea_auth::game_profile::GameProfile; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_core::GameType; -use azalea_world::PartialWorld; +use azalea_ecs::{ + event::EventReader, + system::{Commands, Res}, +}; +use azalea_world::EntityInfos; use uuid::Uuid; -/// Something that has a world associated to it. this is usually a `Client`. -pub trait WorldHaver { - fn world(&self) -> &PartialWorld; -} +use crate::{packet_handling::AddPlayerEvent, GameProfileComponent}; /// A player in the tab list. #[derive(Debug, Clone)] @@ -18,5 +19,22 @@ pub struct PlayerInfo { pub gamemode: GameType, pub latency: i32, /// The player's display name in the tab list. - pub display_name: Option<Component>, + pub display_name: Option<FormattedText>, +} + +/// Add a [`GameProfileComponent`] when an [`AddPlayerEvent`] is received. +/// Usually the `GameProfileComponent` will be added from the +/// `ClientboundGamePacket::AddPlayer` handler though. +pub fn retroactively_add_game_profile_component( + mut commands: Commands, + mut events: EventReader<AddPlayerEvent>, + entity_infos: Res<EntityInfos>, +) { + for event in events.iter() { + if let Some(entity) = entity_infos.get_entity_by_uuid(&event.info.uuid) { + commands + .entity(entity) + .insert(GameProfileComponent(event.info.profile.clone())); + } + } } diff --git a/azalea-client/src/plugins.rs b/azalea-client/src/plugins.rs deleted file mode 100644 index 93641906..00000000 --- a/azalea-client/src/plugins.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::{Client, Event}; -use async_trait::async_trait; -use nohash_hasher::NoHashHasher; -use std::{ - any::{Any, TypeId}, - collections::HashMap, - hash::BuildHasherDefault, -}; - -type U64Hasher = BuildHasherDefault<NoHashHasher<u64>>; - -// kind of based on https://docs.rs/http/latest/src/http/extensions.rs.html -#[derive(Clone, Default)] -pub struct PluginStates { - map: Option<HashMap<TypeId, Box<dyn PluginState>, U64Hasher>>, -} - -/// A map of PluginState TypeIds to AnyPlugin objects. This can then be built -/// into a [`PluginStates`] object to get a fresh new state based on this -/// plugin. -/// -/// If you're using the azalea crate, you should generate this from the -/// `plugins!` macro. -#[derive(Clone, Default)] -pub struct Plugins { - map: Option<HashMap<TypeId, Box<dyn AnyPlugin>, U64Hasher>>, -} - -impl PluginStates { - pub fn get<T: PluginState>(&self) -> Option<&T> { - self.map - .as_ref() - .and_then(|map| map.get(&TypeId::of::<T>())) - .and_then(|boxed| (boxed.as_ref() as &dyn Any).downcast_ref::<T>()) - } -} - -impl Plugins { - /// Create a new empty set of plugins. - pub fn new() -> Self { - Self::default() - } - - /// Add a new plugin to this set. - pub fn add<T: Plugin + Clone>(&mut self, plugin: T) { - if self.map.is_none() { - self.map = Some(HashMap::with_hasher(BuildHasherDefault::default())); - } - self.map - .as_mut() - .unwrap() - .insert(TypeId::of::<T::State>(), Box::new(plugin)); - } - - /// Build our plugin states from this set of plugins. Note that if you're - /// using `azalea` you'll probably never need to use this as it's called - /// for you. - pub fn build(self) -> PluginStates { - let mut map = HashMap::with_hasher(BuildHasherDefault::default()); - for (id, plugin) in self.map.unwrap().into_iter() { - map.insert(id, plugin.build()); - } - PluginStates { map: Some(map) } - } -} - -impl IntoIterator for PluginStates { - type Item = Box<dyn PluginState>; - type IntoIter = std::vec::IntoIter<Self::Item>; - - /// Iterate over the plugin states. - fn into_iter(self) -> Self::IntoIter { - self.map - .map(|map| map.into_values().collect::<Vec<_>>()) - .unwrap_or_default() - .into_iter() - } -} - -/// A `PluginState` keeps the current state of a plugin for a client. All the -/// fields must be atomic. Unique `PluginState`s are built from [`Plugin`]s. -#[async_trait] -pub trait PluginState: Send + Sync + PluginStateClone + Any + 'static { - async fn handle(self: Box<Self>, event: Event, bot: Client); -} - -/// Plugins can keep their own personal state, listen to [`Event`]s, and add -/// new functions to [`Client`]. -pub trait Plugin: Send + Sync + Any + 'static { - type State: PluginState; - - fn build(&self) -> Self::State; -} - -/// AnyPlugin is basically a Plugin but without the State associated type -/// it has to exist so we can do a hashmap with Box<dyn AnyPlugin> -#[doc(hidden)] -pub trait AnyPlugin: Send + Sync + Any + AnyPluginClone + 'static { - fn build(&self) -> Box<dyn PluginState>; -} - -impl<S: PluginState, B: Plugin<State = S> + Clone> AnyPlugin for B { - fn build(&self) -> Box<dyn PluginState> { - Box::new(self.build()) - } -} - -/// An internal trait that allows PluginState to be cloned. -#[doc(hidden)] -pub trait PluginStateClone { - fn clone_box(&self) -> Box<dyn PluginState>; -} -impl<T> PluginStateClone for T -where - T: 'static + PluginState + Clone, -{ - fn clone_box(&self) -> Box<dyn PluginState> { - Box::new(self.clone()) - } -} -impl Clone for Box<dyn PluginState> { - fn clone(&self) -> Self { - self.clone_box() - } -} - -/// An internal trait that allows AnyPlugin to be cloned. -#[doc(hidden)] -pub trait AnyPluginClone { - fn clone_box(&self) -> Box<dyn AnyPlugin>; -} -impl<T> AnyPluginClone for T -where - T: 'static + Plugin + Clone, -{ - fn clone_box(&self) -> Box<dyn AnyPlugin> { - Box::new(self.clone()) - } -} -impl Clone for Box<dyn AnyPlugin> { - fn clone(&self) -> Self { - self.clone_box() - } -} diff --git a/azalea-client/src/task_pool.rs b/azalea-client/src/task_pool.rs new file mode 100644 index 00000000..2a3afbbc --- /dev/null +++ b/azalea-client/src/task_pool.rs @@ -0,0 +1,177 @@ +//! Borrowed from `bevy_core`. + +use azalea_ecs::{ + app::{App, Plugin}, + schedule::IntoSystemDescriptor, + system::Resource, +}; +use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder}; + +/// Setup of default task pools: `AsyncComputeTaskPool`, `ComputeTaskPool`, +/// `IoTaskPool`. +#[derive(Default)] +pub struct TaskPoolPlugin { + /// Options for the [`TaskPool`](bevy_tasks::TaskPool) created at + /// application start. + pub task_pool_options: TaskPoolOptions, +} + +impl Plugin for TaskPoolPlugin { + fn build(&self, app: &mut App) { + // Setup the default bevy task pools + self.task_pool_options.create_default_pools(); + + #[cfg(not(target_arch = "wasm32"))] + app.add_system_to_stage( + azalea_ecs::app::CoreStage::Last, + bevy_tasks::tick_global_task_pools_on_main_thread.at_end(), + ); + } +} + +/// Helper for configuring and creating the default task pools. For end-users +/// who want full control, set up [`TaskPoolPlugin`](super::TaskPoolPlugin) +#[derive(Clone, Resource)] +pub struct TaskPoolOptions { + /// If the number of physical cores is less than min_total_threads, force + /// using min_total_threads + pub min_total_threads: usize, + /// If the number of physical cores is greater than max_total_threads, force + /// using max_total_threads + pub max_total_threads: usize, + + /// Used to determine number of IO threads to allocate + pub io: TaskPoolThreadAssignmentPolicy, + /// Used to determine number of async compute threads to allocate + pub async_compute: TaskPoolThreadAssignmentPolicy, + /// Used to determine number of compute threads to allocate + pub compute: TaskPoolThreadAssignmentPolicy, +} + +impl Default for TaskPoolOptions { + fn default() -> Self { + TaskPoolOptions { + // By default, use however many cores are available on the system + min_total_threads: 1, + max_total_threads: std::usize::MAX, + + // Use 25% of cores for IO, at least 1, no more than 4 + io: TaskPoolThreadAssignmentPolicy { + min_threads: 1, + max_threads: 4, + percent: 0.25, + }, + + // Use 25% of cores for async compute, at least 1, no more than 4 + async_compute: TaskPoolThreadAssignmentPolicy { + min_threads: 1, + max_threads: 4, + percent: 0.25, + }, + + // Use all remaining cores for compute (at least 1) + compute: TaskPoolThreadAssignmentPolicy { + min_threads: 1, + max_threads: std::usize::MAX, + percent: 1.0, // This 1.0 here means "whatever is left over" + }, + } + } +} + +impl TaskPoolOptions { + // /// Create a configuration that forces using the given number of threads. + // pub fn with_num_threads(thread_count: usize) -> Self { + // TaskPoolOptions { + // min_total_threads: thread_count, + // max_total_threads: thread_count, + // ..Default::default() + // } + // } + + /// Inserts the default thread pools into the given resource map based on + /// the configured values + pub fn create_default_pools(&self) { + let total_threads = bevy_tasks::available_parallelism() + .clamp(self.min_total_threads, self.max_total_threads); + + let mut remaining_threads = total_threads; + + { + // Determine the number of IO threads we will use + let io_threads = self + .io + .get_number_of_threads(remaining_threads, total_threads); + + remaining_threads = remaining_threads.saturating_sub(io_threads); + + IoTaskPool::init(|| { + TaskPoolBuilder::default() + .num_threads(io_threads) + .thread_name("IO Task Pool".to_string()) + .build() + }); + } + + { + // Determine the number of async compute threads we will use + let async_compute_threads = self + .async_compute + .get_number_of_threads(remaining_threads, total_threads); + + remaining_threads = remaining_threads.saturating_sub(async_compute_threads); + + AsyncComputeTaskPool::init(|| { + TaskPoolBuilder::default() + .num_threads(async_compute_threads) + .thread_name("Async Compute Task Pool".to_string()) + .build() + }); + } + + { + // Determine the number of compute threads we will use + // This is intentionally last so that an end user can specify 1.0 as the percent + let compute_threads = self + .compute + .get_number_of_threads(remaining_threads, total_threads); + + ComputeTaskPool::init(|| { + TaskPoolBuilder::default() + .num_threads(compute_threads) + .thread_name("Compute Task Pool".to_string()) + .build() + }); + } + } +} + +/// Defines a simple way to determine how many threads to use given the number +/// of remaining cores and number of total cores +#[derive(Clone)] +pub struct TaskPoolThreadAssignmentPolicy { + /// Force using at least this many threads + pub min_threads: usize, + /// Under no circumstance use more than this many threads for this pool + pub max_threads: usize, + /// Target using this percentage of total cores, clamped by min_threads and + /// max_threads. It is permitted to use 1.0 to try to use all remaining + /// threads + pub percent: f32, +} + +impl TaskPoolThreadAssignmentPolicy { + /// Determine the number of threads to use for this task pool + fn get_number_of_threads(&self, remaining_threads: usize, total_threads: usize) -> usize { + assert!(self.percent >= 0.0); + let mut desired = (total_threads as f32 * self.percent).round() as usize; + + // Limit ourselves to the number of cores available + desired = desired.min(remaining_threads); + + // Clamp by min_threads, max_threads. (This may result in us using more threads + // than are available, this is intended. An example case where this + // might happen is a device with <= 2 threads. + desired.clamp(self.min_threads, self.max_threads) + } +} diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index 9addac59..e102594d 100755 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -3,13 +3,17 @@ description = "Miscellaneous things in Azalea." edition = "2021" license = "MIT" name = "azalea-core" -version = "0.5.0" repository = "https://github.com/mat-1/azalea/tree/main/azalea-core" +version = "0.5.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-buf = {path = "../azalea-buf", version = "^0.5.0" } -azalea-chat = {path = "../azalea-chat", version = "^0.5.0" } -azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0" } +azalea-buf = {path = "../azalea-buf", version = "^0.5.0"} +azalea-chat = {path = "../azalea-chat", version = "^0.5.0"} +azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"} +bevy_ecs = {version = "0.9.1", default-features = false, optional = true} uuid = "^1.1.2" + +[features] +bevy_ecs = ["dep:bevy_ecs"] diff --git a/azalea-core/src/delta.rs b/azalea-core/src/delta.rs index 4f730d14..ce49ab50 100755 --- a/azalea-core/src/delta.rs +++ b/azalea-core/src/delta.rs @@ -39,6 +39,7 @@ impl PositionDeltaTrait for PositionDelta8 { } impl Vec3 { + #[must_use] pub fn with_delta(&self, delta: &dyn PositionDeltaTrait) -> Vec3 { Vec3 { x: self.x + delta.x(), diff --git a/azalea-core/src/particle/mod.rs b/azalea-core/src/particle/mod.rs index 44c7c2f5..8dc9f8c6 100755 --- a/azalea-core/src/particle/mod.rs +++ b/azalea-core/src/particle/mod.rs @@ -1,6 +1,7 @@ use crate::{BlockPos, Slot}; use azalea_buf::McBuf; +#[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::component::Component))] #[derive(Debug, Clone, McBuf, Default)] pub struct Particle { #[var] diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index ea54c60c..d04f58cc 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -1,5 +1,5 @@ use crate::ResourceLocation; -use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; +use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; use std::{ io::{Cursor, Write}, ops::{Add, AddAssign, Mul, Rem, Sub}, @@ -109,7 +109,7 @@ macro_rules! vec3_impl { }; } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, McBuf)] pub struct Vec3 { pub x: f64, pub y: f64, @@ -270,12 +270,22 @@ impl From<&Vec3> for BlockPos { } } } +impl From<Vec3> for BlockPos { + fn from(pos: Vec3) -> Self { + BlockPos::from(&pos) + } +} impl From<&Vec3> for ChunkPos { fn from(pos: &Vec3) -> Self { ChunkPos::from(&BlockPos::from(pos)) } } +impl From<Vec3> for ChunkPos { + fn from(pos: Vec3) -> Self { + ChunkPos::from(&pos) + } +} 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/resource_location.rs b/azalea-core/src/resource_location.rs index e810b96e..4e25d00e 100755 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -3,6 +3,9 @@ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use std::io::{Cursor, Write}; +// TODO: make a `resourcelocation!("minecraft:overwolrd")` macro that checks if +// it's correct at compile-time. + #[derive(Hash, Clone, PartialEq, Eq)] pub struct ResourceLocation { pub namespace: String, diff --git a/azalea-ecs/Cargo.toml b/azalea-ecs/Cargo.toml new file mode 100644 index 00000000..30c9676b --- /dev/null +++ b/azalea-ecs/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "azalea-ecs" +version = "0.5.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +azalea-ecs-macros = {path = "./azalea-ecs-macros", version = "^0.5.0"} +bevy_app = "0.9.1" +bevy_ecs = {version = "0.9.1", default-features = false} +iyes_loopless = "0.9.1" +tokio = {version = "1.25.0", features = ["time"]} diff --git a/azalea-ecs/azalea-ecs-macros/Cargo.toml b/azalea-ecs/azalea-ecs-macros/Cargo.toml new file mode 100755 index 00000000..2301f2f1 --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +description = "Azalea ECS Macros" +edition = "2021" +license = "MIT OR Apache-2.0" +name = "azalea-ecs-macros" +version = "0.5.0" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "1.0" +toml = "0.7.0" diff --git a/azalea-ecs/azalea-ecs-macros/src/component.rs b/azalea-ecs/azalea-ecs-macros/src/component.rs new file mode 100644 index 00000000..306b64de --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/component.rs @@ -0,0 +1,125 @@ +use crate::utils::{get_lit_str, Symbol}; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result}; + +use crate::utils; + +pub fn derive_resource(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let azalea_ecs_path: Path = crate::azalea_ecs_path(); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #azalea_ecs_path::system::BevyResource for #struct_name #type_generics #where_clause { + } + }) +} + +pub fn derive_component(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let azalea_ecs_path: Path = crate::azalea_ecs_path(); + + let attrs = match parse_component_attr(&ast) { + Ok(attrs) => attrs, + Err(e) => return e.into_compile_error().into(), + }; + + let storage = storage_path(&azalea_ecs_path, attrs.storage); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #azalea_ecs_path::component::BevyComponent for #struct_name #type_generics #where_clause { + type Storage = #storage; + } + }) +} + +pub const COMPONENT: Symbol = Symbol("component"); +pub const STORAGE: Symbol = Symbol("storage"); + +struct Attrs { + storage: StorageTy, +} + +#[derive(Clone, Copy)] +enum StorageTy { + Table, + SparseSet, +} + +// values for `storage` attribute +const TABLE: &str = "Table"; +const SPARSE_SET: &str = "SparseSet"; + +fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> { + let meta_items = utils::parse_attrs(ast, COMPONENT)?; + + let mut attrs = Attrs { + storage: StorageTy::Table, + }; + + for meta in meta_items { + use syn::{ + Meta::NameValue, + NestedMeta::{Lit, Meta}, + }; + match meta { + Meta(NameValue(m)) if m.path == STORAGE => { + attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() { + TABLE => StorageTy::Table, + SPARSE_SET => StorageTy::SparseSet, + s => { + return Err(Error::new_spanned( + m.lit, + format!( + "Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'." + ), + )) + } + }; + } + Meta(meta_item) => { + return Err(Error::new_spanned( + meta_item.path(), + format!( + "unknown component attribute `{}`", + meta_item.path().into_token_stream() + ), + )); + } + Lit(lit) => { + return Err(Error::new_spanned( + lit, + "unexpected literal in component attribute", + )) + } + } + } + + Ok(attrs) +} + +fn storage_path(azalea_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { + let typename = match ty { + StorageTy::Table => Ident::new("TableStorage", Span::call_site()), + StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()), + }; + + quote! { #azalea_ecs_path::component::#typename } +} diff --git a/azalea-ecs/azalea-ecs-macros/src/fetch.rs b/azalea-ecs/azalea-ecs-macros/src/fetch.rs new file mode 100644 index 00000000..8a6b93ba --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/fetch.rs @@ -0,0 +1,466 @@ +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span}; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + punctuated::Punctuated, + Attribute, Data, DataStruct, DeriveInput, Field, Fields, +}; + +use crate::azalea_ecs_path; + +#[derive(Default)] +struct FetchStructAttributes { + pub is_mutable: bool, + pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>, +} + +static MUTABLE_ATTRIBUTE_NAME: &str = "mutable"; +static DERIVE_ATTRIBUTE_NAME: &str = "derive"; + +mod field_attr_keywords { + syn::custom_keyword!(ignore); +} + +pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query"; + +pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { + let visibility = ast.vis; + + let mut fetch_struct_attributes = FetchStructAttributes::default(); + for attr in &ast.attrs { + if !attr + .path + .get_ident() + .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) + { + continue; + } + + attr.parse_args_with(|input: ParseStream| { + let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?; + for meta in meta { + let ident = meta.path().get_ident().unwrap_or_else(|| { + panic!( + "Unrecognized attribute: `{}`", + meta.path().to_token_stream() + ) + }); + if ident == MUTABLE_ATTRIBUTE_NAME { + if let syn::Meta::Path(_) = meta { + fetch_struct_attributes.is_mutable = true; + } else { + panic!( + "The `{MUTABLE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments" + ); + } + } else if ident == DERIVE_ATTRIBUTE_NAME { + if let syn::Meta::List(meta_list) = meta { + fetch_struct_attributes + .derive_args + .extend(meta_list.nested.iter().cloned()); + } else { + panic!( + "Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute" + ); + } + } else { + panic!( + "Unrecognized attribute: `{}`", + meta.path().to_token_stream() + ); + } + } + Ok(()) + }) + .unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format")); + } + + let path = azalea_ecs_path(); + + let user_generics = ast.generics.clone(); + let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl(); + let user_generics_with_world = { + let mut generics = ast.generics.clone(); + generics.params.insert(0, parse_quote!('__w)); + generics + }; + let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = + user_generics_with_world.split_for_impl(); + + let struct_name = ast.ident.clone(); + let read_only_struct_name = if fetch_struct_attributes.is_mutable { + Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site()) + } else { + struct_name.clone() + }; + + let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site()); + let read_only_item_struct_name = if fetch_struct_attributes.is_mutable { + Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site()) + } else { + item_struct_name.clone() + }; + + let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site()); + let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable { + Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()) + } else { + fetch_struct_name.clone() + }; + + let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site()); + + let fields = match &ast.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => &fields.named, + _ => panic!("Expected a struct with named fields"), + }; + + let mut ignored_field_attrs = Vec::new(); + let mut ignored_field_visibilities = Vec::new(); + let mut ignored_field_idents = Vec::new(); + let mut ignored_field_types = Vec::new(); + let mut field_attrs = Vec::new(); + let mut field_visibilities = Vec::new(); + let mut field_idents = Vec::new(); + let mut field_types = Vec::new(); + let mut read_only_field_types = Vec::new(); + + for field in fields { + let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field); + + let field_ident = field.ident.as_ref().unwrap().clone(); + if is_ignored { + ignored_field_attrs.push(attrs); + ignored_field_visibilities.push(field.vis.clone()); + ignored_field_idents.push(field_ident.clone()); + ignored_field_types.push(field.ty.clone()); + } else { + field_attrs.push(attrs); + field_visibilities.push(field.vis.clone()); + field_idents.push(field_ident.clone()); + let field_ty = field.ty.clone(); + field_types.push(quote!(#field_ty)); + read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly)); + } + } + + let derive_args = &fetch_struct_attributes.derive_args; + // `#[derive()]` is valid syntax + let derive_macro_call = quote! { #[derive(#derive_args)] }; + + let impl_fetch = |is_readonly: bool| { + let struct_name = if is_readonly { + &read_only_struct_name + } else { + &struct_name + }; + let item_struct_name = if is_readonly { + &read_only_item_struct_name + } else { + &item_struct_name + }; + let fetch_struct_name = if is_readonly { + &read_only_fetch_struct_name + } else { + &fetch_struct_name + }; + + let field_types = if is_readonly { + &read_only_field_types + } else { + &field_types + }; + + quote! { + #derive_macro_call + #[doc = "Automatically generated [`WorldQuery`] item type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], returned when iterating over query results."] + #[automatically_derived] + #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { + #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* + } + + #[doc(hidden)] + #[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], used to define the world data accessed by this query."] + #[automatically_derived] + #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { + #(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field + unsafe impl #user_impl_generics #path::query::WorldQuery + for #struct_name #user_ty_generics #user_where_clauses { + + type Item<'__w> = #item_struct_name #user_ty_generics_with_world; + type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world; + type ReadOnly = #read_only_struct_name #user_ty_generics; + type State = #state_struct_name #user_ty_generics; + + fn shrink<'__wlong: '__wshort, '__wshort>( + item: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wlong> + ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wshort> { + #item_struct_name { + #( + #field_idents: <#field_types>::shrink(item.#field_idents), + )* + #( + #ignored_field_idents: item.#ignored_field_idents, + )* + } + } + + unsafe fn init_fetch<'__w>( + _world: &'__w #path::world::World, + state: &Self::State, + _last_change_tick: u32, + _change_tick: u32 + ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { + #fetch_struct_name { + #(#field_idents: + <#field_types>::init_fetch( + _world, + &state.#field_idents, + _last_change_tick, + _change_tick + ), + )* + #(#ignored_field_idents: Default::default(),)* + } + } + + unsafe fn clone_fetch<'__w>( + _fetch: &<Self as #path::query::WorldQuery>::Fetch<'__w> + ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { + #fetch_struct_name { + #( + #field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents), + )* + #( + #ignored_field_idents: Default::default(), + )* + } + } + + const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*; + + const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; + + /// SAFETY: we call `set_archetype` for each member that implements `Fetch` + #[inline] + unsafe fn set_archetype<'__w>( + _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, + _state: &Self::State, + _archetype: &'__w #path::archetype::Archetype, + _table: &'__w #path::storage::Table + ) { + #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)* + } + + /// SAFETY: we call `set_table` for each member that implements `Fetch` + #[inline] + unsafe fn set_table<'__w>( + _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, + _state: &Self::State, + _table: &'__w #path::storage::Table + ) { + #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* + } + + /// SAFETY: we call `fetch` for each member that implements `Fetch`. + #[inline(always)] + unsafe fn fetch<'__w>( + _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, + _entity: Entity, + _table_row: usize + ) -> <Self as #path::query::WorldQuery>::Item<'__w> { + Self::Item { + #(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* + #(#ignored_field_idents: Default::default(),)* + } + } + + #[allow(unused_variables)] + #[inline(always)] + unsafe fn filter_fetch<'__w>( + _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, + _entity: Entity, + _table_row: usize + ) -> bool { + true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* + } + + fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { + #( <#field_types>::update_component_access(&state.#field_idents, _access); )* + } + + fn update_archetype_component_access( + state: &Self::State, + _archetype: &#path::archetype::Archetype, + _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> + ) { + #( + <#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access); + )* + } + + fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { + #state_struct_name { + #(#field_idents: <#field_types>::init_state(world),)* + #(#ignored_field_idents: Default::default(),)* + } + } + + fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { + true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* + } + } + } + }; + + let mutable_impl = impl_fetch(false); + let readonly_impl = if fetch_struct_attributes.is_mutable { + let world_query_impl = impl_fetch(true); + quote! { + #[doc(hidden)] + #[doc = "Automatically generated internal [`WorldQuery`] type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], used for read-only access."] + #[automatically_derived] + #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { + #( #field_idents: #read_only_field_types, )* + #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* + } + + #world_query_impl + } + } else { + quote! {} + }; + + let read_only_asserts = if fetch_struct_attributes.is_mutable { + quote! { + // Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only. + // This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery` + // but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway + #( assert_readonly::<#read_only_field_types>(); )* + } + } else { + quote! { + // Statically checks that the safety guarantee of `ReadOnlyWorldQuery` for `$fetch_struct_name` actually holds true. + // We need this to make sure that we don't compile `ReadOnlyWorldQuery` if our struct contains nested `WorldQuery` + // members that don't implement it. I.e.: + // ``` + // #[derive(WorldQuery)] + // pub struct Foo { a: &'static mut MyComponent } + // ``` + #( assert_readonly::<#field_types>(); )* + } + }; + + TokenStream::from(quote! { + #mutable_impl + + #readonly_impl + + #[doc(hidden)] + #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] + #[doc = stringify!(#struct_name)] + #[doc = "`], used for caching."] + #[automatically_derived] + #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { + #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* + #(#ignored_field_idents: #ignored_field_types,)* + } + + /// SAFETY: we assert fields are readonly below + unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery + for #read_only_struct_name #user_ty_generics #user_where_clauses {} + + #[allow(dead_code)] + const _: () = { + fn assert_readonly<T>() + where + T: #path::query::ReadOnlyWorldQuery, + { + } + + // We generate a readonly assertion for every struct member. + fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world { + #read_only_asserts + } + }; + + // The original struct will most likely be left unused. As we don't want our users having + // to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed + // workaround. + #[allow(dead_code)] + const _: () = { + fn dead_code_workaround #user_impl_generics ( + q: #struct_name #user_ty_generics, + q2: #read_only_struct_name #user_ty_generics + ) #user_where_clauses { + #(q.#field_idents;)* + #(q.#ignored_field_idents;)* + #(q2.#field_idents;)* + #(q2.#ignored_field_idents;)* + + } + }; + }) +} + +struct WorldQueryFieldInfo { + /// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute. + is_ignored: bool, + /// All field attributes except for `world_query` ones. + attrs: Vec<Attribute>, +} + +fn read_world_query_field_info(field: &Field) -> WorldQueryFieldInfo { + let is_ignored = field + .attrs + .iter() + .find(|attr| { + attr.path + .get_ident() + .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) + }) + .map_or(false, |attr| { + let mut is_ignored = false; + attr.parse_args_with(|input: ParseStream| { + if input + .parse::<Option<field_attr_keywords::ignore>>()? + .is_some() + { + is_ignored = true; + } + Ok(()) + }) + .unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format")); + + is_ignored + }); + + let attrs = field + .attrs + .iter() + .filter(|attr| { + attr.path + .get_ident() + .map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME) + }) + .cloned() + .collect(); + + WorldQueryFieldInfo { is_ignored, attrs } +} diff --git a/azalea-ecs/azalea-ecs-macros/src/lib.rs b/azalea-ecs/azalea-ecs-macros/src/lib.rs new file mode 100755 index 00000000..09ccb094 --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/lib.rs @@ -0,0 +1,523 @@ +//! A fork of bevy_ecs_macros that uses azalea_ecs instead of bevy_ecs. + +extern crate proc_macro; + +mod component; +mod fetch; +pub(crate) mod utils; + +use crate::fetch::derive_world_query_impl; +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + spanned::Spanned, + token::Comma, + DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result, + Token, TypeParam, +}; +use utils::{derive_label, get_named_struct_fields, BevyManifest}; + +struct AllTuples { + macro_ident: Ident, + start: usize, + end: usize, + idents: Vec<Ident>, +} + +impl Parse for AllTuples { + fn parse(input: ParseStream) -> Result<Self> { + let macro_ident = input.parse::<Ident>()?; + input.parse::<Comma>()?; + let start = input.parse::<LitInt>()?.base10_parse()?; + input.parse::<Comma>()?; + let end = input.parse::<LitInt>()?.base10_parse()?; + input.parse::<Comma>()?; + let mut idents = vec![input.parse::<Ident>()?]; + while input.parse::<Comma>().is_ok() { + idents.push(input.parse::<Ident>()?); + } + + Ok(AllTuples { + macro_ident, + start, + end, + idents, + }) + } +} + +#[proc_macro] +pub fn all_tuples(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as AllTuples); + let len = input.end - input.start; + let mut ident_tuples = Vec::with_capacity(len); + for i in input.start..=input.end { + let idents = input + .idents + .iter() + .map(|ident| format_ident!("{}{}", ident, i)); + if input.idents.len() < 2 { + ident_tuples.push(quote! { + #(#idents)* + }); + } else { + ident_tuples.push(quote! { + (#(#idents),*) + }); + } + } + + let macro_ident = &input.macro_ident; + let invocations = (input.start..=input.end).map(|i| { + let ident_tuples = &ident_tuples[..i]; + quote! { + #macro_ident!(#(#ident_tuples),*); + } + }); + TokenStream::from(quote! { + #( + #invocations + )* + }) +} + +enum BundleFieldKind { + Component, + Ignore, +} + +const BUNDLE_ATTRIBUTE_NAME: &str = "bundle"; +const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore"; + +#[proc_macro_derive(Bundle, attributes(bundle))] +pub fn derive_bundle(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let ecs_path = azalea_ecs_path(); + + let named_fields = match get_named_struct_fields(&ast.data) { + Ok(fields) => &fields.named, + Err(e) => return e.into_compile_error().into(), + }; + + let mut field_kind = Vec::with_capacity(named_fields.len()); + + 'field_loop: for field in named_fields.iter() { + for attr in &field.attrs { + if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) { + if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() { + if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() { + if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) { + field_kind.push(BundleFieldKind::Ignore); + continue 'field_loop; + } + + return syn::Error::new( + path.span(), + format!( + "Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`" + ), + ) + .into_compile_error() + .into(); + } + + return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into(); + } + } + } + + field_kind.push(BundleFieldKind::Component); + } + + let field = named_fields + .iter() + .map(|field| field.ident.as_ref().unwrap()) + .collect::<Vec<_>>(); + let field_type = named_fields + .iter() + .map(|field| &field.ty) + .collect::<Vec<_>>(); + + let mut field_component_ids = Vec::new(); + let mut field_get_components = Vec::new(); + let mut field_from_components = Vec::new(); + for ((field_type, field_kind), field) in + field_type.iter().zip(field_kind.iter()).zip(field.iter()) + { + match field_kind { + BundleFieldKind::Component => { + field_component_ids.push(quote! { + <#field_type as #ecs_path::bundle::BevyBundle>::component_ids(components, storages, &mut *ids); + }); + field_get_components.push(quote! { + self.#field.get_components(&mut *func); + }); + field_from_components.push(quote! { + #field: <#field_type as #ecs_path::bundle::BevyBundle>::from_components(ctx, &mut *func), + }); + } + + BundleFieldKind::Ignore => { + field_from_components.push(quote! { + #field: ::std::default::Default::default(), + }); + } + } + } + let generics = ast.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let struct_name = &ast.ident; + + TokenStream::from(quote! { + /// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order + unsafe impl #impl_generics #ecs_path::bundle::BevyBundle for #struct_name #ty_generics #where_clause { + fn component_ids( + components: &mut #ecs_path::component::Components, + storages: &mut #ecs_path::storage::Storages, + ids: &mut impl FnMut(#ecs_path::component::ComponentId) + ){ + #(#field_component_ids)* + } + + #[allow(unused_variables, non_snake_case)] + unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self + where + __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> + { + Self { + #(#field_from_components)* + } + } + + #[allow(unused_variables)] + fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) { + #(#field_get_components)* + } + } + }) +} + +fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> { + (0..count) + .map(|i| Ident::new(&fmt_string(i), Span::call_site())) + .collect::<Vec<Ident>>() +} + +#[proc_macro] +pub fn impl_param_set(_input: TokenStream) -> TokenStream { + let mut tokens = TokenStream::new(); + let max_params = 8; + let params = get_idents(|i| format!("P{i}"), max_params); + let params_fetch = get_idents(|i| format!("PF{i}"), max_params); + let metas = get_idents(|i| format!("m{i}"), max_params); + let mut param_fn_muts = Vec::new(); + for (i, param) in params.iter().enumerate() { + let fn_name = Ident::new(&format!("p{i}"), Span::call_site()); + let index = Index::from(i); + param_fn_muts.push(quote! { + pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item { + // SAFETY: systems run without conflicts with other systems. + // Conflicting params in ParamSet are not accessible at the same time + // ParamSets are guaranteed to not conflict with other SystemParams + unsafe { + <#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) + } + } + }); + } + + for param_count in 1..=max_params { + let param = ¶ms[0..param_count]; + let param_fetch = ¶ms_fetch[0..param_count]; + let meta = &metas[0..param_count]; + let param_fn_mut = ¶m_fn_muts[0..param_count]; + tokens.extend(TokenStream::from(quote! { + impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)> + { + type Fetch = ParamSetState<(#(#param::Fetch,)*)>; + } + + // SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read + + unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)> + where #(#param_fetch: ReadOnlySystemParamFetch,)* + { } + + // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts + // with any prior access, a panic will occur. + + unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)> + { + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { + #( + // Pretend to add each param to the system alone, see if it conflicts + let mut #meta = system_meta.clone(); + #meta.component_access_set.clear(); + #meta.archetype_component_access.clear(); + #param_fetch::init(world, &mut #meta); + let #param = #param_fetch::init(world, &mut system_meta.clone()); + )* + #( + system_meta + .component_access_set + .extend(#meta.component_access_set); + system_meta + .archetype_component_access + .extend(&#meta.archetype_component_access); + )* + ParamSetState((#(#param,)*)) + } + + fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { + let (#(#param,)*) = &mut self.0; + #( + #param.new_archetype(archetype, system_meta); + )* + } + + fn apply(&mut self, world: &mut World) { + self.0.apply(world) + } + } + + + + impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)> + { + type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>; + + #[inline] + unsafe fn get_param( + state: &'s mut Self, + system_meta: &SystemMeta, + world: &'w World, + change_tick: u32, + ) -> Self::Item { + ParamSet { + param_states: &mut state.0, + system_meta: system_meta.clone(), + world, + change_tick, + } + } + } + + impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)> + { + + #(#param_fn_mut)* + } + })); + } + + tokens +} + +#[derive(Default)] +struct SystemParamFieldAttributes { + pub ignore: bool, +} + +static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; + +/// Implement `SystemParam` to use a struct as a parameter in a system +#[proc_macro_derive(SystemParam, attributes(system_param))] +pub fn derive_system_param(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let fields = match get_named_struct_fields(&ast.data) { + Ok(fields) => &fields.named, + Err(e) => return e.into_compile_error().into(), + }; + let path = azalea_ecs_path(); + + let field_attributes = fields + .iter() + .map(|field| { + ( + field, + field + .attrs + .iter() + .find(|a| *a.path.get_ident().as_ref().unwrap() == SYSTEM_PARAM_ATTRIBUTE_NAME) + .map_or_else(SystemParamFieldAttributes::default, |a| { + syn::custom_keyword!(ignore); + let mut attributes = SystemParamFieldAttributes::default(); + a.parse_args_with(|input: ParseStream| { + if input.parse::<Option<ignore>>()?.is_some() { + attributes.ignore = true; + } + Ok(()) + }) + .expect("Invalid 'system_param' attribute format."); + + attributes + }), + ) + }) + .collect::<Vec<(&Field, SystemParamFieldAttributes)>>(); + let mut fields = Vec::new(); + let mut field_indices = Vec::new(); + let mut field_types = Vec::new(); + let mut ignored_fields = Vec::new(); + let mut ignored_field_types = Vec::new(); + for (i, (field, attrs)) in field_attributes.iter().enumerate() { + if attrs.ignore { + ignored_fields.push(field.ident.as_ref().unwrap()); + ignored_field_types.push(&field.ty); + } else { + fields.push(field.ident.as_ref().unwrap()); + field_types.push(&field.ty); + field_indices.push(Index::from(i)); + } + } + + let generics = ast.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let lifetimeless_generics: Vec<_> = generics + .params + .iter() + .filter(|g| matches!(g, GenericParam::Type(_))) + .collect(); + + let mut punctuated_generics = Punctuated::<_, Token![,]>::new(); + punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g { + GenericParam::Type(g) => GenericParam::Type(TypeParam { + default: None, + ..g.clone() + }), + _ => unreachable!(), + })); + + let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new(); + punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { + GenericParam::Type(g) => &g.ident, + _ => unreachable!(), + })); + + let struct_name = &ast.ident; + let fetch_struct_visibility = &ast.vis; + + TokenStream::from(quote! { + // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. + // The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via + // <EventReader<'static, 'static, T> as SystemParam>::Fetch + const _: () = { + impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause { + type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>; + } + + #[doc(hidden)] + #fetch_struct_visibility struct FetchState <TSystemParamState, #punctuated_generic_idents> { + state: TSystemParamState, + marker: std::marker::PhantomData<fn()->(#punctuated_generic_idents)> + } + + unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause { + fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self { + Self { + state: TSystemParamState::init(world, system_meta), + marker: std::marker::PhantomData, + } + } + + fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { + self.state.new_archetype(archetype, system_meta) + } + + fn apply(&mut self, world: &mut #path::world::World) { + self.state.apply(world) + } + } + + impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause { + type Item = #struct_name #ty_generics; + unsafe fn get_param( + state: &'s mut Self, + system_meta: &#path::system::SystemMeta, + world: &'w #path::world::World, + change_tick: u32, + ) -> Self::Item { + #struct_name { + #(#fields: <<#field_types as #path::system::SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* + #(#ignored_fields: <#ignored_field_types>::default(),)* + } + } + } + + // Safety: The `ParamState` is `ReadOnlySystemParamFetch`, so this can only read from the `World` + unsafe impl<TSystemParamState: #path::system::SystemParamState + #path::system::ReadOnlySystemParamFetch, #punctuated_generics> #path::system::ReadOnlySystemParamFetch for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {} + }; + }) +} + +/// Implement `WorldQuery` to use a struct as a parameter in a query +#[proc_macro_derive(WorldQuery, attributes(world_query))] +pub fn derive_world_query(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + derive_world_query_impl(ast) +} + +/// Generates an impl of the `SystemLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with +/// `#[system_label(ignore_fields)]`. +#[proc_macro_derive(SystemLabel, attributes(system_label))] +pub fn derive_system_label(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let mut trait_path = azalea_ecs_path(); + trait_path.segments.push(format_ident!("schedule").into()); + trait_path + .segments + .push(format_ident!("SystemLabel").into()); + derive_label(input, &trait_path, "system_label") +} + +/// Generates an impl of the `StageLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with +/// `#[stage_label(ignore_fields)]`. +#[proc_macro_derive(StageLabel, attributes(stage_label))] +pub fn derive_stage_label(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let mut trait_path = azalea_ecs_path(); + trait_path.segments.push(format_ident!("schedule").into()); + trait_path.segments.push(format_ident!("StageLabel").into()); + derive_label(input, &trait_path, "stage_label") +} + +/// Generates an impl of the `RunCriteriaLabel` trait. +/// +/// This works only for unit structs, or enums with only unit variants. +/// You may force a struct or variant to behave as if it were fieldless with +/// `#[run_criteria_label(ignore_fields)]`. +#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))] +pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let mut trait_path = azalea_ecs_path(); + trait_path.segments.push(format_ident!("schedule").into()); + trait_path + .segments + .push(format_ident!("RunCriteriaLabel").into()); + derive_label(input, &trait_path, "run_criteria_label") +} + +pub(crate) fn azalea_ecs_path() -> syn::Path { + BevyManifest::default().get_path("azalea_ecs") +} + +#[proc_macro_derive(Resource)] +pub fn derive_resource(input: TokenStream) -> TokenStream { + component::derive_resource(input) +} + +#[proc_macro_derive(Component, attributes(component))] +pub fn derive_component(input: TokenStream) -> TokenStream { + component::derive_component(input) +} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs b/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs new file mode 100644 index 00000000..05f0712a --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs @@ -0,0 +1,45 @@ +#![allow(dead_code)] + +use syn::DeriveInput; + +use super::symbol::Symbol; + +pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> { + let mut list = Vec::new(); + for attr in ast.attrs.iter().filter(|a| a.path == attr_name) { + match attr.parse_meta()? { + syn::Meta::List(meta) => list.extend(meta.nested.into_iter()), + other => { + return Err(syn::Error::new_spanned( + other, + format!("expected #[{attr_name}(...)]"), + )) + } + } + } + Ok(list) +} + +pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> { + if let syn::Lit::Str(lit) = lit { + Ok(lit) + } else { + Err(syn::Error::new_spanned( + lit, + format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"), + )) + } +} + +pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> { + if let syn::Lit::Bool(lit) = lit { + Ok(lit.value()) + } else { + Err(syn::Error::new_spanned( + lit, + format!( + "expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`" + ), + )) + } +} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs b/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs new file mode 100644 index 00000000..5fbba0e9 --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs @@ -0,0 +1,224 @@ +#![allow(dead_code)] + +extern crate proc_macro; + +mod attrs; +mod shape; +mod symbol; + +pub use attrs::*; +pub use shape::*; +pub use symbol::*; + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use std::{env, path::PathBuf}; +use syn::spanned::Spanned; +use toml::{map::Map, Value}; + +pub struct BevyManifest { + manifest: Map<String, Value>, +} + +impl Default for BevyManifest { + fn default() -> Self { + Self { + manifest: env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .map(|mut path| { + path.push("Cargo.toml"); + let manifest = std::fs::read_to_string(path).unwrap(); + toml::from_str(&manifest).unwrap() + }) + .unwrap(), + } + } +} + +impl BevyManifest { + pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> { + const AZALEA: &str = "azalea"; + const BEVY_ECS: &str = "bevy_ecs"; + const BEVY: &str = "bevy"; + + fn dep_package(dep: &Value) -> Option<&str> { + if dep.as_str().is_some() { + None + } else { + dep.as_table() + .unwrap() + .get("package") + .map(|name| name.as_str().unwrap()) + } + } + + let find_in_deps = |deps: &Map<String, Value>| -> Option<syn::Path> { + let package = if let Some(dep) = deps.get(name) { + return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); + } else if let Some(dep) = deps.get(AZALEA) { + dep_package(dep).unwrap_or(AZALEA) + } else if let Some(dep) = deps.get(BEVY_ECS) { + dep_package(dep).unwrap_or(BEVY_ECS) + } else if let Some(dep) = deps.get(BEVY) { + dep_package(dep).unwrap_or(BEVY) + } else { + return None; + }; + + let mut path = Self::parse_str::<syn::Path>(package); + if let Some(module) = name.strip_prefix("azalea_") { + path.segments.push(Self::parse_str(module)); + } + Some(path) + }; + + let deps = self + .manifest + .get("dependencies") + .map(|deps| deps.as_table().unwrap()); + let deps_dev = self + .manifest + .get("dev-dependencies") + .map(|deps| deps.as_table().unwrap()); + + deps.and_then(find_in_deps) + .or_else(|| deps_dev.and_then(find_in_deps)) + } + + /// Returns the path for the crate with the given name. + /// + /// This is a convenience method for constructing a [manifest] and + /// calling the [`get_path`] method. + /// + /// This method should only be used where you just need the path and can't + /// cache the [manifest]. If caching is possible, it's recommended to create + /// the [manifest] yourself and use the [`get_path`] method. + /// + /// [`get_path`]: Self::get_path + /// [manifest]: Self + pub fn get_path_direct(name: &str) -> syn::Path { + Self::default().get_path(name) + } + + pub fn get_path(&self, name: &str) -> syn::Path { + self.maybe_get_path(name) + .unwrap_or_else(|| Self::parse_str(name)) + } + + pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T { + syn::parse(path.parse::<TokenStream>().unwrap()).unwrap() + } +} + +/// Derive a label trait +/// +/// # Args +/// +/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label +/// trait +/// - `trait_path`: The path [`syn::Path`] to the label trait +pub fn derive_label( + input: syn::DeriveInput, + trait_path: &syn::Path, + attr_name: &str, +) -> TokenStream { + // return true if the variant specified is an `ignore_fields` attribute + fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { + if attr.path.get_ident().as_ref().unwrap() != &attr_name { + return false; + } + + syn::custom_keyword!(ignore_fields); + attr.parse_args_with(|input: syn::parse::ParseStream| { + let ignore = input.parse::<Option<ignore_fields>>()?.is_some(); + Ok(ignore) + }) + .unwrap() + } + + let ident = input.ident.clone(); + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { + where_token: Default::default(), + predicates: Default::default(), + }); + where_clause + .predicates + .push(syn::parse2(quote! { Self: 'static }).unwrap()); + + let as_str = match input.data { + syn::Data::Struct(d) => { + // see if the user tried to ignore fields incorrectly + if let Some(attr) = d + .fields + .iter() + .flat_map(|f| &f.attrs) + .find(|a| is_ignore(a, attr_name)) + { + let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); + } + // Structs must either be fieldless, or explicitly ignore the fields. + let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); + if matches!(d.fields, syn::Fields::Unit) || ignore_fields { + let lit = ident.to_string(); + quote! { #lit } + } else { + let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); + return quote_spanned! { + d.fields.span() => compile_error!(#err_msg); + } + .into(); + } + } + syn::Data::Enum(d) => { + // check if the user put #[label(ignore_fields)] in the wrong place + if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { + let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); + return quote_spanned! { + attr.span() => compile_error!(#err_msg); + } + .into(); + } + let arms = d.variants.iter().map(|v| { + // Variants must either be fieldless, or explicitly ignore the fields. + let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); + if matches!(v.fields, syn::Fields::Unit) | ignore_fields { + let mut path = syn::Path::from(ident.clone()); + path.segments.push(v.ident.clone().into()); + let lit = format!("{ident}::{}", v.ident.clone()); + quote! { #path { .. } => #lit } + } else { + let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); + quote_spanned! { + v.fields.span() => _ => { compile_error!(#err_msg); } + } + } + }); + quote! { + match self { + #(#arms),* + } + } + } + syn::Data::Union(_) => { + return quote_spanned! { + input.span() => compile_error!("Unions cannot be used as labels."); + } + .into(); + } + }; + + (quote! { + impl #impl_generics #trait_path for #ident #ty_generics #where_clause { + fn as_str(&self) -> &'static str { + #as_str + } + } + }) + .into() +} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs b/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs new file mode 100644 index 00000000..98230749 --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs @@ -0,0 +1,21 @@ +use proc_macro::Span; +use syn::{Data, DataStruct, Error, Fields, FieldsNamed}; + +/// Get the fields of a data structure if that structure is a struct with named +/// fields; otherwise, return a compile error that points to the site of the +/// macro invocation. +pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> { + match data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => Ok(fields), + _ => Err(Error::new( + // This deliberately points to the call site rather than the structure + // body; marking the entire body as the source of the error makes it + // impossible to figure out which `derive` has a problem. + Span::call_site().into(), + "Only structs with named fields are supported", + )), + } +} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs b/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs new file mode 100644 index 00000000..dc639f4d --- /dev/null +++ b/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs @@ -0,0 +1,35 @@ +use std::fmt::{self, Display}; +use syn::{Ident, Path}; + +#[derive(Copy, Clone)] +pub struct Symbol(pub &'static str); + +impl PartialEq<Symbol> for Ident { + fn eq(&self, word: &Symbol) -> bool { + self == word.0 + } +} + +impl<'a> PartialEq<Symbol> for &'a Ident { + fn eq(&self, word: &Symbol) -> bool { + *self == word.0 + } +} + +impl PartialEq<Symbol> for Path { + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } +} + +impl<'a> PartialEq<Symbol> for &'a Path { + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } +} + +impl Display for Symbol { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.0) + } +} diff --git a/azalea-ecs/src/lib.rs b/azalea-ecs/src/lib.rs new file mode 100644 index 00000000..44579c5d --- /dev/null +++ b/azalea-ecs/src/lib.rs @@ -0,0 +1,144 @@ +#![feature(trait_alias)] + +//! Re-export important parts of `bevy_ecs` and `bevy_app` and make them more +//! compatible with Azalea. +//! +//! This is completely compatible with `bevy_ecs`, so it won't cause issues if +//! you use plugins meant for Bevy. +//! +//! Changes: +//! - Add [`TickPlugin`], [`TickStage`] and [`AppTickExt`] +//! - Change the macros to use azalea/azalea_ecs instead of bevy/bevy_ecs +//! - Rename bevy_ecs::world::World to azalea_ecs::ecs::Ecs +//! - Re-export `bevy_app` in the `app` module. + +use std::time::{Duration, Instant}; + +pub mod ecs { + pub use bevy_ecs::world::World as Ecs; + pub use bevy_ecs::world::{EntityMut, EntityRef, Mut}; +} +pub mod component { + pub use azalea_ecs_macros::Component; + pub use bevy_ecs::component::{ComponentId, ComponentStorage, Components, TableStorage}; + + // we do this because re-exporting Component would re-export the macro as well, + // which is bad (since we have our own Component macro) + // instead, we have to do this so Component is a trait alias and the original + // impl-able trait is still available as BevyComponent + pub trait Component = bevy_ecs::component::Component; + pub use bevy_ecs::component::Component as BevyComponent; +} +pub mod bundle { + pub use azalea_ecs_macros::Bundle; + pub trait Bundle = bevy_ecs::bundle::Bundle; + pub use bevy_ecs::bundle::Bundle as BevyBundle; +} +pub mod system { + pub use azalea_ecs_macros::Resource; + pub use bevy_ecs::system::{ + Command, Commands, EntityCommands, Query, Res, ResMut, SystemState, + }; + pub trait Resource = bevy_ecs::system::Resource; + pub use bevy_ecs::system::Resource as BevyResource; +} +pub use bevy_app as app; +pub use bevy_ecs::{entity, event, ptr, query, schedule, storage}; + +use app::{App, CoreStage, Plugin}; +use bevy_ecs::schedule::*; +use ecs::Ecs; + +pub struct TickPlugin { + /// How often a tick should happen. 50 milliseconds by default. Set to 0 to + /// tick every update. + pub tick_interval: Duration, +} +impl Plugin for TickPlugin { + fn build(&self, app: &mut App) { + app.add_stage_before( + CoreStage::Update, + TickLabel, + TickStage { + interval: self.tick_interval, + next_tick: Instant::now(), + stage: Box::new(SystemStage::parallel()), + }, + ); + } +} +impl Default for TickPlugin { + fn default() -> Self { + Self { + tick_interval: Duration::from_millis(50), + } + } +} + +#[derive(StageLabel)] +struct TickLabel; + +/// A [`Stage`] that runs every 50 milliseconds. +pub struct TickStage { + pub interval: Duration, + pub next_tick: Instant, + stage: Box<dyn Stage>, +} + +impl Stage for TickStage { + fn run(&mut self, ecs: &mut Ecs) { + // if the interval is 0, that means it runs every tick + if self.interval.is_zero() { + self.stage.run(ecs); + return; + } + // keep calling run until it's caught up + // TODO: Minecraft bursts up to 10 ticks and then skips, we should too (but + // check the source so we do it right) + while Instant::now() > self.next_tick { + self.next_tick += self.interval; + self.stage.run(ecs); + } + } +} + +pub trait AppTickExt { + fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App; + fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App; +} + +impl AppTickExt for App { + /// Adds a set of ECS systems that will run every 50 milliseconds. + /// + /// Note that you should NOT have `EventReader`s in tick systems, as this + /// will make them sometimes be missed. + fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App { + let tick_stage = self + .schedule + .get_stage_mut::<TickStage>(TickLabel) + .expect("Tick Stage not found"); + let stage = tick_stage + .stage + .downcast_mut::<SystemStage>() + .expect("Fixed Timestep sub-stage is not a SystemStage"); + stage.add_system_set(system_set); + self + } + + /// Adds a new ECS system that will run every 50 milliseconds. + /// + /// Note that you should NOT have `EventReader`s in tick systems, as this + /// will make them sometimes be missed. + fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App { + let tick_stage = self + .schedule + .get_stage_mut::<TickStage>(TickLabel) + .expect("Tick Stage not found"); + let stage = tick_stage + .stage + .downcast_mut::<SystemStage>() + .expect("Fixed Timestep sub-stage is not a SystemStage"); + stage.add_system(system); + self + } +} diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs index a4df15c1..36109f35 100755 --- a/azalea-nbt/src/encode.rs +++ b/azalea-nbt/src/encode.rs @@ -26,62 +26,62 @@ fn write_compound( Tag::Byte(value) => { writer.write_u8(1)?; write_string(writer, key)?; - writer.write_i8(*value)? + writer.write_i8(*value)?; } Tag::Short(value) => { writer.write_u8(2)?; write_string(writer, key)?; - writer.write_i16::<BE>(*value)? + writer.write_i16::<BE>(*value)?; } Tag::Int(value) => { writer.write_u8(3)?; write_string(writer, key)?; - writer.write_i32::<BE>(*value)? + writer.write_i32::<BE>(*value)?; } Tag::Long(value) => { writer.write_u8(4)?; write_string(writer, key)?; - writer.write_i64::<BE>(*value)? + writer.write_i64::<BE>(*value)?; } Tag::Float(value) => { writer.write_u8(5)?; write_string(writer, key)?; - writer.write_f32::<BE>(*value)? + writer.write_f32::<BE>(*value)?; } Tag::Double(value) => { writer.write_u8(6)?; write_string(writer, key)?; - writer.write_f64::<BE>(*value)? + writer.write_f64::<BE>(*value)?; } Tag::ByteArray(value) => { writer.write_u8(7)?; write_string(writer, key)?; - write_bytearray(writer, value)? + write_bytearray(writer, value)?; } Tag::String(value) => { writer.write_u8(8)?; write_string(writer, key)?; - write_string(writer, value)? + write_string(writer, value)?; } Tag::List(value) => { writer.write_u8(9)?; write_string(writer, key)?; - write_list(writer, value)? + write_list(writer, value)?; } Tag::Compound(value) => { writer.write_u8(10)?; write_string(writer, key)?; - write_compound(writer, value, true)? + write_compound(writer, value, true)?; } Tag::IntArray(value) => { writer.write_u8(11)?; write_string(writer, key)?; - write_intarray(writer, value)? + write_intarray(writer, value)?; } Tag::LongArray(value) => { writer.write_u8(12)?; write_string(writer, key)?; - write_longarray(writer, value)? + write_longarray(writer, value)?; } } } diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs index 65b152fa..a18a42b1 100755 --- a/azalea-nbt/src/tag.rs +++ b/azalea-nbt/src/tag.rs @@ -2,10 +2,11 @@ use ahash::AHashMap; use serde::{Deserialize, Serialize}; /// An NBT value. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[serde(untagged)] pub enum Tag { - End, // 0 + #[default] + End, // 0 Byte(i8), // 1 Short(i16), // 2 Int(i32), // 3 @@ -20,12 +21,6 @@ pub enum Tag { LongArray(Vec<i64>), // 12 } -impl Default for Tag { - fn default() -> Self { - Tag::End - } -} - impl Tag { /// Get the numerical ID of the tag type. #[inline] diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index eb8b9dfc..e0fc7f96 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -9,11 +9,14 @@ version = "0.5.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-block = {path = "../azalea-block", version = "^0.5.0" } -azalea-core = {path = "../azalea-core", version = "^0.5.0" } -azalea-world = {path = "../azalea-world", version = "^0.5.0" } +azalea-block = { path = "../azalea-block", version = "^0.5.0" } +azalea-core = { path = "../azalea-core", version = "^0.5.0" } +azalea-world = { path = "../azalea-world", version = "^0.5.0" } +azalea-registry = { path = "../azalea-registry", version = "^0.5.0" } +iyes_loopless = "0.9.1" once_cell = "1.16.0" parking_lot = "^0.12.1" +azalea-ecs = { version = "0.5.0", path = "../azalea-ecs" } [dev-dependencies] uuid = "^1.1.2" diff --git a/azalea-physics/src/collision/discrete_voxel_shape.rs b/azalea-physics/src/collision/discrete_voxel_shape.rs index 51d45316..4a329398 100755 --- a/azalea-physics/src/collision/discrete_voxel_shape.rs +++ b/azalea-physics/src/collision/discrete_voxel_shape.rs @@ -64,7 +64,7 @@ impl DiscreteVoxelShape { } pub fn for_all_boxes(&self, consumer: impl IntLineConsumer, swap: bool) { - BitSetDiscreteVoxelShape::for_all_boxes(self, consumer, swap) + BitSetDiscreteVoxelShape::for_all_boxes(self, consumer, swap); } } diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 458303c5..7d934020 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -5,13 +5,15 @@ mod shape; mod world_collisions; use azalea_core::{Axis, Vec3, AABB, EPSILON}; -use azalea_world::entity::{Entity, EntityData}; -use azalea_world::{MoveEntityError, WeakWorld}; +use azalea_world::{ + entity::{self}, + MoveEntityError, World, +}; pub use blocks::BlockWithShape; pub use discrete_voxel_shape::*; pub use shape::*; -use std::ops::Deref; -use world_collisions::CollisionGetter; + +use self::world_collisions::get_block_collisions; pub enum MoverType { Own, @@ -21,192 +23,170 @@ pub enum MoverType { Shulker, } -pub trait HasCollision { - fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3; -} +// private Vec3 collide(Vec3 var1) { +// AABB var2 = this.getBoundingBox(); +// List var3 = this.level.getEntityCollisions(this, +// var2.expandTowards(var1)); Vec3 var4 = var1.lengthSqr() == 0.0D ? +// var1 : collideBoundingBox(this, var1, var2, this.level, var3); +// boolean var5 = var1.x != var4.x; +// boolean var6 = var1.y != var4.y; +// boolean var7 = var1.z != var4.z; +// boolean var8 = this.onGround || var6 && var1.y < 0.0D; +// if (this.maxUpStep > 0.0F && var8 && (var5 || var7)) { +// Vec3 var9 = collideBoundingBox(this, new Vec3(var1.x, +// (double)this.maxUpStep, var1.z), var2, this.level, var3); Vec3 +// var10 = collideBoundingBox(this, new Vec3(0.0D, (double)this.maxUpStep, +// 0.0D), var2.expandTowards(var1.x, 0.0D, var1.z), this.level, var3); +// if (var10.y < (double)this.maxUpStep) { +// Vec3 var11 = collideBoundingBox(this, new Vec3(var1.x, 0.0D, +// var1.z), var2.move(var10), this.level, var3).add(var10); if +// (var11.horizontalDistanceSqr() > var9.horizontalDistanceSqr()) { +// var9 = var11; +// } +// } + +// if (var9.horizontalDistanceSqr() > var4.horizontalDistanceSqr()) { +// return var9.add(collideBoundingBox(this, new Vec3(0.0D, -var9.y + +// var1.y, 0.0D), var2.move(var9), this.level, var3)); } +// } + +// return var4; +// } +fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 { + let entity_bounding_box = physics.bounding_box; + // TODO: get_entity_collisions + // let entity_collisions = world.get_entity_collisions(self, + // entity_bounding_box.expand_towards(movement)); + let entity_collisions = Vec::new(); + if movement.length_sqr() == 0.0 { + *movement + } else { + collide_bounding_box(movement, &entity_bounding_box, world, entity_collisions) + } -pub trait MovableEntity { - fn move_colliding( - &mut self, - mover_type: &MoverType, - movement: &Vec3, - ) -> Result<(), MoveEntityError>; + // TODO: stepping (for stairs and stuff) + + // collided_movement } -impl<D: Deref<Target = WeakWorld>> HasCollision for D { - // private Vec3 collide(Vec3 var1) { - // AABB var2 = this.getBoundingBox(); - // List var3 = this.level.getEntityCollisions(this, - // var2.expandTowards(var1)); Vec3 var4 = var1.lengthSqr() == 0.0D ? - // var1 : collideBoundingBox(this, var1, var2, this.level, var3); - // boolean var5 = var1.x != var4.x; - // boolean var6 = var1.y != var4.y; - // boolean var7 = var1.z != var4.z; - // boolean var8 = this.onGround || var6 && var1.y < 0.0D; - // if (this.maxUpStep > 0.0F && var8 && (var5 || var7)) { - // Vec3 var9 = collideBoundingBox(this, new Vec3(var1.x, - // (double)this.maxUpStep, var1.z), var2, this.level, var3); Vec3 - // var10 = collideBoundingBox(this, new Vec3(0.0D, (double)this.maxUpStep, - // 0.0D), var2.expandTowards(var1.x, 0.0D, var1.z), this.level, var3); - // if (var10.y < (double)this.maxUpStep) { - // Vec3 var11 = collideBoundingBox(this, new Vec3(var1.x, 0.0D, - // var1.z), var2.move(var10), this.level, var3).add(var10); if - // (var11.horizontalDistanceSqr() > var9.horizontalDistanceSqr()) { - // var9 = var11; - // } - // } - - // if (var9.horizontalDistanceSqr() > var4.horizontalDistanceSqr()) { - // return var9.add(collideBoundingBox(this, new Vec3(0.0D, -var9.y + - // var1.y, 0.0D), var2.move(var9), this.level, var3)); } +/// Move an entity by a given delta, checking for collisions. +pub fn move_colliding( + _mover_type: &MoverType, + movement: &Vec3, + world: &World, + position: &mut entity::Position, + physics: &mut entity::Physics, +) -> Result<(), MoveEntityError> { + // TODO: do all these + + // if self.no_physics { + // return; + // }; + + // if (var1 == MoverType.PISTON) { + // var2 = this.limitPistonMovement(var2); + // if (var2.equals(Vec3.ZERO)) { + // return; // } + // } - // return var4; + // if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { + // var2 = var2.multiply(this.stuckSpeedMultiplier); + // this.stuckSpeedMultiplier = Vec3.ZERO; + // this.setDeltaMovement(Vec3.ZERO); // } - fn collide(&self, movement: &Vec3, entity: &EntityData) -> Vec3 { - let entity_bounding_box = entity.bounding_box; - // TODO: get_entity_collisions - // let entity_collisions = world.get_entity_collisions(self, - // entity_bounding_box.expand_towards(movement)); - let entity_collisions = Vec::new(); - if movement.length_sqr() == 0.0 { - *movement - } else { - collide_bounding_box( - Some(entity), - movement, - &entity_bounding_box, - self, - entity_collisions, - ) - } - // TODO: stepping (for stairs and stuff) + // movement = this.maybeBackOffFromEdge(movement, moverType); - // collided_movement - } -} + let collide_result = collide(movement, world, physics); -impl<D: Deref<Target = WeakWorld>> MovableEntity for Entity<'_, D> { - /// Move an entity by a given delta, checking for collisions. - fn move_colliding( - &mut self, - _mover_type: &MoverType, - movement: &Vec3, - ) -> Result<(), MoveEntityError> { - // TODO: do all these - - // if self.no_physics { - // return; - // }; - - // if (var1 == MoverType.PISTON) { - // var2 = this.limitPistonMovement(var2); - // if (var2.equals(Vec3.ZERO)) { - // return; - // } - // } - - // if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { - // var2 = var2.multiply(this.stuckSpeedMultiplier); - // this.stuckSpeedMultiplier = Vec3.ZERO; - // this.setDeltaMovement(Vec3.ZERO); - // } - - // movement = this.maybeBackOffFromEdge(movement, moverType); - - let collide_result = { self.world.collide(movement, self) }; - - let move_distance = collide_result.length_sqr(); - - if move_distance > EPSILON { - // TODO: fall damage - - let new_pos = { - let entity_pos = self.pos(); - Vec3 { - x: entity_pos.x + collide_result.x, - y: entity_pos.y + collide_result.y, - z: entity_pos.z + collide_result.z, - } - }; - - self.world.set_entity_pos(self.id, new_pos)?; - } + let move_distance = collide_result.length_sqr(); + + if move_distance > EPSILON { + // TODO: fall damage + + let new_pos = { + Vec3 { + x: position.x + collide_result.x, + y: position.y + collide_result.y, + z: position.z + collide_result.z, + } + }; - let x_collision = movement.x != collide_result.x; - let z_collision = movement.z != collide_result.z; - let horizontal_collision = x_collision || z_collision; - let vertical_collision = movement.y != collide_result.y; - let on_ground = vertical_collision && movement.y < 0.; - self.on_ground = on_ground; + **position = new_pos; + } - // TODO: minecraft checks for a "minor" horizontal collision here + let x_collision = movement.x != collide_result.x; + let z_collision = movement.z != collide_result.z; + let horizontal_collision = x_collision || z_collision; + let vertical_collision = movement.y != collide_result.y; + let on_ground = vertical_collision && movement.y < 0.; + physics.on_ground = on_ground; - let _block_pos_below = self.on_pos_legacy(); - // let _block_state_below = self - // .world - // .get_block_state(&block_pos_below) - // .expect("Couldn't get block state below"); + // TODO: minecraft checks for a "minor" horizontal collision here - // self.check_fall_damage(collide_result.y, on_ground, block_state_below, - // block_pos_below); + let _block_pos_below = entity::on_pos_legacy(&world.chunks, position); + // let _block_state_below = self + // .world + // .get_block_state(&block_pos_below) + // .expect("Couldn't get block state below"); - // if self.isRemoved() { return; } + // self.check_fall_damage(collide_result.y, on_ground, block_state_below, + // block_pos_below); - if horizontal_collision { - let delta_movement = &self.delta; - self.delta = Vec3 { - x: if x_collision { 0. } else { delta_movement.x }, - y: delta_movement.y, - z: if z_collision { 0. } else { delta_movement.z }, - } - } + // if self.isRemoved() { return; } - if vertical_collision { - // blockBelow.updateEntityAfterFallOn(this.level, this); - // the default implementation of updateEntityAfterFallOn sets the y movement to - // 0 - self.delta.y = 0.; + if horizontal_collision { + let delta_movement = &physics.delta; + physics.delta = Vec3 { + x: if x_collision { 0. } else { delta_movement.x }, + y: delta_movement.y, + z: if z_collision { 0. } else { delta_movement.z }, } + } - if on_ground { - // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, - // this); - } + if vertical_collision { + // blockBelow.updateEntityAfterFallOn(this.level, this); + // the default implementation of updateEntityAfterFallOn sets the y movement to + // 0 + physics.delta.y = 0.; + } + + if on_ground { + // blockBelow.stepOn(this.level, blockPosBelow, blockStateBelow, + // this); + } - // sounds + // sounds - // this.tryCheckInsideBlocks(); + // this.tryCheckInsideBlocks(); - // float var25 = this.getBlockSpeedFactor(); - // this.setDeltaMovement(this.getDeltaMovement().multiply((double)var25, 1.0D, - // (double)var25)); if (this.level.getBlockStatesIfLoaded(this. - // getBoundingBox().deflate(1.0E-6D)).noneMatch((var0) -> { - // return var0.is(BlockTags.FIRE) || var0.is(Blocks.LAVA); - // })) { - // if (this.remainingFireTicks <= 0) { - // this.setRemainingFireTicks(-this.getFireImmuneTicks()); - // } + // float var25 = this.getBlockSpeedFactor(); + // this.setDeltaMovement(this.getDeltaMovement().multiply((double)var25, 1.0D, + // (double)var25)); if (this.level.getBlockStatesIfLoaded(this. + // getBoundingBox().deflate(1.0E-6D)).noneMatch((var0) -> { + // return var0.is(BlockTags.FIRE) || var0.is(Blocks.LAVA); + // })) { + // if (this.remainingFireTicks <= 0) { + // this.setRemainingFireTicks(-this.getFireImmuneTicks()); + // } - // if (this.wasOnFire && (this.isInPowderSnow || - // this.isInWaterRainOrBubble())) { this. - // playEntityOnFireExtinguishedSound(); } - // } + // if (this.wasOnFire && (this.isInPowderSnow || + // this.isInWaterRainOrBubble())) { this. + // playEntityOnFireExtinguishedSound(); } + // } - // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) - // { this.setRemainingFireTicks(-this.getFireImmuneTicks()); - // } + // if (this.isOnFire() && (this.isInPowderSnow || this.isInWaterRainOrBubble())) + // { this.setRemainingFireTicks(-this.getFireImmuneTicks()); + // } - Ok(()) - } + Ok(()) } fn collide_bounding_box( - entity: Option<&EntityData>, movement: &Vec3, entity_bounding_box: &AABB, - world: &WeakWorld, + world: &World, entity_collisions: Vec<VoxelShape>, ) -> Vec3 { let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1); @@ -218,7 +198,7 @@ fn collide_bounding_box( // TODO: world border let block_collisions = - world.get_block_collisions(entity, entity_bounding_box.expand_towards(movement)); + get_block_collisions(world, entity_bounding_box.expand_towards(movement)); let block_collisions = block_collisions.collect::<Vec<_>>(); 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 bb2ed2e5..cc184591 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -539,7 +539,7 @@ impl VoxelShape { x_coords[var7 as usize], y_coords[var8 as usize], z_coords[var9 as usize], - ) + ); }, true, ); diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index 9e238bf9..c7b2a91b 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -1,34 +1,17 @@ +use super::Shapes; use crate::collision::{BlockWithShape, VoxelShape, AABB}; use azalea_block::BlockState; use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; -use azalea_world::entity::EntityData; -use azalea_world::{Chunk, WeakWorld}; +use azalea_world::{Chunk, World}; use parking_lot::RwLock; use std::sync::Arc; -use super::Shapes; - -pub trait CollisionGetter { - fn get_block_collisions<'a>( - &'a self, - entity: Option<&EntityData>, - aabb: AABB, - ) -> BlockCollisions<'a>; -} - -impl CollisionGetter for WeakWorld { - fn get_block_collisions<'a>( - &'a self, - entity: Option<&EntityData>, - aabb: AABB, - ) -> BlockCollisions<'a> { - BlockCollisions::new(self, entity, aabb) - } +pub fn get_block_collisions(world: &World, aabb: AABB) -> BlockCollisions<'_> { + BlockCollisions::new(world, aabb) } pub struct BlockCollisions<'a> { - pub world: &'a WeakWorld, - // context: CollisionContext, + pub world: &'a World, pub aabb: AABB, pub entity_shape: VoxelShape, pub cursor: Cursor3d, @@ -36,8 +19,7 @@ pub struct BlockCollisions<'a> { } impl<'a> BlockCollisions<'a> { - // TODO: the entity is stored in the context - pub fn new(world: &'a WeakWorld, _entity: Option<&EntityData>, aabb: AABB) -> Self { + pub fn new(world: &'a World, aabb: AABB) -> Self { let origin_x = (aabb.min_x - EPSILON) as i32 - 1; let origin_y = (aabb.min_y - EPSILON) as i32 - 1; let origin_z = (aabb.min_z - EPSILON) as i32 - 1; @@ -75,7 +57,7 @@ impl<'a> BlockCollisions<'a> { // return var7; // } - self.world.get_chunk(&chunk_pos) + self.world.chunks.get(&chunk_pos) } } @@ -89,15 +71,14 @@ impl<'a> Iterator for BlockCollisions<'a> { } let chunk = self.get_chunk(item.pos.x, item.pos.z); - let chunk = match chunk { - Some(chunk) => chunk, - None => continue, + let Some(chunk) = chunk else { + continue }; let pos = item.pos; let block_state: BlockState = chunk .read() - .get(&(&pos).into(), self.world.min_y()) + .get(&(&pos).into(), self.world.chunks.min_y) .unwrap_or(BlockState::Air); // TODO: continue if self.only_suffocating_blocks and the block is not diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 2409dff7..96bebd1a 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -5,24 +5,56 @@ pub mod collision; use azalea_block::{Block, BlockState}; use azalea_core::{BlockPos, Vec3}; +use azalea_ecs::{ + app::{App, Plugin}, + entity::Entity, + event::{EventReader, EventWriter}, + query::With, + schedule::{IntoSystemDescriptor, SystemSet}, + system::{Query, Res}, + AppTickExt, +}; use azalea_world::{ - entity::{Entity, EntityData}, - WeakWorld, + entity::{ + metadata::Sprinting, move_relative, Attributes, Jumping, Physics, Position, WorldName, + }, + Local, World, WorldContainer, }; -use collision::{MovableEntity, MoverType}; -use std::ops::Deref; - -pub trait HasPhysics { - fn travel(&mut self, acceleration: &Vec3); - fn ai_step(&mut self); - - fn jump_from_ground(&mut self); +use collision::{move_colliding, MoverType}; + +pub struct PhysicsPlugin; +impl Plugin for PhysicsPlugin { + fn build(&self, app: &mut App) { + app.add_event::<ForceJumpEvent>() + .add_system( + force_jump_listener + .label("force_jump_listener") + .after("ai_step"), + ) + .add_tick_system_set( + SystemSet::new() + .with_system(ai_step.label("ai_step")) + .with_system( + travel + .label("travel") + .after("ai_step") + .after("force_jump_listener"), + ), + ); + } } -impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { - /// Move the entity with the given acceleration while handling friction, - /// gravity, collisions, and some other stuff. - fn travel(&mut self, acceleration: &Vec3) { +/// Move the entity with the given acceleration while handling friction, +/// gravity, collisions, and some other stuff. +fn travel( + mut query: Query<(&mut Physics, &mut Position, &Attributes, &WorldName), With<Local>>, + world_container: Res<WorldContainer>, +) { + for (mut physics, mut position, attributes, world_name) in &mut query { + let world_lock = world_container + .get(world_name) + .expect("All entities should be in a valid world"); + let world = world_lock.read(); // if !self.is_effective_ai() && !self.is_controlled_by_local_instance() { // // this.calculateEntityAnimation(this, this instanceof FlyingAnimal); // return; @@ -37,24 +69,29 @@ impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { // TODO: elytra - let block_pos_below = get_block_pos_below_that_affects_movement(self); + let block_pos_below = get_block_pos_below_that_affects_movement(&position); - let block_state_below = self - .world + let block_state_below = world + .chunks .get_block_state(&block_pos_below) .unwrap_or(BlockState::Air); let block_below: Box<dyn Block> = block_state_below.into(); let block_friction = block_below.behavior().friction; - let inertia = if self.on_ground { + let inertia = if physics.on_ground { block_friction * 0.91 } else { 0.91 }; // this applies the current delta - let mut movement = - handle_relative_friction_and_calculate_movement(self, acceleration, block_friction); + let mut movement = handle_relative_friction_and_calculate_movement( + block_friction, + &world, + &mut physics, + &mut position, + attributes, + ); movement.y -= gravity; @@ -66,96 +103,132 @@ impl<D: Deref<Target = WeakWorld>> HasPhysics for Entity<'_, D> { // if should_discard_friction(self) { if false { - self.delta = movement; + physics.delta = movement; } else { - self.delta = Vec3 { + physics.delta = Vec3 { x: movement.x * inertia as f64, y: movement.y * 0.98f64, z: movement.z * inertia as f64, }; } } +} - /// applies air resistance, calls self.travel(), and some other random - /// stuff. - fn ai_step(&mut self) { +/// applies air resistance, calls self.travel(), and some other random +/// stuff. +pub fn ai_step( + mut query: Query< + (Entity, &mut Physics, Option<&Jumping>), + With<Local>, + // TODO: ai_step should only run for players in loaded chunks + // With<LocalPlayerInLoadedChunk> maybe there should be an InLoadedChunk/InUnloadedChunk + // component? + >, + mut force_jump_events: EventWriter<ForceJumpEvent>, +) { + for (entity, mut physics, jumping) in &mut query { // vanilla does movement interpolation here, doesn't really matter much for a // bot though - if self.delta.x.abs() < 0.003 { - self.delta.x = 0.; + if physics.delta.x.abs() < 0.003 { + physics.delta.x = 0.; } - if self.delta.y.abs() < 0.003 { - self.delta.y = 0.; + if physics.delta.y.abs() < 0.003 { + physics.delta.y = 0.; } - if self.delta.z.abs() < 0.003 { - self.delta.z = 0.; + if physics.delta.z.abs() < 0.003 { + physics.delta.z = 0.; } - if self.jumping { - // TODO: jumping in liquids and jump delay + if let Some(jumping) = jumping { + if **jumping { + // TODO: jumping in liquids and jump delay - if self.on_ground { - self.jump_from_ground(); + if physics.on_ground { + force_jump_events.send(ForceJumpEvent(entity)); + } } } - self.xxa *= 0.98; - self.zza *= 0.98; - - self.travel(&Vec3 { - x: self.xxa as f64, - y: self.yya as f64, - z: self.zza as f64, - }); - // freezing - // pushEntities - // drowning damage + physics.xxa *= 0.98; + physics.zza *= 0.98; + + // TODO: freezing, pushEntities, drowning damage (in their own systems, + // after `travel`) } +} - fn jump_from_ground(&mut self) { - let jump_power: f64 = jump_power(self) as f64 + jump_boost_power(self); - let old_delta_movement = self.delta; - self.delta = Vec3 { - x: old_delta_movement.x, - y: jump_power, - z: old_delta_movement.z, - }; - if self.metadata.sprinting { - let y_rot = self.y_rot * 0.017453292; - self.delta += Vec3 { - x: (-f32::sin(y_rot) * 0.2) as f64, - y: 0., - z: (f32::cos(y_rot) * 0.2) as f64, +/// Jump even if we aren't on the ground. +pub struct ForceJumpEvent(pub Entity); + +fn force_jump_listener( + mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>, + world_container: Res<WorldContainer>, + mut events: EventReader<ForceJumpEvent>, +) { + for event in events.iter() { + if let Ok((mut physics, position, sprinting, world_name)) = query.get_mut(event.0) { + let world_lock = world_container + .get(world_name) + .expect("All entities should be in a valid world"); + let world = world_lock.read(); + + let jump_power: f64 = jump_power(&world, position) as f64 + jump_boost_power(); + let old_delta_movement = physics.delta; + physics.delta = Vec3 { + x: old_delta_movement.x, + y: jump_power, + z: old_delta_movement.z, }; - } + if **sprinting { + // sprint jumping gives some extra velocity + let y_rot = physics.y_rot * 0.017453292; + physics.delta += Vec3 { + x: (-f32::sin(y_rot) * 0.2) as f64, + y: 0., + z: (f32::cos(y_rot) * 0.2) as f64, + }; + } - self.has_impulse = true; + physics.has_impulse = true; + } } } -fn get_block_pos_below_that_affects_movement(entity: &EntityData) -> BlockPos { +fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos { BlockPos::new( - entity.pos().x.floor() as i32, + position.x.floor() as i32, // TODO: this uses bounding_box.min_y instead of position.y - (entity.pos().y - 0.5f64).floor() as i32, - entity.pos().z.floor() as i32, + (position.y - 0.5f64).floor() as i32, + position.z.floor() as i32, ) } -fn handle_relative_friction_and_calculate_movement<D: Deref<Target = WeakWorld>>( - entity: &mut Entity<D>, - acceleration: &Vec3, +fn handle_relative_friction_and_calculate_movement( block_friction: f32, + world: &World, + physics: &mut Physics, + position: &mut Position, + attributes: &Attributes, ) -> Vec3 { - entity.move_relative( - get_friction_influenced_speed(&*entity, block_friction), - acceleration, + move_relative( + physics, + get_friction_influenced_speed(physics, attributes, block_friction), + &Vec3 { + x: physics.xxa as f64, + y: physics.yya as f64, + z: physics.zza as f64, + }, ); // entity.delta = entity.handle_on_climbable(entity.delta); - entity - .move_colliding(&MoverType::Own, &entity.delta.clone()) - .expect("Entity should exist."); + move_colliding( + &MoverType::Own, + &physics.delta.clone(), + world, + position, + physics, + ) + .expect("Entity should exist."); // let delta_movement = entity.delta; // ladders // if ((entity.horizontalCollision || entity.jumping) && (entity.onClimbable() @@ -164,16 +237,16 @@ fn handle_relative_friction_and_calculate_movement<D: Deref<Target = WeakWorld>> // Vec3(var3.x, 0.2D, var3.z); } // TODO: powdered snow - entity.delta + physics.delta } // private float getFrictionInfluencedSpeed(float friction) { // return this.onGround ? this.getSpeed() * (0.21600002F / (friction * // friction * friction)) : this.flyingSpeed; } -fn get_friction_influenced_speed(entity: &EntityData, friction: f32) -> f32 { +fn get_friction_influenced_speed(physics: &Physics, attributes: &Attributes, friction: f32) -> f32 { // TODO: have speed & flying_speed fields in entity - if entity.on_ground { - let speed: f32 = entity.attributes.speed.calculate() as f32; + if physics.on_ground { + let speed: f32 = attributes.speed.calculate() as f32; speed * (0.216f32 / (friction * friction * friction)) } else { // entity.flying_speed @@ -183,11 +256,11 @@ fn get_friction_influenced_speed(entity: &EntityData, friction: f32) -> f32 { /// Returns the what the entity's jump should be multiplied by based on the /// block they're standing on. -fn block_jump_factor<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { - let block_at_pos = entity.world.get_block_state(&entity.pos().into()); - let block_below = entity - .world - .get_block_state(&get_block_pos_below_that_affects_movement(entity)); +fn block_jump_factor(world: &World, position: &Position) -> f32 { + let block_at_pos = world.chunks.get_block_state(&position.into()); + let block_below = world + .chunks + .get_block_state(&get_block_pos_below_that_affects_movement(position)); let block_at_pos_jump_factor = if let Some(block) = block_at_pos { Box::<dyn Block>::from(block).behavior().jump_factor @@ -211,11 +284,11 @@ fn block_jump_factor<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { // public double getJumpBoostPower() { // return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * // (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } -fn jump_power<D: Deref<Target = WeakWorld>>(entity: &Entity<D>) -> f32 { - 0.42 * block_jump_factor(entity) +fn jump_power(world: &World, position: &Position) -> f32 { + 0.42 * block_jump_factor(world, position) } -fn jump_boost_power<D: Deref<Target = WeakWorld>>(_entity: &Entity<D>) -> f64 { +fn jump_boost_power() -> f64 { // TODO: potion effects // if let Some(effects) = entity.effects() { // if let Some(jump_effect) = effects.get(&Effect::Jump) { @@ -231,131 +304,218 @@ fn jump_boost_power<D: Deref<Target = WeakWorld>>(_entity: &Entity<D>) -> f64 { #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; - use azalea_core::ChunkPos; + use azalea_core::{ChunkPos, ResourceLocation}; + use azalea_ecs::{app::App, TickPlugin}; use azalea_world::{ - entity::{metadata, EntityMetadata}, - Chunk, PartialWorld, + entity::{EntityBundle, MinecraftEntityId}, + Chunk, EntityPlugin, PartialWorld, }; use uuid::Uuid; + /// You need an app to spawn entities in the world and do updates. + fn make_test_app() -> App { + let mut app = App::new(); + app.add_plugin(TickPlugin { + tick_interval: Duration::ZERO, + }) + .add_plugin(PhysicsPlugin) + .add_plugin(EntityPlugin) + .init_resource::<WorldContainer>(); + app + } + #[test] fn test_gravity() { - let mut world = PartialWorld::default(); - - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0., - y: 70., - z: 0., - }, - EntityMetadata::Player(metadata::Player::default()), - ), - ); - let mut entity = world.entity_mut(0).unwrap(); - // y should start at 70 - assert_eq!(entity.pos().y, 70.); - entity.ai_step(); - // delta is applied before gravity, so the first tick only sets the delta - assert_eq!(entity.pos().y, 70.); - assert!(entity.delta.y < 0.); - entity.ai_step(); - // the second tick applies the delta to the position, so now it should go down - assert!( - entity.pos().y < 70., - "Entity y ({}) didn't go down after physics steps", - entity.pos().y + let mut app = make_test_app(); + let _world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, ); + + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0., + y: 70., + z: 0., + }, + azalea_registry::EntityKind::Zombie, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // y should start at 70 + assert_eq!(entity_pos.y, 70.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // delta is applied before gravity, so the first tick only sets the delta + assert_eq!(entity_pos.y, 70.); + let entity_physics = app.world.get::<Physics>(entity).unwrap().clone(); + assert!(entity_physics.delta.y < 0.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // the second tick applies the delta to the position, so now it should go down + assert!( + entity_pos.y < 70., + "Entity y ({}) didn't go down after physics steps", + entity_pos.y + ); + } } #[test] fn test_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 70., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, + ); + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 70., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = partial_world.chunks.set_block_state( + &BlockPos { x: 0, y: 69, z: 0 }, + BlockState::Stone, + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state(&BlockPos { x: 0, y: 69, z: 0 }, BlockState::Stone); assert!( block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); - entity.ai_step(); - // delta will change, but it won't move until next tick - assert_eq!(entity.pos().y, 70.); - assert!(entity.delta.y < 0.); - entity.ai_step(); - // the second tick applies the delta to the position, but it also does collision - assert_eq!(entity.pos().y, 70.); + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // delta will change, but it won't move until next tick + assert_eq!(entity_pos.y, 70.); + let entity_physics = app.world.get::<Physics>(entity).unwrap().clone(); + assert!(entity_physics.delta.y < 0.); + } + app.update(); + { + let entity_pos = *app.world.get::<Position>(entity).unwrap(); + // the second tick applies the delta to the position, but it also does collision + assert_eq!(entity_pos.y, 70.); + } } #[test] fn test_slab_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 71., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, ); - let block_state = world.set_block_state( + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, + ); + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 71., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = partial_world.chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::StoneSlab_BottomFalse, + &mut world_lock.write().chunks, ); assert!( block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 69.5); + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 69.5); } #[test] fn test_top_slab_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 71., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state( + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 71., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::StoneSlab_TopFalse, ); @@ -363,33 +523,47 @@ mod tests { block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 70.); + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 70.); } #[test] fn test_weird_wall_collision() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.add_entity( - 0, - EntityData::new( - Uuid::nil(), - Vec3 { - x: 0.5, - y: 73., - z: 0.5, - }, - EntityMetadata::Player(metadata::Player::default()), - ), + let mut app = make_test_app(); + let world_lock = app.world.resource_mut::<WorldContainer>().insert( + ResourceLocation::new("minecraft:overworld").unwrap(), + 384, + -64, + ); + let mut partial_world = PartialWorld::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut world_lock.write().chunks, ); - let block_state = world.set_block_state( + let entity = app + .world + .spawn(( + EntityBundle::new( + Uuid::nil(), + Vec3 { + x: 0.5, + y: 73., + z: 0.5, + }, + azalea_registry::EntityKind::Player, + ResourceLocation::new("minecraft:overworld").unwrap(), + ), + MinecraftEntityId(0), + Local, + )) + .id(); + let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, BlockState::CobblestoneWall_LowLowLowFalseFalseLow, ); @@ -397,11 +571,12 @@ mod tests { block_state.is_some(), "Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed" ); - let mut entity = world.entity_mut(0).unwrap(); // do a few steps so we fall on the slab for _ in 0..20 { - entity.ai_step(); + app.update(); } - assert_eq!(entity.pos().y, 70.5); + + let entity_pos = app.world.get::<Position>(entity).unwrap(); + assert_eq!(entity_pos.y, 70.5); } } diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 79a8913f..735c0397 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -22,6 +22,7 @@ azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0" } azalea-protocol-macros = {path = "./azalea-protocol-macros", version = "^0.5.0" } azalea-registry = {path = "../azalea-registry", version = "^0.5.0" } azalea-world = {path = "../azalea-world", version = "^0.5.0" } +bevy_ecs = { version = "0.9.1", default-features = false } byteorder = "^1.4.3" bytes = "^1.1.0" flate2 = "1.0.23" diff --git a/azalea-protocol/azalea-protocol-macros/src/lib.rs b/azalea-protocol/azalea-protocol-macros/src/lib.rs index 849a49e7..e04a2dbc 100755 --- a/azalea-protocol/azalea-protocol-macros/src/lib.rs +++ b/azalea-protocol/azalea-protocol-macros/src/lib.rs @@ -3,19 +3,17 @@ use quote::quote; use syn::{ self, braced, parse::{Parse, ParseStream, Result}, - parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token, + parse_macro_input, DeriveInput, Ident, LitInt, Token, }; fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); - let fields = match &data { - syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, - _ => panic!("#[derive(*Packet)] can only be used on structs"), + let syn::Data::Struct(syn::DataStruct { fields, .. }) = &data else { + panic!("#[derive(*Packet)] can only be used on structs") }; - let FieldsNamed { named: _, .. } = match fields { - syn::Fields::Named(f) => f, - _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), + let syn::Fields::Named(_) = fields else { + panic!("#[derive(*Packet)] can only be used on structs with named fields") }; let variant_name = variant_name_from(&ident); diff --git a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs index fcbd8fa5..75f3f4dc 100755 --- a/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_add_entity_packet.rs @@ -1,7 +1,7 @@ use azalea_buf::McBuf; -use azalea_core::Vec3; +use azalea_core::{ResourceLocation, Vec3}; use azalea_protocol_macros::ClientboundGamePacket; -use azalea_world::entity::{EntityData, EntityMetadata}; +use azalea_world::entity::{metadata::apply_default_metadata, EntityBundle}; use uuid::Uuid; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] @@ -10,10 +10,8 @@ pub struct ClientboundAddEntityPacket { #[var] pub id: u32, pub uuid: Uuid, - pub entity_type: azalea_registry::EntityType, - pub x: f64, - pub y: f64, - pub z: f64, + pub entity_type: azalea_registry::EntityKind, + pub position: Vec3, pub x_rot: i8, pub y_rot: i8, pub y_head_rot: i8, @@ -24,17 +22,31 @@ pub struct ClientboundAddEntityPacket { pub z_vel: i16, } -impl From<&ClientboundAddEntityPacket> for EntityData { - fn from(p: &ClientboundAddEntityPacket) -> Self { - Self::new( - p.uuid, - Vec3 { - x: p.x, - y: p.y, - z: p.z, - }, - // default metadata for the entity type - EntityMetadata::from(p.entity_type), - ) +// impl From<&ClientboundAddEntityPacket> for EntityData { +// fn from(p: &ClientboundAddEntityPacket) -> Self { +// Self::new( +// p.uuid, +// Vec3 { +// x: p.x, +// y: p.y, +// z: p.z, +// }, +// // default metadata for the entity type +// EntityMetadata::from(p.entity_type), +// ) +// } +// } + +impl ClientboundAddEntityPacket { + /// Make the entity into a bundle that can be inserted into the ECS. You + /// must apply the metadata after inserting the bundle with + /// [`Self::apply_metadata`]. + pub fn as_entity_bundle(&self, world_name: ResourceLocation) -> EntityBundle { + EntityBundle::new(self.uuid, self.position, self.entity_type, world_name) + } + + /// Apply the default metadata for the given entity. + pub fn apply_metadata(&self, entity: &mut bevy_ecs::system::EntityCommands) { + apply_default_metadata(entity, self.entity_type); } } diff --git a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs index 58c7b0a3..4cbeb1b9 100755 --- a/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_add_player_packet.rs @@ -1,7 +1,8 @@ use azalea_buf::McBuf; -use azalea_core::Vec3; +use azalea_core::{ResourceLocation, Vec3}; use azalea_protocol_macros::ClientboundGamePacket; -use azalea_world::entity::{metadata, EntityData, EntityMetadata}; +use azalea_registry::EntityKind; +use azalea_world::entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle}; use uuid::Uuid; /// This packet is sent by the server when a player comes into visible range, @@ -11,23 +12,16 @@ pub struct ClientboundAddPlayerPacket { #[var] pub id: u32, pub uuid: Uuid, - pub x: f64, - pub y: f64, - pub z: f64, + pub position: Vec3, pub x_rot: i8, pub y_rot: i8, } -impl From<&ClientboundAddPlayerPacket> for EntityData { - fn from(p: &ClientboundAddPlayerPacket) -> Self { - Self::new( - p.uuid, - Vec3 { - x: p.x, - y: p.y, - z: p.z, - }, - EntityMetadata::Player(metadata::Player::default()), - ) +impl ClientboundAddPlayerPacket { + pub fn as_player_bundle(&self, world_name: ResourceLocation) -> PlayerBundle { + PlayerBundle { + entity: EntityBundle::new(self.uuid, self.position, EntityKind::Player, world_name), + metadata: PlayerMetadataBundle::default(), + } } } diff --git a/azalea-protocol/src/packets/game/clientbound_award_stats_packet.rs b/azalea-protocol/src/packets/game/clientbound_award_stats_packet.rs index 2812987d..aaec1849 100755 --- a/azalea-protocol/src/packets/game/clientbound_award_stats_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_award_stats_packet.rs @@ -16,7 +16,7 @@ pub enum Stat { Broken(azalea_registry::Item), PickedUp(azalea_registry::Item), Dropped(azalea_registry::Item), - Killed(azalea_registry::EntityType), - KilledBy(azalea_registry::EntityType), + Killed(azalea_registry::EntityKind), + KilledBy(azalea_registry::EntityKind), Custom(azalea_registry::CustomStat), } diff --git a/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs index faa3b1a9..e3c95034 100755 --- a/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_block_entity_data_packet.rs @@ -5,6 +5,6 @@ use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundBlockEntityDataPacket { pub pos: BlockPos, - pub block_entity_type: azalea_registry::BlockEntityType, + pub block_entity_type: azalea_registry::BlockEntityKind, pub tag: azalea_nbt::Tag, } diff --git a/azalea-protocol/src/packets/game/clientbound_boss_event_packet.rs b/azalea-protocol/src/packets/game/clientbound_boss_event_packet.rs index 3ffbaea1..e73b55ba 100755 --- a/azalea-protocol/src/packets/game/clientbound_boss_event_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_boss_event_packet.rs @@ -1,7 +1,7 @@ use azalea_buf::{ BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, }; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_core::FixedBitSet; use azalea_protocol_macros::ClientboundGamePacket; use std::io::Cursor; @@ -19,7 +19,7 @@ pub enum Operation { Add(AddOperation), Remove, UpdateProgress(f32), - UpdateName(Component), + UpdateName(FormattedText), UpdateStyle(Style), UpdateProperties(Properties), } @@ -31,7 +31,7 @@ impl McBufReadable for Operation { 0 => Operation::Add(AddOperation::read_from(buf)?), 1 => Operation::Remove, 2 => Operation::UpdateProgress(f32::read_from(buf)?), - 3 => Operation::UpdateName(Component::read_from(buf)?), + 3 => Operation::UpdateName(FormattedText::read_from(buf)?), 4 => Operation::UpdateStyle(Style::read_from(buf)?), 5 => Operation::UpdateProperties(Properties::read_from(buf)?), _ => { @@ -76,7 +76,7 @@ impl McBufWritable for Operation { #[derive(Clone, Debug, McBuf)] pub struct AddOperation { - name: Component, + name: FormattedText, progress: f32, style: Style, properties: Properties, diff --git a/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs b/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs index 5a72ae33..40f28259 100755 --- a/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_chat_preview_packet.rs @@ -1,9 +1,9 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundChatPreviewPacket { pub query_id: i32, - pub preview: Option<Component>, + pub preview: Option<FormattedText>, } diff --git a/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs b/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs index 652ce78a..88c6f29e 100755 --- a/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_command_suggestions_packet.rs @@ -1,13 +1,13 @@ use azalea_brigadier::suggestion::Suggestions; use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundCommandSuggestionsPacket { #[var] pub id: u32, - pub suggestions: Suggestions<Component>, + pub suggestions: Suggestions<FormattedText>, } #[cfg(test)] @@ -24,7 +24,7 @@ mod tests { suggestions: vec![Suggestion { text: "foo".to_string(), range: StringRange::new(1, 4), - tooltip: Some(Component::from("bar".to_string())), + tooltip: Some(FormattedText::from("bar".to_string())), }], }; let mut buf = Vec::new(); diff --git a/azalea-protocol/src/packets/game/clientbound_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_commands_packet.rs index fa11a355..73dcbce7 100755 --- a/azalea-protocol/src/packets/game/clientbound_commands_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_commands_packet.rs @@ -114,7 +114,7 @@ pub enum BrigadierParser { ItemStack, ItemPredicate, Color, - Component, + FormattedText, Message, NbtCompoundTag, NbtTag, diff --git a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs index ecff0278..f8771c37 100755 --- a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs @@ -1,8 +1,8 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundDisconnectPacket { - pub reason: Component, + pub reason: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_disguised_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_disguised_chat_packet.rs index 701284a0..9aa9fd6f 100644 --- a/azalea-protocol/src/packets/game/clientbound_disguised_chat_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_disguised_chat_packet.rs @@ -1,10 +1,10 @@ use super::clientbound_player_chat_packet::ChatTypeBound; use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundDisguisedChatPacket { - pub message: Component, + pub message: FormattedText, pub chat_type: ChatTypeBound, } diff --git a/azalea-protocol/src/packets/game/clientbound_map_item_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_map_item_data_packet.rs index 38d92135..0f858181 100755 --- a/azalea-protocol/src/packets/game/clientbound_map_item_data_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_map_item_data_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::{McBuf, McBufReadable, McBufWritable}; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, ClientboundGamePacket, McBuf)] @@ -20,7 +20,7 @@ pub struct MapDecoration { /// Minecraft does & 15 on this value, azalea-protocol doesn't. I don't /// think it matters. pub rot: i8, - pub name: Option<Component>, + pub name: Option<FormattedText>, } #[derive(Debug, Clone)] diff --git a/azalea-protocol/src/packets/game/clientbound_open_screen_packet.rs b/azalea-protocol/src/packets/game/clientbound_open_screen_packet.rs index f127f587..9b8b02a1 100755 --- a/azalea-protocol/src/packets/game/clientbound_open_screen_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_open_screen_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] @@ -7,5 +7,5 @@ pub struct ClientboundOpenScreenPacket { #[var] pub container_id: u32, pub menu_type: azalea_registry::Menu, - pub title: Component, + pub title: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs index 098ffa03..fb805dea 100644 --- a/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_chat_packet.rs @@ -3,7 +3,7 @@ use azalea_buf::{ }; use azalea_chat::{ translatable_component::{StringOrComponent, TranslatableComponent}, - Component, + FormattedText, }; use azalea_core::BitSet; use azalea_crypto::MessageSignature; @@ -18,7 +18,7 @@ pub struct ClientboundPlayerChatPacket { pub index: u32, pub signature: Option<MessageSignature>, pub body: PackedSignedMessageBody, - pub unsigned_content: Option<Component>, + pub unsigned_content: Option<FormattedText>, pub filter_mask: FilterMask, pub chat_type: ChatTypeBound, } @@ -66,8 +66,8 @@ pub enum ChatType { #[derive(Clone, Debug, McBuf, PartialEq)] pub struct ChatTypeBound { pub chat_type: ChatType, - pub name: Component, - pub target_name: Option<Component>, + pub name: FormattedText, + pub target_name: Option<FormattedText>, } // must be in Client @@ -87,19 +87,19 @@ pub struct MessageSignatureCache { // {} } impl ClientboundPlayerChatPacket { - /// Returns the content of the message. If you want to get the Component + /// Returns the content of the message. If you want to get the FormattedText /// for the whole message including the sender part, use /// [`ClientboundPlayerChatPacket::message`]. #[must_use] - pub fn content(&self) -> Component { + pub fn content(&self) -> FormattedText { self.unsigned_content .clone() - .unwrap_or_else(|| Component::from(self.body.content.clone())) + .unwrap_or_else(|| FormattedText::from(self.body.content.clone())) } /// Get the full message, including the sender part. #[must_use] - pub fn message(&self) -> Component { + pub fn message(&self) -> FormattedText { let sender = self.chat_type.name.clone(); let content = self.content(); let target = self.chat_type.target_name.clone(); @@ -107,16 +107,16 @@ impl ClientboundPlayerChatPacket { let translation_key = self.chat_type.chat_type.chat_translation_key(); let mut args = vec![ - StringOrComponent::Component(sender), - StringOrComponent::Component(content), + StringOrComponent::FormattedText(sender), + StringOrComponent::FormattedText(content), ]; if let Some(target) = target { - args.push(StringOrComponent::Component(target)); + args.push(StringOrComponent::FormattedText(target)); } let component = TranslatableComponent::new(translation_key.to_string(), args); - Component::Translatable(component) + FormattedText::Translatable(component) } } diff --git a/azalea-protocol/src/packets/game/clientbound_player_combat_kill_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_combat_kill_packet.rs index fa547af1..eae96f65 100755 --- a/azalea-protocol/src/packets/game/clientbound_player_combat_kill_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_combat_kill_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; /// Used to send a respawn screen. @@ -8,5 +8,5 @@ pub struct ClientboundPlayerCombatKillPacket { #[var] pub player_id: u32, pub killer_id: u32, - pub message: Component, + pub message: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_player_info_update_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_info_update_packet.rs index 4fa16209..dc518c9c 100644 --- a/azalea-protocol/src/packets/game/clientbound_player_info_update_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_player_info_update_packet.rs @@ -2,7 +2,7 @@ use azalea_auth::game_profile::{GameProfile, ProfilePropertyValue}; use azalea_buf::{ BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, }; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_core::{FixedBitSet, GameType}; use azalea_protocol_macros::ClientboundGamePacket; use std::{ @@ -25,7 +25,7 @@ pub struct PlayerInfoEntry { pub listed: bool, pub latency: i32, pub game_mode: GameType, - pub display_name: Option<Component>, + pub display_name: Option<FormattedText>, pub chat_session: Option<RemoteChatSessionData>, } @@ -53,7 +53,7 @@ pub struct UpdateLatencyAction { } #[derive(Clone, Debug, McBuf)] pub struct UpdateDisplayNameAction { - pub display_name: Option<Component>, + pub display_name: Option<FormattedText>, } impl McBufReadable for ClientboundPlayerInfoUpdatePacket { diff --git a/azalea-protocol/src/packets/game/clientbound_resource_pack_packet.rs b/azalea-protocol/src/packets/game/clientbound_resource_pack_packet.rs index 73ade728..a545ff31 100755 --- a/azalea-protocol/src/packets/game/clientbound_resource_pack_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_resource_pack_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] @@ -7,5 +7,5 @@ pub struct ClientboundResourcePackPacket { pub url: String, pub hash: String, pub required: bool, - pub prompt: Option<Component>, + pub prompt: Option<FormattedText>, } diff --git a/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs index 16841e3a..4b2bf055 100755 --- a/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_server_data_packet.rs @@ -1,10 +1,10 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundServerDataPacket { - pub motd: Option<Component>, + pub motd: Option<FormattedText>, pub icon_base64: Option<String>, pub enforces_secure_chat: bool, } diff --git a/azalea-protocol/src/packets/game/clientbound_set_action_bar_text_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_action_bar_text_packet.rs index e279e33c..60b80fe9 100755 --- a/azalea-protocol/src/packets/game/clientbound_set_action_bar_text_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_action_bar_text_packet.rs @@ -1,8 +1,8 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundSetActionBarTextPacket { - pub text: Component, + pub text: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_set_objective_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_objective_packet.rs index 9e2da325..3809b5b6 100755 --- a/azalea-protocol/src/packets/game/clientbound_set_objective_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_objective_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; use std::io::{Cursor, Write}; @@ -48,7 +48,7 @@ impl McBufWritable for Method { #[derive(McBuf, Clone, Debug)] pub struct DisplayInfo { - pub display_name: Component, + pub display_name: FormattedText, pub render_type: RenderType, } diff --git a/azalea-protocol/src/packets/game/clientbound_set_player_team_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_player_team_packet.rs index 0b3c1e24..1a09aeb5 100755 --- a/azalea-protocol/src/packets/game/clientbound_set_player_team_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_player_team_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; -use azalea_chat::{style::ChatFormatting, Component}; +use azalea_chat::{style::ChatFormatting, FormattedText}; use azalea_protocol_macros::ClientboundGamePacket; use std::io::{Cursor, Write}; @@ -61,13 +61,13 @@ impl McBufWritable for Method { #[derive(McBuf, Clone, Debug)] pub struct Parameters { - pub display_name: Component, + pub display_name: FormattedText, pub options: u8, pub nametag_visibility: String, pub collision_rule: String, pub color: ChatFormatting, - pub player_prefix: Component, - pub player_suffix: Component, + pub player_prefix: FormattedText, + pub player_suffix: FormattedText, } type PlayerList = Vec<String>; diff --git a/azalea-protocol/src/packets/game/clientbound_set_subtitle_text_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_subtitle_text_packet.rs index b7d1e7a4..9b25ac05 100755 --- a/azalea-protocol/src/packets/game/clientbound_set_subtitle_text_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_subtitle_text_packet.rs @@ -1,8 +1,8 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundSetSubtitleTextPacket { - pub text: Component, + pub text: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_set_title_text_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_title_text_packet.rs index a9185e1a..fb00a4e5 100755 --- a/azalea-protocol/src/packets/game/clientbound_set_title_text_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_set_title_text_packet.rs @@ -1,8 +1,8 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundSetTitleTextPacket { - pub text: Component, + pub text: FormattedText, } 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 9fe03fb2..8a5823a0 100755 --- a/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_system_chat_packet.rs @@ -1,9 +1,9 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket, PartialEq)] pub struct ClientboundSystemChatPacket { - pub content: Component, + pub content: FormattedText, pub overlay: bool, } diff --git a/azalea-protocol/src/packets/game/clientbound_tab_list_packet.rs b/azalea-protocol/src/packets/game/clientbound_tab_list_packet.rs index 94f23241..47dd1ab2 100755 --- a/azalea-protocol/src/packets/game/clientbound_tab_list_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_tab_list_packet.rs @@ -1,9 +1,9 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundTabListPacket { - pub header: Component, - pub footer: Component, + pub header: FormattedText, + pub footer: FormattedText, } diff --git a/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs b/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs index 05a912ba..eceaa3aa 100755 --- a/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_teleport_entity_packet.rs @@ -1,13 +1,12 @@ use azalea_buf::McBuf; +use azalea_core::Vec3; use azalea_protocol_macros::ClientboundGamePacket; #[derive(Clone, Debug, McBuf, ClientboundGamePacket)] pub struct ClientboundTeleportEntityPacket { #[var] pub id: u32, - pub x: f64, - pub y: f64, - pub z: f64, + pub position: Vec3, pub y_rot: i8, pub x_rot: i8, pub on_ground: bool, 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 e2a46d38..038cdcf2 100755 --- a/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_update_advancements_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_core::{ResourceLocation, Slot}; use azalea_protocol_macros::ClientboundGamePacket; use std::collections::HashMap; @@ -23,8 +23,8 @@ pub struct Advancement { #[derive(Clone, Debug)] pub struct DisplayInfo { - pub title: Component, - pub description: Component, + pub title: FormattedText, + pub description: FormattedText, pub icon: Slot, pub frame: FrameType, pub show_toast: bool, @@ -128,8 +128,8 @@ mod tests { Advancement { parent_id: None, display: Some(DisplayInfo { - title: Component::from("title".to_string()), - description: Component::from("description".to_string()), + title: FormattedText::from("title".to_string()), + description: FormattedText::from("description".to_string()), icon: Slot::Empty, frame: FrameType::Task, show_toast: true, diff --git a/azalea-protocol/src/packets/game/serverbound_set_jigsaw_block_packet.rs b/azalea-protocol/src/packets/game/serverbound_set_jigsaw_block_packet.rs index 989009f4..dbc08b16 100755 --- a/azalea-protocol/src/packets/game/serverbound_set_jigsaw_block_packet.rs +++ b/azalea-protocol/src/packets/game/serverbound_set_jigsaw_block_packet.rs @@ -15,8 +15,7 @@ pub struct ServerboundSetJigsawBlockPacket { pub target: ResourceLocation, pub pool: ResourceLocation, pub final_state: String, - pub joint: String, /* TODO: Does JigsawBlockEntity$JointType::getSerializedName, may not be - * implemented */ + pub joint: String, } pub enum JointType { diff --git a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs index 983dc31b..31cd370d 100755 --- a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs @@ -1,8 +1,8 @@ use azalea_buf::McBuf; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundLoginPacket; #[derive(Clone, Debug, McBuf, ClientboundLoginPacket)] pub struct ClientboundLoginDisconnectPacket { - pub reason: Component, + pub reason: FormattedText, } diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs index efa6080c..40452c87 100755 --- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs +++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs @@ -1,5 +1,5 @@ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_protocol_macros::ClientboundStatusPacket; use serde::{Deserialize, Serialize}; use serde_json::{value::Serializer, Value}; @@ -28,7 +28,7 @@ pub struct Players { // the entire packet is just json, which is why it has deserialize #[derive(Clone, Debug, Serialize, Deserialize, ClientboundStatusPacket)] pub struct ClientboundStatusResponsePacket { - pub description: Component, + pub description: FormattedText, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub favicon: Option<String>, diff --git a/azalea-registry/Cargo.toml b/azalea-registry/Cargo.toml index dcab681b..5f1d7e3a 100755 --- a/azalea-registry/Cargo.toml +++ b/azalea-registry/Cargo.toml @@ -3,11 +3,12 @@ description = "Use Minecraft's registries." edition = "2021" license = "MIT" name = "azalea-registry" -version = "0.5.0" repository = "https://github.com/mat-1/azalea/tree/main/azalea-registry" +version = "0.5.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-buf = {path = "../azalea-buf", version = "^0.5.0" } -azalea-registry-macros = {path = "./azalea-registry-macros", version = "^0.5.0" } +azalea-buf = {path = "../azalea-buf", version = "^0.5.0"} +azalea-registry-macros = {path = "./azalea-registry-macros", version = "^0.5.0"} +enum-as-inner = "0.5.1" diff --git a/azalea-registry/azalea-registry-macros/src/lib.rs b/azalea-registry/azalea-registry-macros/src/lib.rs index 68f2330e..d72f24cf 100755 --- a/azalea-registry/azalea-registry-macros/src/lib.rs +++ b/azalea-registry/azalea-registry-macros/src/lib.rs @@ -128,7 +128,7 @@ pub fn registry(input: TokenStream) -> TokenStream { // Display that uses registry ids let mut display_items = quote! {}; - for item in input.items.iter() { + for item in &input.items { let name = &item.name; let id = &item.id; display_items.extend(quote! { diff --git a/azalea-registry/src/lib.rs b/azalea-registry/src/lib.rs index 7a0b9234..96c9ea26 100755 --- a/azalea-registry/src/lib.rs +++ b/azalea-registry/src/lib.rs @@ -1103,7 +1103,7 @@ registry!(Block, { ReinforcedDeepslate => "minecraft:reinforced_deepslate", }); -registry!(BlockEntityType, { +registry!(BlockEntityKind, { Furnace => "minecraft:furnace", Chest => "minecraft:chest", TrappedChest => "minecraft:trapped_chest", @@ -1144,7 +1144,7 @@ registry!(BlockEntityType, { ChiseledBookshelf => "minecraft:chiseled_bookshelf", }); -registry!(BlockPredicateType, { +registry!(BlockPredicateKind, { MatchingBlocks => "minecraft:matching_blocks", MatchingBlockTag => "minecraft:matching_block_tag", MatchingFluids => "minecraft:matching_fluids", @@ -1189,7 +1189,7 @@ registry!(ChunkStatus, { Full => "minecraft:full", }); -registry!(CommandArgumentType, { +registry!(CommandArgumentKind, { Bool => "brigadier:bool", Float => "brigadier:float", Double => "brigadier:double", @@ -1360,7 +1360,7 @@ registry!(Enchantment, { VanishingCurse => "minecraft:vanishing_curse", }); -registry!(EntityType, { +registry!(EntityKind, { Allay => "minecraft:allay", AreaEffectCloud => "minecraft:area_effect_cloud", ArmorStand => "minecraft:armor_stand", @@ -1482,7 +1482,7 @@ registry!(EntityType, { FishingBobber => "minecraft:fishing_bobber", }); -registry!(FloatProviderType, { +registry!(FloatProviderKind, { Constant => "minecraft:constant", Uniform => "minecraft:uniform", ClampedNormal => "minecraft:clamped_normal", @@ -1552,7 +1552,7 @@ registry!(GameEvent, { Teleport => "minecraft:teleport", }); -registry!(HeightProviderType, { +registry!(HeightProviderKind, { Constant => "minecraft:constant", Uniform => "minecraft:uniform", BiasedToBottom => "minecraft:biased_to_bottom", @@ -1572,7 +1572,7 @@ registry!(Instrument, { DreamGoatHorn => "minecraft:dream_goat_horn", }); -registry!(IntProviderType, { +registry!(IntProviderKind, { Constant => "minecraft:constant", Uniform => "minecraft:uniform", BiasedToBottom => "minecraft:biased_to_bottom", @@ -2770,7 +2770,7 @@ registry!(Item, { EchoShard => "minecraft:echo_shard", }); -registry!(LootConditionType, { +registry!(LootConditionKind, { Inverted => "minecraft:inverted", Alternative => "minecraft:alternative", RandomChance => "minecraft:random_chance", @@ -2790,7 +2790,7 @@ registry!(LootConditionType, { ValueCheck => "minecraft:value_check", }); -registry!(LootFunctionType, { +registry!(LootFunctionKind, { SetCount => "minecraft:set_count", EnchantWithLevels => "minecraft:enchant_with_levels", EnchantRandomly => "minecraft:enchant_randomly", @@ -2818,19 +2818,19 @@ registry!(LootFunctionType, { SetInstrument => "minecraft:set_instrument", }); -registry!(LootNbtProviderType, { +registry!(LootNbtProviderKind, { Storage => "minecraft:storage", Context => "minecraft:context", }); -registry!(LootNumberProviderType, { +registry!(LootNumberProviderKind, { Constant => "minecraft:constant", Uniform => "minecraft:uniform", Binomial => "minecraft:binomial", Score => "minecraft:score", }); -registry!(LootPoolEntryType, { +registry!(LootPoolEntryKind, { Empty => "minecraft:empty", Item => "minecraft:item", LootTable => "minecraft:loot_table", @@ -2841,12 +2841,12 @@ registry!(LootPoolEntryType, { Group => "minecraft:group", }); -registry!(LootScoreProviderType, { +registry!(LootScoreProviderKind, { Fixed => "minecraft:fixed", Context => "minecraft:context", }); -registry!(MemoryModuleType, { +registry!(MemoryModuleKind, { Dummy => "minecraft:dummy", Home => "minecraft:home", JobSite => "minecraft:job_site", @@ -3038,7 +3038,7 @@ registry!(PaintingVariant, { DonkeyKong => "minecraft:donkey_kong", }); -registry!(ParticleType, { +registry!(ParticleKind, { AmbientEntityEffect => "minecraft:ambient_entity_effect", AngryVillager => "minecraft:angry_villager", Block => "minecraft:block", @@ -3134,7 +3134,7 @@ registry!(ParticleType, { Shriek => "minecraft:shriek", }); -registry!(PointOfInterestType, { +registry!(PointOfInterestKind, { Armorer => "minecraft:armorer", Butcher => "minecraft:butcher", Cartographer => "minecraft:cartographer", @@ -3163,7 +3163,7 @@ registry!(PosRuleTest, { AxisAlignedLinearPos => "minecraft:axis_aligned_linear_pos", }); -registry!(PositionSourceType, { +registry!(PositionSourceKind, { Block => "minecraft:block", Entity => "minecraft:entity", }); @@ -3238,7 +3238,7 @@ registry!(RecipeSerializer, { Smithing => "minecraft:smithing", }); -registry!(RecipeType, { +registry!(RecipeKind, { Crafting => "minecraft:crafting", Smelting => "minecraft:smelting", Blasting => "minecraft:blasting", @@ -3264,7 +3264,7 @@ registry!(Schedule, { VillagerDefault => "minecraft:villager_default", }); -registry!(SensorType, { +registry!(SensorKind, { Dummy => "minecraft:dummy", NearestItems => "minecraft:nearest_items", NearestLivingEntities => "minecraft:nearest_living_entities", @@ -4684,7 +4684,7 @@ registry!(SoundEvent, { EntityZombieVillagerStep => "minecraft:entity.zombie_villager.step", }); -registry!(StatType, { +registry!(StatKind, { Mined => "minecraft:mined", Crafted => "minecraft:crafted", Used => "minecraft:used", @@ -4714,7 +4714,7 @@ registry!(VillagerProfession, { Weaponsmith => "minecraft:weaponsmith", }); -registry!(VillagerType, { +registry!(VillagerKind, { Desert => "minecraft:desert", Jungle => "minecraft:jungle", Plains => "minecraft:plains", @@ -4731,7 +4731,7 @@ registry!(WorldgenBiomeSource, { TheEnd => "minecraft:the_end", }); -registry!(WorldgenBlockStateProviderType, { +registry!(WorldgenBlockStateProviderKind, { SimpleStateProvider => "minecraft:simple_state_provider", WeightedStateProvider => "minecraft:weighted_state_provider", NoiseThresholdProvider => "minecraft:noise_threshold_provider", @@ -4753,7 +4753,7 @@ registry!(WorldgenChunkGenerator, { Debug => "minecraft:debug", }); -registry!(WorldgenDensityFunctionType, { +registry!(WorldgenDensityFunctionKind, { BlendAlpha => "minecraft:blend_alpha", BlendOffset => "minecraft:blend_offset", Beardifier => "minecraft:beardifier", @@ -4852,12 +4852,12 @@ registry!(WorldgenFeature, { SculkPatch => "minecraft:sculk_patch", }); -registry!(WorldgenFeatureSizeType, { +registry!(WorldgenFeatureSizeKind, { TwoLayersFeatureSize => "minecraft:two_layers_feature_size", ThreeLayersFeatureSize => "minecraft:three_layers_feature_size", }); -registry!(WorldgenFoliagePlacerType, { +registry!(WorldgenFoliagePlacerKind, { BlobFoliagePlacer => "minecraft:blob_foliage_placer", SpruceFoliagePlacer => "minecraft:spruce_foliage_placer", PineFoliagePlacer => "minecraft:pine_foliage_placer", @@ -4891,7 +4891,7 @@ registry!(WorldgenMaterialRule, { Condition => "minecraft:condition", }); -registry!(WorldgenPlacementModifierType, { +registry!(WorldgenPlacementModifierKind, { BlockPredicateFilter => "minecraft:block_predicate_filter", RarityFilter => "minecraft:rarity_filter", SurfaceRelativeThresholdFilter => "minecraft:surface_relative_threshold_filter", @@ -4909,7 +4909,7 @@ registry!(WorldgenPlacementModifierType, { CarvingMask => "minecraft:carving_mask", }); -registry!(WorldgenRootPlacerType, { +registry!(WorldgenRootPlacerKind, { MangroveRootPlacer => "minecraft:mangrove_root_placer", }); @@ -4998,7 +4998,7 @@ registry!(WorldgenStructureProcessor, { ProtectedBlocks => "minecraft:protected_blocks", }); -registry!(WorldgenStructureType, { +registry!(WorldgenStructureKind, { BuriedTreasure => "minecraft:buried_treasure", DesertPyramid => "minecraft:desert_pyramid", EndCity => "minecraft:end_city", @@ -5017,7 +5017,7 @@ registry!(WorldgenStructureType, { WoodlandMansion => "minecraft:woodland_mansion", }); -registry!(WorldgenTreeDecoratorType, { +registry!(WorldgenTreeDecoratorKind, { TrunkVine => "minecraft:trunk_vine", LeaveVine => "minecraft:leave_vine", Cocoa => "minecraft:cocoa", @@ -5026,7 +5026,7 @@ registry!(WorldgenTreeDecoratorType, { AttachedToLeaves => "minecraft:attached_to_leaves", }); -registry!(WorldgenTrunkPlacerType, { +registry!(WorldgenTrunkPlacerKind, { StraightTrunkPlacer => "minecraft:straight_trunk_placer", ForkingTrunkPlacer => "minecraft:forking_trunk_placer", GiantTrunkPlacer => "minecraft:giant_trunk_placer", diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 00d81ee1..4ddbd54f 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -9,15 +9,19 @@ version = "0.5.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-block = {path = "../azalea-block", default-features = false, version = "^0.5.0" } -azalea-buf = {path = "../azalea-buf", version = "^0.5.0" } -azalea-chat = {path = "../azalea-chat", version = "^0.5.0" } -azalea-core = {path = "../azalea-core", version = "^0.5.0" } -azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0" } -azalea-registry = {path = "../azalea-registry", version = "^0.5.0" } +azalea-block = {path = "../azalea-block", default-features = false, version = "^0.5.0"} +azalea-buf = {path = "../azalea-buf", version = "^0.5.0"} +azalea-chat = {path = "../azalea-chat", version = "^0.5.0"} +azalea-core = {path = "../azalea-core", version = "^0.5.0", features = ["bevy_ecs"]} +azalea-ecs = { version = "0.5.0", path = "../azalea-ecs" } +azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"} +azalea-registry = {path = "../azalea-registry", version = "^0.5.0"} +derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} enum-as-inner = "0.5.1" +iyes_loopless = "0.9.1" log = "0.4.17" nohash-hasher = "0.2.0" +once_cell = "1.16.0" parking_lot = "^0.12.1" thiserror = "1.0.34" uuid = "1.1.2" diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index c0c5f43a..734dfe29 100755 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -15,13 +15,10 @@ use std::{ const SECTION_HEIGHT: u32 = 16; /// An efficient storage of chunks for a client that has a limited render -/// distance. This has support for using a shared [`WeakChunkStorage`]. If you -/// have an infinite render distance (like a server), you should use -/// [`ChunkStorage`] instead. +/// distance. This has support for using a shared [`ChunkStorage`]. pub struct PartialChunkStorage { - /// Chunk storage that can be shared by clients. - pub shared: Arc<RwLock<WeakChunkStorage>>, - + /// The center of the view, i.e. the chunk the player is currently in. You + /// can safely modify this. pub view_center: ChunkPos, chunk_radius: u32, view_range: u32, @@ -33,23 +30,16 @@ pub struct PartialChunkStorage { /// actively being used somewhere else they'll be forgotten. This is used for /// shared worlds. #[derive(Debug)] -pub struct WeakChunkStorage { - pub height: u32, - pub min_y: i32, - pub chunks: HashMap<ChunkPos, Weak<RwLock<Chunk>>>, -} - -/// A storage of potentially infinite chunks in a world. Chunks are stored as -/// an `Arc<Mutex>` so they can be shared across threads. pub struct ChunkStorage { pub height: u32, pub min_y: i32, - pub chunks: HashMap<ChunkPos, Arc<RwLock<Chunk>>>, + pub chunks: HashMap<ChunkPos, Weak<RwLock<Chunk>>>, } /// A single chunk in a world (16*?*16 blocks). This only contains the blocks /// and biomes. You can derive the height of the chunk from the number of -/// sections, but you need a [`ChunkStorage`] to get the minimum Y coordinate. +/// sections, but you need a [`ChunkStorage`] to get the minimum Y +/// coordinate. #[derive(Debug)] pub struct Chunk { pub sections: Vec<Section>, @@ -82,10 +72,9 @@ impl Default for Chunk { } impl PartialChunkStorage { - pub fn new(chunk_radius: u32, shared: Arc<RwLock<WeakChunkStorage>>) -> Self { + pub fn new(chunk_radius: u32) -> Self { let view_range = chunk_radius * 2 + 1; PartialChunkStorage { - shared, view_center: ChunkPos::new(0, 0), chunk_radius, view_range, @@ -93,13 +82,6 @@ impl PartialChunkStorage { } } - pub fn min_y(&self) -> i32 { - self.shared.read().min_y - } - pub fn height(&self) -> u32 { - self.shared.read().height - } - fn get_index(&self, chunk_pos: &ChunkPos) -> usize { (i32::rem_euclid(chunk_pos.x, self.view_range as i32) * (self.view_range as i32) + i32::rem_euclid(chunk_pos.z, self.view_range as i32)) as usize @@ -110,20 +92,28 @@ impl PartialChunkStorage { && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius } - pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> { - if pos.y < self.min_y() || pos.y >= (self.min_y() + self.height() as i32) { + pub fn set_block_state( + &self, + pos: &BlockPos, + state: BlockState, + chunk_storage: &mut ChunkStorage, + ) -> Option<BlockState> { + if pos.y < chunk_storage.min_y + || pos.y >= (chunk_storage.min_y + chunk_storage.height as i32) + { return None; } let chunk_pos = ChunkPos::from(pos); - let chunk = self.get(&chunk_pos)?; - let mut chunk = chunk.write(); - Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y())) + let chunk_lock = chunk_storage.get(&chunk_pos)?; + let mut chunk = chunk_lock.write(); + Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y)) } pub fn replace_with_packet_data( &mut self, pos: &ChunkPos, data: &mut Cursor<&[u8]>, + chunk_storage: &mut ChunkStorage, ) -> Result<(), BufReadError> { debug!("Replacing chunk at {:?}", pos); if !self.in_range(pos) { @@ -135,19 +125,16 @@ impl PartialChunkStorage { return Ok(()); } - let chunk = Arc::new(RwLock::new(Chunk::read_with_dimension_height( - data, - self.height(), - )?)); + let chunk = Chunk::read_with_dimension_height(data, chunk_storage.height)?; trace!("Loaded chunk {:?}", pos); - self.set(pos, Some(chunk)); + self.set(pos, Some(chunk), chunk_storage); Ok(()) } /// Get a [`Chunk`] within render distance, or `None` if it's not loaded. - /// Use [`PartialChunkStorage::get`] to get a chunk from the shared storage. + /// Use [`ChunkStorage::get`] to get a chunk from the shared storage. pub fn limited_get(&self, pos: &ChunkPos) -> Option<&Arc<RwLock<Chunk>>> { if !self.in_range(pos) { warn!( @@ -161,7 +148,7 @@ impl PartialChunkStorage { self.chunks[index].as_ref() } /// Get a mutable reference to a [`Chunk`] within render distance, or - /// `None` if it's not loaded. Use [`PartialChunkStorage::get`] to get + /// `None` if it's not loaded. Use [`ChunkStorage::get`] to get /// a chunk from the shared storage. pub fn limited_get_mut(&mut self, pos: &ChunkPos) -> Option<&mut Option<Arc<RwLock<Chunk>>>> { if !self.in_range(pos) { @@ -172,26 +159,30 @@ impl PartialChunkStorage { Some(&mut self.chunks[index]) } - /// Get a chunk, - pub fn get(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> { - self.shared - .read() - .chunks - .get(pos) - .and_then(|chunk| chunk.upgrade()) + /// Set a chunk in the shared storage and reference it from the limited + /// storage. Use [`Self::set_with_shared_reference`] if you already have + /// an `Arc<RwLock<Chunk>>`. + /// + /// # Panics + /// If the chunk is not in the render distance. + pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) { + self.set_with_shared_reference(pos, chunk.map(|c| Arc::new(RwLock::new(c))), chunk_storage); } /// Set a chunk in the shared storage and reference it from the limited - /// storage. + /// storage. Use [`Self::set`] if you don't already have an + /// `Arc<RwLock<Chunk>>` (it'll make it for you). /// /// # Panics /// If the chunk is not in the render distance. - pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) { + pub fn set_with_shared_reference( + &mut self, + pos: &ChunkPos, + chunk: Option<Arc<RwLock<Chunk>>>, + chunk_storage: &mut ChunkStorage, + ) { if let Some(chunk) = &chunk { - self.shared - .write() - .chunks - .insert(*pos, Arc::downgrade(chunk)); + chunk_storage.chunks.insert(*pos, Arc::downgrade(chunk)); } else { // don't remove it from the shared storage, since it'll be removed // automatically if this was the last reference @@ -201,9 +192,9 @@ impl PartialChunkStorage { } } } -impl WeakChunkStorage { +impl ChunkStorage { pub fn new(height: u32, min_y: i32) -> Self { - WeakChunkStorage { + ChunkStorage { height, min_y, chunks: HashMap::new(), @@ -280,7 +271,7 @@ impl Chunk { // TODO: make sure the section exists let section = &mut self.sections[section_index as usize]; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - section.set(chunk_section_pos, state) + section.set(chunk_section_pos, state); } } @@ -295,12 +286,10 @@ impl McBufWritable for Chunk { impl Debug for PartialChunkStorage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ChunkStorage") + f.debug_struct("PartialChunkStorage") .field("view_center", &self.view_center) .field("chunk_radius", &self.chunk_radius) .field("view_range", &self.view_range) - .field("height", &self.height()) - .field("min_y", &self.min_y()) // .field("chunks", &self.chunks) .field("chunks", &format_args!("{} items", self.chunks.len())) .finish() @@ -373,10 +362,10 @@ impl Section { impl Default for PartialChunkStorage { fn default() -> Self { - Self::new(8, Arc::new(RwLock::new(WeakChunkStorage::default()))) + Self::new(8) } } -impl Default for WeakChunkStorage { +impl Default for ChunkStorage { fn default() -> Self { Self::new(384, -64) } @@ -408,34 +397,26 @@ mod tests { #[test] fn test_out_of_bounds_y() { - let mut chunk_storage = PartialChunkStorage::default(); - chunk_storage.set( + let mut chunk_storage = ChunkStorage::default(); + let mut partial_chunk_storage = PartialChunkStorage::default(); + partial_chunk_storage.set( &ChunkPos { x: 0, z: 0 }, - Some(Arc::new(RwLock::new(Chunk::default()))), + Some(Chunk::default()), + &mut chunk_storage, ); assert!(chunk_storage - .shared - .read() .get_block_state(&BlockPos { x: 0, y: 319, z: 0 }) .is_some()); assert!(chunk_storage - .shared - .read() .get_block_state(&BlockPos { x: 0, y: 320, z: 0 }) .is_none()); assert!(chunk_storage - .shared - .read() .get_block_state(&BlockPos { x: 0, y: 338, z: 0 }) .is_none()); assert!(chunk_storage - .shared - .read() .get_block_state(&BlockPos { x: 0, y: -64, z: 0 }) .is_some()); assert!(chunk_storage - .shared - .read() .get_block_state(&BlockPos { x: 0, y: -65, z: 0 }) .is_none()); } diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index acdc9b05..74d70659 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -1,52 +1,76 @@ -use crate::WeakWorld; use azalea_core::ResourceLocation; +use azalea_ecs::system::Resource; use log::error; +use nohash_hasher::IntMap; +use parking_lot::RwLock; use std::{ collections::HashMap, sync::{Arc, Weak}, }; -/// A container of [`WeakWorld`]s. Worlds are stored as a Weak pointer here, so +use crate::{ChunkStorage, World}; + +/// A container of [`World`]s. Worlds are stored as a Weak pointer here, so /// if no clients are using a world it will be forgotten. -#[derive(Default)] -pub struct WeakWorldContainer { - pub worlds: HashMap<ResourceLocation, Weak<WeakWorld>>, +#[derive(Default, Resource)] +pub struct WorldContainer { + // We just refer to the chunks here and don't include entities because there's not that many + // cases where we'd want to get every entity in the world (just getting the entities in chunks + // should work fine). + + // Entities are garbage collected (by manual reference counting in EntityInfos) so we don't + // need to worry about them here. + + // If it looks like we're relying on the server giving us unique world names, that's because we + // are. An evil server could give us two worlds with the same name and then we'd have no way of + // 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 worlds: HashMap<ResourceLocation, Weak<RwLock<World>>>, } -impl WeakWorldContainer { +impl WorldContainer { pub fn new() -> Self { - WeakWorldContainer { + WorldContainer { worlds: HashMap::new(), } } /// Get a world from the container. - pub fn get(&self, name: &ResourceLocation) -> Option<Arc<WeakWorld>> { + pub fn get(&self, name: &ResourceLocation) -> Option<Arc<RwLock<World>>> { self.worlds.get(name).and_then(|world| world.upgrade()) } /// Add an empty world to the container (or not if it already exists) and /// returns a strong reference to the world. #[must_use = "the world will be immediately forgotten if unused"] - pub fn insert(&mut self, name: ResourceLocation, height: u32, min_y: i32) -> Arc<WeakWorld> { - if let Some(existing) = self.worlds.get(&name).and_then(|world| world.upgrade()) { - if existing.height() != height { + pub fn insert( + &mut self, + name: ResourceLocation, + height: u32, + min_y: i32, + ) -> Arc<RwLock<World>> { + if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) { + let existing = existing_lock.read(); + if existing.chunks.height != height { error!( "Shared dimension height mismatch: {} != {}", - existing.height(), - height, + existing.chunks.height, height, ); } - if existing.min_y() != min_y { + if existing.chunks.min_y != min_y { error!( "Shared world min_y mismatch: {} != {}", - existing.min_y(), - min_y, + existing.chunks.min_y, min_y, ); } - existing + existing_lock.clone() } else { - let world = Arc::new(WeakWorld::new(height, min_y)); + let world = Arc::new(RwLock::new(World { + chunks: ChunkStorage::new(height, min_y), + entities_by_chunk: HashMap::new(), + entity_by_id: IntMap::default(), + })); self.worlds.insert(name, Arc::downgrade(&world)); world } diff --git a/azalea-world/src/entity/attributes.rs b/azalea-world/src/entity/attributes.rs index fca6b88f..fd78a328 100644 --- a/azalea-world/src/entity/attributes.rs +++ b/azalea-world/src/entity/attributes.rs @@ -6,11 +6,12 @@ use std::{ }; use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable}; +use azalea_ecs::component::Component; use thiserror::Error; use uuid::{uuid, Uuid}; -#[derive(Clone, Debug)] -pub struct AttributeModifiers { +#[derive(Clone, Debug, Component)] +pub struct Attributes { pub speed: AttributeInstance, } @@ -41,7 +42,7 @@ impl AttributeInstance { _ => {} } if let AttributeModifierOperation::MultiplyTotal = modifier.operation { - total *= 1.0 + modifier.amount + total *= 1.0 + modifier.amount; } } total diff --git a/azalea-world/src/entity/data.rs b/azalea-world/src/entity/data.rs index baebd210..14d257e3 100755 --- a/azalea-world/src/entity/data.rs +++ b/azalea-world/src/entity/data.rs @@ -1,15 +1,20 @@ +//! Define some types needed for entity metadata. + use azalea_block::BlockState; -use azalea_buf::{BufReadError, McBufVarReadable, McBufVarWritable}; -use azalea_buf::{McBuf, McBufReadable, McBufWritable}; -use azalea_chat::Component; +use azalea_buf::{ + BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable, +}; +use azalea_chat::FormattedText; use azalea_core::{BlockPos, Direction, GlobalPos, Particle, Slot}; +use azalea_ecs::component::Component; +use derive_more::Deref; use enum_as_inner::EnumAsInner; use nohash_hasher::IntSet; use std::io::{Cursor, Write}; use uuid::Uuid; -#[derive(Clone, Debug)] -pub struct EntityMetadataItems(pub Vec<EntityDataItem>); +#[derive(Clone, Debug, Deref)] +pub struct EntityMetadataItems(Vec<EntityDataItem>); #[derive(Clone, Debug)] pub struct EntityDataItem { @@ -52,8 +57,8 @@ pub enum EntityDataValue { Long(i64), Float(f32), String(String), - Component(Component), - OptionalComponent(Option<Component>), + FormattedText(FormattedText), + OptionalFormattedText(Option<FormattedText>), ItemStack(Slot), Boolean(bool), Rotations(Rotations), @@ -105,7 +110,7 @@ pub struct Rotations { pub z: f32, } -#[derive(Clone, Debug, Copy, McBuf, Default)] +#[derive(Clone, Debug, Copy, McBuf, Default, Component)] pub enum Pose { #[default] Standing = 0, @@ -120,7 +125,7 @@ pub enum Pose { #[derive(Debug, Clone, McBuf)] pub struct VillagerData { - pub kind: azalea_registry::VillagerType, + pub kind: azalea_registry::VillagerKind, pub profession: azalea_registry::VillagerProfession, #[var] pub level: u32, diff --git a/azalea-world/src/entity/dimensions.rs b/azalea-world/src/entity/dimensions.rs index 1d013d10..daf85432 100755 --- a/azalea-world/src/entity/dimensions.rs +++ b/azalea-world/src/entity/dimensions.rs @@ -1,4 +1,7 @@ use azalea_core::{Vec3, AABB}; +use azalea_ecs::{query::Changed, system::Query}; + +use super::{Physics, Position}; #[derive(Debug, Default)] pub struct EntityDimensions { @@ -21,3 +24,15 @@ impl EntityDimensions { } } } + +/// Sets the position of the entity. This doesn't update the cache in +/// azalea-world, and should only be used within azalea-world! +/// +/// # Safety +/// Cached position in the world must be updated. +pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) { + for (position, mut physics) in query.iter_mut() { + let bounding_box = physics.dimensions.make_bounding_box(position); + physics.bounding_box = bounding_box; + } +} diff --git a/azalea-world/src/entity/metadata.rs b/azalea-world/src/entity/metadata.rs index 44ec6dae..c95d8c3a 100644 --- a/azalea-world/src/entity/metadata.rs +++ b/azalea-world/src/entity/metadata.rs @@ -1,8758 +1,10682 @@ +#![allow(clippy::single_match)] + // This file is generated from codegen/lib/code/entity.py. // Don't change it manually! -#![allow(clippy::clone_on_copy, clippy::derivable_impls)] -use super::{EntityDataValue, OptionalUnsignedInt, Pose, Rotations, VillagerData}; +use super::{EntityDataItem, EntityDataValue, OptionalUnsignedInt, Pose, Rotations, VillagerData}; use azalea_block::BlockState; -use azalea_chat::Component; +use azalea_chat::FormattedText; use azalea_core::{BlockPos, Direction, Particle, Slot}; -use enum_as_inner::EnumAsInner; -use std::{ - collections::VecDeque, - ops::{Deref, DerefMut}, -}; +use azalea_ecs::{bundle::Bundle, component::Component}; +use derive_more::{Deref, DerefMut}; +use thiserror::Error; use uuid::Uuid; -#[derive(Debug, Clone)] -pub struct Allay { - pub abstract_creature: AbstractCreature, - pub dancing: bool, - pub can_duplicate: bool, -} - -impl Allay { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let dancing = metadata.pop_front()?.into_boolean().ok()?; - let can_duplicate = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_creature, - dancing, - can_duplicate, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.dancing.clone())); - metadata.push(EntityDataValue::Boolean(self.can_duplicate.clone())); - metadata - } -} - -impl Default for Allay { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - dancing: false, - can_duplicate: true, - } - } -} - +#[derive(Error, Debug)] +pub enum UpdateMetadataError { + #[error("Wrong type ({0:?})")] + WrongType(EntityDataValue), +} +impl From<EntityDataValue> for UpdateMetadataError { + fn from(value: EntityDataValue) -> Self { + Self::WrongType(value) + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct OnFire(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ShiftKeyDown(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Sprinting(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Swimming(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CurrentlyGlowing(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Invisible(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct FallFlying(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct AirSupply(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct CustomName(pub Option<FormattedText>); +#[derive(Component, Deref, DerefMut)] +pub struct CustomNameVisible(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Silent(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct NoGravity(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TicksFrozen(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct AutoSpinAttack(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct AbstractLivingUsingItem(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Health(pub f32); +#[derive(Component, Deref, DerefMut)] +pub struct AbstractLivingEffectColor(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct EffectAmbience(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ArrowCount(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct StingerCount(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct SleepingPos(pub Option<BlockPos>); +#[derive(Component, Deref, DerefMut)] +pub struct NoAi(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LeftHanded(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Aggressive(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Dancing(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CanDuplicate(pub bool); +#[derive(Component)] +pub struct Allay; impl Allay { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.dancing = value.into_boolean().ok()?, - 17 => self.can_duplicate = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(Dancing(d.value.into_boolean()?)); + } + 17 => { + entity.insert(CanDuplicate(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Allay { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Allay { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct AreaEffectCloud { - pub abstract_entity: AbstractEntity, - pub radius: f32, - pub color: i32, - pub waiting: bool, - pub particle: Particle, -} - -impl AreaEffectCloud { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let radius = metadata.pop_front()?.into_float().ok()?; - let color = metadata.pop_front()?.into_int().ok()?; - let waiting = metadata.pop_front()?.into_boolean().ok()?; - let particle = metadata.pop_front()?.into_particle().ok()?; - Some(Self { - abstract_entity, - radius, - color, - waiting, - particle, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Float(self.radius.clone())); - metadata.push(EntityDataValue::Int(self.color.clone())); - metadata.push(EntityDataValue::Boolean(self.waiting.clone())); - metadata.push(EntityDataValue::Particle(self.particle.clone())); - metadata - } -} - -impl Default for AreaEffectCloud { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - radius: 3.0, - color: 0, - waiting: false, - particle: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AllayMetadataBundle { + _marker: Allay, + parent: AbstractCreatureMetadataBundle, + dancing: Dancing, + can_duplicate: CanDuplicate, +} +impl Default for AllayMetadataBundle { + fn default() -> Self { + Self { + _marker: Allay, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + dancing: Dancing(false), + can_duplicate: CanDuplicate(true), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Radius(pub f32); +#[derive(Component, Deref, DerefMut)] +pub struct AreaEffectCloudColor(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Waiting(pub bool); +#[derive(Component)] +pub struct AreaEffectCloud; impl AreaEffectCloud { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.radius = value.into_float().ok()?, - 9 => self.color = value.into_int().ok()?, - 10 => self.waiting = value.into_boolean().ok()?, - 11 => self.particle = value.into_particle().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(Radius(d.value.into_float()?)); + } + 9 => { + entity.insert(AreaEffectCloudColor(d.value.into_int()?)); + } + 10 => { + entity.insert(Waiting(d.value.into_boolean()?)); + } + 11 => { + entity.insert(d.value.into_particle()?); + } _ => {} } - Some(()) - } -} -impl Deref for AreaEffectCloud { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for AreaEffectCloud { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct ArmorStand { - pub abstract_living: AbstractLiving, - pub small: bool, - pub show_arms: bool, - pub no_base_plate: bool, - pub marker: bool, - pub head_pose: Rotations, - pub body_pose: Rotations, - pub left_arm_pose: Rotations, - pub right_arm_pose: Rotations, - pub left_leg_pose: Rotations, - pub right_leg_pose: Rotations, -} - -impl ArmorStand { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_living = AbstractLiving::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let small = bitfield & 0x1 != 0; - let show_arms = bitfield & 0x4 != 0; - let no_base_plate = bitfield & 0x8 != 0; - let marker = bitfield & 0x10 != 0; - let head_pose = metadata.pop_front()?.into_rotations().ok()?; - let body_pose = metadata.pop_front()?.into_rotations().ok()?; - let left_arm_pose = metadata.pop_front()?.into_rotations().ok()?; - let right_arm_pose = metadata.pop_front()?.into_rotations().ok()?; - let left_leg_pose = metadata.pop_front()?.into_rotations().ok()?; - let right_leg_pose = metadata.pop_front()?.into_rotations().ok()?; - Some(Self { - abstract_living, - small, - show_arms, - no_base_plate, - marker, - head_pose, - body_pose, - left_arm_pose, - right_arm_pose, - left_leg_pose, - right_leg_pose, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_living.write()); - let mut bitfield = 0u8; - if self.small { - bitfield &= 0x1; - } - if self.show_arms { - bitfield &= 0x4; - } - if self.no_base_plate { - bitfield &= 0x8; - } - if self.marker { - bitfield &= 0x10; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Rotations(self.head_pose.clone())); - metadata.push(EntityDataValue::Rotations(self.body_pose.clone())); - metadata.push(EntityDataValue::Rotations(self.left_arm_pose.clone())); - metadata.push(EntityDataValue::Rotations(self.right_arm_pose.clone())); - metadata.push(EntityDataValue::Rotations(self.left_leg_pose.clone())); - metadata.push(EntityDataValue::Rotations(self.right_leg_pose.clone())); - metadata - } -} - -impl Default for ArmorStand { - fn default() -> Self { - Self { - abstract_living: Default::default(), - small: false, - show_arms: false, - no_base_plate: false, - marker: false, - head_pose: Default::default(), - body_pose: Default::default(), - left_arm_pose: Default::default(), - right_arm_pose: Default::default(), - left_leg_pose: Default::default(), - right_leg_pose: Default::default(), - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AreaEffectCloudMetadataBundle { + _marker: AreaEffectCloud, + parent: AbstractEntityMetadataBundle, + radius: Radius, + area_effect_cloud_color: AreaEffectCloudColor, + waiting: Waiting, + particle: Particle, +} +impl Default for AreaEffectCloudMetadataBundle { + fn default() -> Self { + Self { + _marker: AreaEffectCloud, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + radius: Radius(3.0), + area_effect_cloud_color: AreaEffectCloudColor(0), + waiting: Waiting(false), + particle: Particle::default(), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct Small(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ShowArms(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct NoBasePlate(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ArmorStandMarker(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HeadPose(pub Rotations); +#[derive(Component, Deref, DerefMut)] +pub struct BodyPose(pub Rotations); +#[derive(Component, Deref, DerefMut)] +pub struct LeftArmPose(pub Rotations); +#[derive(Component, Deref, DerefMut)] +pub struct RightArmPose(pub Rotations); +#[derive(Component, Deref, DerefMut)] +pub struct LeftLegPose(pub Rotations); +#[derive(Component, Deref, DerefMut)] +pub struct RightLegPose(pub Rotations); +#[derive(Component)] +pub struct ArmorStand; impl ArmorStand { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=14 => self.abstract_living.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=14 => AbstractLiving::apply_metadata(entity, d)?, 15 => { - let bitfield = value.into_byte().ok()?; - self.small = bitfield & 0x1 != 0; - self.show_arms = bitfield & 0x4 != 0; - self.no_base_plate = bitfield & 0x8 != 0; - self.marker = bitfield & 0x10 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Small(bitfield & 0x1 != 0)); + entity.insert(ShowArms(bitfield & 0x4 != 0)); + entity.insert(NoBasePlate(bitfield & 0x8 != 0)); + entity.insert(ArmorStandMarker(bitfield & 0x10 != 0)); + } + 16 => { + entity.insert(HeadPose(d.value.into_rotations()?)); + } + 17 => { + entity.insert(BodyPose(d.value.into_rotations()?)); + } + 18 => { + entity.insert(LeftArmPose(d.value.into_rotations()?)); + } + 19 => { + entity.insert(RightArmPose(d.value.into_rotations()?)); + } + 20 => { + entity.insert(LeftLegPose(d.value.into_rotations()?)); + } + 21 => { + entity.insert(RightLegPose(d.value.into_rotations()?)); } - 16 => self.head_pose = value.into_rotations().ok()?, - 17 => self.body_pose = value.into_rotations().ok()?, - 18 => self.left_arm_pose = value.into_rotations().ok()?, - 19 => self.right_arm_pose = value.into_rotations().ok()?, - 20 => self.left_leg_pose = value.into_rotations().ok()?, - 21 => self.right_leg_pose = value.into_rotations().ok()?, _ => {} } - Some(()) - } -} -impl Deref for ArmorStand { - type Target = AbstractLiving; - fn deref(&self) -> &Self::Target { - &self.abstract_living - } -} -impl DerefMut for ArmorStand { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_living - } -} - -#[derive(Debug, Clone)] -pub struct Arrow { - pub abstract_entity: AbstractEntity, - pub crit_arrow: bool, - pub shot_from_crossbow: bool, - pub no_physics: bool, - pub pierce_level: u8, - pub effect_color: i32, -} - -impl Arrow { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let crit_arrow = bitfield & 0x1 != 0; - let shot_from_crossbow = bitfield & 0x4 != 0; - let no_physics = bitfield & 0x2 != 0; - let pierce_level = metadata.pop_front()?.into_byte().ok()?; - let effect_color = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_entity, - crit_arrow, - shot_from_crossbow, - no_physics, - pierce_level, - effect_color, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - let mut bitfield = 0u8; - if self.crit_arrow { - bitfield &= 0x1; - } - if self.shot_from_crossbow { - bitfield &= 0x4; - } - if self.no_physics { - bitfield &= 0x2; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Byte(self.pierce_level.clone())); - metadata.push(EntityDataValue::Int(self.effect_color.clone())); - metadata - } -} - -impl Default for Arrow { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - crit_arrow: false, - shot_from_crossbow: false, - no_physics: false, - pierce_level: 0, - effect_color: -1, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ArmorStandMetadataBundle { + _marker: ArmorStand, + parent: AbstractLivingMetadataBundle, + small: Small, + show_arms: ShowArms, + no_base_plate: NoBasePlate, + armor_stand_marker: ArmorStandMarker, + head_pose: HeadPose, + body_pose: BodyPose, + left_arm_pose: LeftArmPose, + right_arm_pose: RightArmPose, + left_leg_pose: LeftLegPose, + right_leg_pose: RightLegPose, +} +impl Default for ArmorStandMetadataBundle { + fn default() -> Self { + Self { + _marker: ArmorStand, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + small: Small(false), + show_arms: ShowArms(false), + no_base_plate: NoBasePlate(false), + armor_stand_marker: ArmorStandMarker(false), + head_pose: HeadPose(Default::default()), + body_pose: BodyPose(Default::default()), + left_arm_pose: LeftArmPose(Default::default()), + right_arm_pose: RightArmPose(Default::default()), + left_leg_pose: LeftLegPose(Default::default()), + right_leg_pose: RightLegPose(Default::default()), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct ArrowCritArrow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ArrowShotFromCrossbow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ArrowNoPhysics(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ArrowPierceLevel(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct ArrowEffectColor(pub i32); +#[derive(Component)] +pub struct Arrow; impl Arrow { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, 8 => { - let bitfield = value.into_byte().ok()?; - self.crit_arrow = bitfield & 0x1 != 0; - self.shot_from_crossbow = bitfield & 0x4 != 0; - self.no_physics = bitfield & 0x2 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(ArrowCritArrow(bitfield & 0x1 != 0)); + entity.insert(ArrowShotFromCrossbow(bitfield & 0x4 != 0)); + entity.insert(ArrowNoPhysics(bitfield & 0x2 != 0)); + } + 9 => { + entity.insert(ArrowPierceLevel(d.value.into_byte()?)); + } + 10 => { + entity.insert(ArrowEffectColor(d.value.into_int()?)); } - 9 => self.pierce_level = value.into_byte().ok()?, - 10 => self.effect_color = value.into_int().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Arrow { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Arrow { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Axolotl { - pub abstract_animal: AbstractAnimal, - pub variant: i32, - pub playing_dead: bool, - pub from_bucket: bool, -} - -impl Axolotl { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let variant = metadata.pop_front()?.into_int().ok()?; - let playing_dead = metadata.pop_front()?.into_boolean().ok()?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - variant, - playing_dead, - from_bucket, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Int(self.variant.clone())); - metadata.push(EntityDataValue::Boolean(self.playing_dead.clone())); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata - } -} - -impl Default for Axolotl { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - variant: 0, - playing_dead: false, - from_bucket: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ArrowMetadataBundle { + _marker: Arrow, + parent: AbstractEntityMetadataBundle, + arrow_crit_arrow: ArrowCritArrow, + arrow_shot_from_crossbow: ArrowShotFromCrossbow, + arrow_no_physics: ArrowNoPhysics, + arrow_pierce_level: ArrowPierceLevel, + arrow_effect_color: ArrowEffectColor, +} +impl Default for ArrowMetadataBundle { + fn default() -> Self { + Self { + _marker: Arrow, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + arrow_crit_arrow: ArrowCritArrow(false), + arrow_shot_from_crossbow: ArrowShotFromCrossbow(false), + arrow_no_physics: ArrowNoPhysics(false), + arrow_pierce_level: ArrowPierceLevel(0), + arrow_effect_color: ArrowEffectColor(-1), } } } +#[derive(Component, Deref, DerefMut)] +pub struct AbstractAgeableBaby(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct AxolotlVariant(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct PlayingDead(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct AxolotlFromBucket(pub bool); +#[derive(Component)] +pub struct Axolotl; impl Axolotl { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.variant = value.into_int().ok()?, - 18 => self.playing_dead = value.into_boolean().ok()?, - 19 => self.from_bucket = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(AxolotlVariant(d.value.into_int()?)); + } + 18 => { + entity.insert(PlayingDead(d.value.into_boolean()?)); + } + 19 => { + entity.insert(AxolotlFromBucket(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Axolotl { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Axolotl { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Bat { - pub abstract_insentient: AbstractInsentient, - pub resting: bool, -} - -impl Bat { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let resting = bitfield & 0x1 != 0; - Some(Self { - abstract_insentient, - resting, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - let mut bitfield = 0u8; - if self.resting { - bitfield &= 0x1; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for Bat { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), - resting: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AxolotlMetadataBundle { + _marker: Axolotl, + parent: AbstractAnimalMetadataBundle, + axolotl_variant: AxolotlVariant, + playing_dead: PlayingDead, + axolotl_from_bucket: AxolotlFromBucket, +} +impl Default for AxolotlMetadataBundle { + fn default() -> Self { + Self { + _marker: Axolotl, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + axolotl_variant: AxolotlVariant(0), + playing_dead: PlayingDead(false), + axolotl_from_bucket: AxolotlFromBucket(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Resting(pub bool); +#[derive(Component)] +pub struct Bat; impl Bat { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_insentient.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, 16 => { - let bitfield = value.into_byte().ok()?; - self.resting = bitfield & 0x1 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Resting(bitfield & 0x1 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for Bat { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for Bat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct Bee { - pub abstract_animal: AbstractAnimal, - pub has_nectar: bool, - pub has_stung: bool, - pub rolling: bool, - pub remaining_anger_time: i32, -} - -impl Bee { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let has_nectar = bitfield & 0x8 != 0; - let has_stung = bitfield & 0x4 != 0; - let rolling = bitfield & 0x2 != 0; - let remaining_anger_time = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_animal, - has_nectar, - has_stung, - rolling, - remaining_anger_time, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.has_nectar { - bitfield &= 0x8; - } - if self.has_stung { - bitfield &= 0x4; - } - if self.rolling { - bitfield &= 0x2; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Int(self.remaining_anger_time.clone())); - metadata - } -} - -impl Default for Bee { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - has_nectar: false, - has_stung: false, - rolling: false, - remaining_anger_time: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct BatMetadataBundle { + _marker: Bat, + parent: AbstractInsentientMetadataBundle, + resting: Resting, +} +impl Default for BatMetadataBundle { + fn default() -> Self { + Self { + _marker: Bat, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + resting: Resting(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct HasNectar(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HasStung(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct BeeRolling(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct BeeRemainingAngerTime(pub i32); +#[derive(Component)] +pub struct Bee; impl Bee { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.has_nectar = bitfield & 0x8 != 0; - self.has_stung = bitfield & 0x4 != 0; - self.rolling = bitfield & 0x2 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(HasNectar(bitfield & 0x8 != 0)); + entity.insert(HasStung(bitfield & 0x4 != 0)); + entity.insert(BeeRolling(bitfield & 0x2 != 0)); + } + 18 => { + entity.insert(BeeRemainingAngerTime(d.value.into_int()?)); } - 18 => self.remaining_anger_time = value.into_int().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Bee { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Bee { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Blaze { - pub abstract_monster: AbstractMonster, - pub charged: bool, -} - -impl Blaze { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let charged = bitfield & 0x1 != 0; - Some(Self { - abstract_monster, - charged, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - let mut bitfield = 0u8; - if self.charged { - bitfield &= 0x1; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for Blaze { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - charged: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct BeeMetadataBundle { + _marker: Bee, + parent: AbstractAnimalMetadataBundle, + has_nectar: HasNectar, + has_stung: HasStung, + bee_rolling: BeeRolling, + bee_remaining_anger_time: BeeRemainingAngerTime, +} +impl Default for BeeMetadataBundle { + fn default() -> Self { + Self { + _marker: Bee, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + has_nectar: HasNectar(false), + has_stung: HasStung(false), + bee_rolling: BeeRolling(false), + bee_remaining_anger_time: BeeRemainingAngerTime(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Charged(pub bool); +#[derive(Component)] +pub struct Blaze; impl Blaze { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, 16 => { - let bitfield = value.into_byte().ok()?; - self.charged = bitfield & 0x1 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Charged(bitfield & 0x1 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for Blaze { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Blaze { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Boat { - pub abstract_entity: AbstractEntity, - pub hurt: i32, - pub hurtdir: i32, - pub damage: f32, - pub kind: i32, - pub paddle_left: bool, - pub paddle_right: bool, - pub bubble_time: i32, -} - -impl Boat { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let hurt = metadata.pop_front()?.into_int().ok()?; - let hurtdir = metadata.pop_front()?.into_int().ok()?; - let damage = metadata.pop_front()?.into_float().ok()?; - let kind = metadata.pop_front()?.into_int().ok()?; - let paddle_left = metadata.pop_front()?.into_boolean().ok()?; - let paddle_right = metadata.pop_front()?.into_boolean().ok()?; - let bubble_time = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_entity, - hurt, - hurtdir, - damage, - kind, - paddle_left, - paddle_right, - bubble_time, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Int(self.hurt.clone())); - metadata.push(EntityDataValue::Int(self.hurtdir.clone())); - metadata.push(EntityDataValue::Float(self.damage.clone())); - metadata.push(EntityDataValue::Int(self.kind.clone())); - metadata.push(EntityDataValue::Boolean(self.paddle_left.clone())); - metadata.push(EntityDataValue::Boolean(self.paddle_right.clone())); - metadata.push(EntityDataValue::Int(self.bubble_time.clone())); - metadata - } -} - -impl Default for Boat { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - hurt: 0, - hurtdir: 1, - damage: 0.0, - kind: Default::default(), - paddle_left: false, - paddle_right: false, - bubble_time: 0, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct BlazeMetadataBundle { + _marker: Blaze, + parent: AbstractMonsterMetadataBundle, + charged: Charged, +} +impl Default for BlazeMetadataBundle { + fn default() -> Self { + Self { + _marker: Blaze, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + charged: Charged(false), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct BoatHurt(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct BoatHurtdir(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct BoatDamage(pub f32); +#[derive(Component, Deref, DerefMut)] +pub struct BoatKind(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct PaddleLeft(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PaddleRight(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct BubbleTime(pub i32); +#[derive(Component)] +pub struct Boat; impl Boat { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.hurt = value.into_int().ok()?, - 9 => self.hurtdir = value.into_int().ok()?, - 10 => self.damage = value.into_float().ok()?, - 11 => self.kind = value.into_int().ok()?, - 12 => self.paddle_left = value.into_boolean().ok()?, - 13 => self.paddle_right = value.into_boolean().ok()?, - 14 => self.bubble_time = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(BoatHurt(d.value.into_int()?)); + } + 9 => { + entity.insert(BoatHurtdir(d.value.into_int()?)); + } + 10 => { + entity.insert(BoatDamage(d.value.into_float()?)); + } + 11 => { + entity.insert(BoatKind(d.value.into_int()?)); + } + 12 => { + entity.insert(PaddleLeft(d.value.into_boolean()?)); + } + 13 => { + entity.insert(PaddleRight(d.value.into_boolean()?)); + } + 14 => { + entity.insert(BubbleTime(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Boat { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Boat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Camel { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, - pub dash: bool, - pub last_pose_change_tick: i64, -} - -impl Camel { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - let dash = metadata.pop_front()?.into_boolean().ok()?; - let last_pose_change_tick = metadata.pop_front()?.into_long().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - dash, - last_pose_change_tick, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata.push(EntityDataValue::Boolean(self.dash.clone())); - metadata.push(EntityDataValue::Long(self.last_pose_change_tick.clone())); - metadata - } -} - -impl Default for Camel { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - dash: false, - last_pose_change_tick: -52, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct BoatMetadataBundle { + _marker: Boat, + parent: AbstractEntityMetadataBundle, + boat_hurt: BoatHurt, + boat_hurtdir: BoatHurtdir, + boat_damage: BoatDamage, + boat_kind: BoatKind, + paddle_left: PaddleLeft, + paddle_right: PaddleRight, + bubble_time: BubbleTime, +} +impl Default for BoatMetadataBundle { + fn default() -> Self { + Self { + _marker: Boat, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + boat_hurt: BoatHurt(0), + boat_hurtdir: BoatHurtdir(1), + boat_damage: BoatDamage(0.0), + boat_kind: BoatKind(Default::default()), + paddle_left: PaddleLeft(false), + paddle_right: PaddleRight(false), + bubble_time: BubbleTime(0), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct CamelTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CamelEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CamelStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CamelBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CamelSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CamelOwnerUuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct Dash(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LastPoseChangeTick(pub i64); +#[derive(Component)] +pub struct Camel; impl Camel { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(CamelTamed(bitfield & 0x2 != 0)); + entity.insert(CamelEating(bitfield & 0x10 != 0)); + entity.insert(CamelStanding(bitfield & 0x20 != 0)); + entity.insert(CamelBred(bitfield & 0x8 != 0)); + entity.insert(CamelSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(CamelOwnerUuid(d.value.into_optional_uuid()?)); + } + 19 => { + entity.insert(Dash(d.value.into_boolean()?)); + } + 20 => { + entity.insert(LastPoseChangeTick(d.value.into_long()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, - 19 => self.dash = value.into_boolean().ok()?, - 20 => self.last_pose_change_tick = value.into_long().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Camel { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Camel { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Cat { - pub abstract_tameable: AbstractTameable, - pub variant: azalea_registry::CatVariant, - pub is_lying: bool, - pub relax_state_one: bool, - pub collar_color: i32, -} - -impl Cat { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_tameable = AbstractTameable::read(metadata)?; - let variant = metadata.pop_front()?.into_cat_variant().ok()?; - let is_lying = metadata.pop_front()?.into_boolean().ok()?; - let relax_state_one = metadata.pop_front()?.into_boolean().ok()?; - let collar_color = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_tameable, - variant, - is_lying, - relax_state_one, - collar_color, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_tameable.write()); - metadata.push(EntityDataValue::CatVariant(self.variant.clone())); - metadata.push(EntityDataValue::Boolean(self.is_lying.clone())); - metadata.push(EntityDataValue::Boolean(self.relax_state_one.clone())); - metadata.push(EntityDataValue::Int(self.collar_color.clone())); - metadata - } -} - -impl Default for Cat { - fn default() -> Self { - Self { - abstract_tameable: Default::default(), - variant: azalea_registry::CatVariant::Tabby, - is_lying: false, - relax_state_one: false, - collar_color: Default::default(), - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CamelMetadataBundle { + _marker: Camel, + parent: AbstractAnimalMetadataBundle, + camel_tamed: CamelTamed, + camel_eating: CamelEating, + camel_standing: CamelStanding, + camel_bred: CamelBred, + camel_saddled: CamelSaddled, + camel_owner_uuid: CamelOwnerUuid, + dash: Dash, + last_pose_change_tick: LastPoseChangeTick, +} +impl Default for CamelMetadataBundle { + fn default() -> Self { + Self { + _marker: Camel, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + camel_tamed: CamelTamed(false), + camel_eating: CamelEating(false), + camel_standing: CamelStanding(false), + camel_bred: CamelBred(false), + camel_saddled: CamelSaddled(false), + camel_owner_uuid: CamelOwnerUuid(None), + dash: Dash(false), + last_pose_change_tick: LastPoseChangeTick(-52), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct Tame(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct InSittingPose(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Owneruuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct CatVariant(pub azalea_registry::CatVariant); +#[derive(Component, Deref, DerefMut)] +pub struct IsLying(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct RelaxStateOne(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct CatCollarColor(pub i32); +#[derive(Component)] +pub struct Cat; impl Cat { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=18 => self.abstract_tameable.set_index(index, value)?, - 19 => self.variant = value.into_cat_variant().ok()?, - 20 => self.is_lying = value.into_boolean().ok()?, - 21 => self.relax_state_one = value.into_boolean().ok()?, - 22 => self.collar_color = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => AbstractTameable::apply_metadata(entity, d)?, + 19 => { + entity.insert(CatVariant(d.value.into_cat_variant()?)); + } + 20 => { + entity.insert(IsLying(d.value.into_boolean()?)); + } + 21 => { + entity.insert(RelaxStateOne(d.value.into_boolean()?)); + } + 22 => { + entity.insert(CatCollarColor(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Cat { - type Target = AbstractTameable; - fn deref(&self) -> &Self::Target { - &self.abstract_tameable - } -} -impl DerefMut for Cat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_tameable - } -} - -#[derive(Debug, Clone)] -pub struct CaveSpider { - pub spider: Spider, -} - -impl CaveSpider { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let spider = Spider::read(metadata)?; - Some(Self { spider }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.spider.write()); - metadata - } -} - -impl Default for CaveSpider { - fn default() -> Self { - Self { - spider: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CatMetadataBundle { + _marker: Cat, + parent: AbstractTameableMetadataBundle, + cat_variant: CatVariant, + is_lying: IsLying, + relax_state_one: RelaxStateOne, + cat_collar_color: CatCollarColor, +} +impl Default for CatMetadataBundle { + fn default() -> Self { + Self { + _marker: Cat, + parent: AbstractTameableMetadataBundle { + _marker: AbstractTameable, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + tame: Tame(false), + in_sitting_pose: InSittingPose(false), + owneruuid: Owneruuid(None), + }, + cat_variant: CatVariant(azalea_registry::CatVariant::Tabby), + is_lying: IsLying(false), + relax_state_one: RelaxStateOne(false), + cat_collar_color: CatCollarColor(Default::default()), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Climbing(pub bool); +#[derive(Component)] +pub struct CaveSpider; impl CaveSpider { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.spider.set_index(index, value) - } -} -impl Deref for CaveSpider { - type Target = Spider; - fn deref(&self) -> &Self::Target { - &self.spider - } -} -impl DerefMut for CaveSpider { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.spider - } -} - -#[derive(Debug, Clone)] -pub struct ChestBoat { - pub boat: Boat, -} - -impl ChestBoat { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let boat = Boat::read(metadata)?; - Some(Self { boat }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.boat.write()); - metadata - } -} - -impl Default for ChestBoat { - fn default() -> Self { - Self { - boat: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => Spider::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CaveSpiderMetadataBundle { + _marker: CaveSpider, + parent: SpiderMetadataBundle, +} +impl Default for CaveSpiderMetadataBundle { + fn default() -> Self { + Self { + _marker: CaveSpider, + parent: SpiderMetadataBundle { + _marker: Spider, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + climbing: Climbing(false), + }, } } } +#[derive(Component)] +pub struct ChestBoat; impl ChestBoat { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.boat.set_index(index, value) - } -} -impl Deref for ChestBoat { - type Target = Boat; - fn deref(&self) -> &Self::Target { - &self.boat - } -} -impl DerefMut for ChestBoat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.boat - } -} - -#[derive(Debug, Clone)] -pub struct ChestMinecart { - pub abstract_minecart: AbstractMinecart, -} - -impl ChestMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - Some(Self { abstract_minecart }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata - } -} - -impl Default for ChestMinecart { - fn default() -> Self { - Self { - abstract_minecart: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=14 => Boat::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ChestBoatMetadataBundle { + _marker: ChestBoat, + parent: BoatMetadataBundle, +} +impl Default for ChestBoatMetadataBundle { + fn default() -> Self { + Self { + _marker: ChestBoat, + parent: BoatMetadataBundle { + _marker: Boat, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + boat_hurt: BoatHurt(0), + boat_hurtdir: BoatHurtdir(1), + boat_damage: BoatDamage(0.0), + boat_kind: BoatKind(Default::default()), + paddle_left: PaddleLeft(false), + paddle_right: PaddleRight(false), + bubble_time: BubbleTime(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct AbstractMinecartHurt(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct AbstractMinecartHurtdir(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct AbstractMinecartDamage(pub f32); +#[derive(Component, Deref, DerefMut)] +pub struct DisplayBlock(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct DisplayOffset(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct CustomDisplay(pub bool); +#[derive(Component)] +pub struct ChestMinecart; impl ChestMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_minecart.set_index(index, value) - } -} -impl Deref for ChestMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for ChestMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct Chicken { - pub abstract_animal: AbstractAnimal, -} - -impl Chicken { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - Some(Self { abstract_animal }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata - } -} - -impl Default for Chicken { - fn default() -> Self { - Self { - abstract_animal: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ChestMinecartMetadataBundle { + _marker: ChestMinecart, + parent: AbstractMinecartMetadataBundle, +} +impl Default for ChestMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: ChestMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, } } } +#[derive(Component)] +pub struct Chicken; impl Chicken { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_animal.set_index(index, value) - } -} -impl Deref for Chicken { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Chicken { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Cod { - pub abstract_creature: AbstractCreature, - pub from_bucket: bool, -} - -impl Cod { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_creature, - from_bucket, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata - } -} - -impl Default for Cod { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - from_bucket: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ChickenMetadataBundle { + _marker: Chicken, + parent: AbstractAnimalMetadataBundle, +} +impl Default for ChickenMetadataBundle { + fn default() -> Self { + Self { + _marker: Chicken, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct CodFromBucket(pub bool); +#[derive(Component)] +pub struct Cod; impl Cod { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.from_bucket = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(CodFromBucket(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Cod { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Cod { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct CommandBlockMinecart { - pub abstract_minecart: AbstractMinecart, - pub command_name: String, - pub last_output: Component, -} - -impl CommandBlockMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - let command_name = metadata.pop_front()?.into_string().ok()?; - let last_output = metadata.pop_front()?.into_component().ok()?; - Some(Self { - abstract_minecart, - command_name, - last_output, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata.push(EntityDataValue::String(self.command_name.clone())); - metadata.push(EntityDataValue::Component(self.last_output.clone())); - metadata - } -} - -impl Default for CommandBlockMinecart { - fn default() -> Self { - Self { - abstract_minecart: Default::default(), - command_name: "".to_string(), - last_output: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CodMetadataBundle { + _marker: Cod, + parent: AbstractCreatureMetadataBundle, + cod_from_bucket: CodFromBucket, +} +impl Default for CodMetadataBundle { + fn default() -> Self { + Self { + _marker: Cod, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + cod_from_bucket: CodFromBucket(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct CommandName(pub String); +#[derive(Component, Deref, DerefMut)] +pub struct LastOutput(pub FormattedText); +#[derive(Component)] +pub struct CommandBlockMinecart; impl CommandBlockMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=13 => self.abstract_minecart.set_index(index, value)?, - 14 => self.command_name = value.into_string().ok()?, - 15 => self.last_output = value.into_component().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + 14 => { + entity.insert(CommandName(d.value.into_string()?)); + } + 15 => { + entity.insert(LastOutput(d.value.into_formatted_text()?)); + } _ => {} } - Some(()) - } -} -impl Deref for CommandBlockMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for CommandBlockMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct Cow { - pub abstract_animal: AbstractAnimal, -} - -impl Cow { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - Some(Self { abstract_animal }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata - } -} - -impl Default for Cow { - fn default() -> Self { - Self { - abstract_animal: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CommandBlockMinecartMetadataBundle { + _marker: CommandBlockMinecart, + parent: AbstractMinecartMetadataBundle, + command_name: CommandName, + last_output: LastOutput, +} +impl Default for CommandBlockMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: CommandBlockMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, + command_name: CommandName("".to_string()), + last_output: LastOutput(Default::default()), } } } +#[derive(Component)] +pub struct Cow; impl Cow { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_animal.set_index(index, value) - } -} -impl Deref for Cow { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Cow { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Creeper { - pub abstract_monster: AbstractMonster, - pub swell_dir: i32, - pub is_powered: bool, - pub is_ignited: bool, -} - -impl Creeper { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let swell_dir = metadata.pop_front()?.into_int().ok()?; - let is_powered = metadata.pop_front()?.into_boolean().ok()?; - let is_ignited = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - swell_dir, - is_powered, - is_ignited, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Int(self.swell_dir.clone())); - metadata.push(EntityDataValue::Boolean(self.is_powered.clone())); - metadata.push(EntityDataValue::Boolean(self.is_ignited.clone())); - metadata - } -} - -impl Default for Creeper { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - swell_dir: -1, - is_powered: false, - is_ignited: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CowMetadataBundle { + _marker: Cow, + parent: AbstractAnimalMetadataBundle, +} +impl Default for CowMetadataBundle { + fn default() -> Self { + Self { + _marker: Cow, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct SwellDir(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct IsPowered(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct IsIgnited(pub bool); +#[derive(Component)] +pub struct Creeper; impl Creeper { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.swell_dir = value.into_int().ok()?, - 17 => self.is_powered = value.into_boolean().ok()?, - 18 => self.is_ignited = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(SwellDir(d.value.into_int()?)); + } + 17 => { + entity.insert(IsPowered(d.value.into_boolean()?)); + } + 18 => { + entity.insert(IsIgnited(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Creeper { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Creeper { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Dolphin { - pub abstract_creature: AbstractCreature, - pub treasure_pos: BlockPos, - pub got_fish: bool, - pub moistness_level: i32, -} - -impl Dolphin { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let treasure_pos = metadata.pop_front()?.into_block_pos().ok()?; - let got_fish = metadata.pop_front()?.into_boolean().ok()?; - let moistness_level = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_creature, - treasure_pos, - got_fish, - moistness_level, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::BlockPos(self.treasure_pos.clone())); - metadata.push(EntityDataValue::Boolean(self.got_fish.clone())); - metadata.push(EntityDataValue::Int(self.moistness_level.clone())); - metadata - } -} - -impl Default for Dolphin { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - treasure_pos: BlockPos::new(0, 0, 0), - got_fish: false, - moistness_level: 2400, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct CreeperMetadataBundle { + _marker: Creeper, + parent: AbstractMonsterMetadataBundle, + swell_dir: SwellDir, + is_powered: IsPowered, + is_ignited: IsIgnited, +} +impl Default for CreeperMetadataBundle { + fn default() -> Self { + Self { + _marker: Creeper, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + swell_dir: SwellDir(-1), + is_powered: IsPowered(false), + is_ignited: IsIgnited(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct TreasurePos(pub BlockPos); +#[derive(Component, Deref, DerefMut)] +pub struct GotFish(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MoistnessLevel(pub i32); +#[derive(Component)] +pub struct Dolphin; impl Dolphin { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.treasure_pos = value.into_block_pos().ok()?, - 17 => self.got_fish = value.into_boolean().ok()?, - 18 => self.moistness_level = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(TreasurePos(d.value.into_block_pos()?)); + } + 17 => { + entity.insert(GotFish(d.value.into_boolean()?)); + } + 18 => { + entity.insert(MoistnessLevel(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Dolphin { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Dolphin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Donkey { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, - pub chest: bool, -} - -impl Donkey { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - let chest = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - chest, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata.push(EntityDataValue::Boolean(self.chest.clone())); - metadata - } -} - -impl Default for Donkey { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - chest: false, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct DolphinMetadataBundle { + _marker: Dolphin, + parent: AbstractCreatureMetadataBundle, + treasure_pos: TreasurePos, + got_fish: GotFish, + moistness_level: MoistnessLevel, +} +impl Default for DolphinMetadataBundle { + fn default() -> Self { + Self { + _marker: Dolphin, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + treasure_pos: TreasurePos(BlockPos::new(0, 0, 0)), + got_fish: GotFish(false), + moistness_level: MoistnessLevel(2400), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeySaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyOwnerUuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct DonkeyChest(pub bool); +#[derive(Component)] +pub struct Donkey; impl Donkey { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(DonkeyTamed(bitfield & 0x2 != 0)); + entity.insert(DonkeyEating(bitfield & 0x10 != 0)); + entity.insert(DonkeyStanding(bitfield & 0x20 != 0)); + entity.insert(DonkeyBred(bitfield & 0x8 != 0)); + entity.insert(DonkeySaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(DonkeyOwnerUuid(d.value.into_optional_uuid()?)); + } + 19 => { + entity.insert(DonkeyChest(d.value.into_boolean()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, - 19 => self.chest = value.into_boolean().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Donkey { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Donkey { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct DragonFireball { - pub abstract_entity: AbstractEntity, -} - -impl DragonFireball { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for DragonFireball { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct DonkeyMetadataBundle { + _marker: Donkey, + parent: AbstractAnimalMetadataBundle, + donkey_tamed: DonkeyTamed, + donkey_eating: DonkeyEating, + donkey_standing: DonkeyStanding, + donkey_bred: DonkeyBred, + donkey_saddled: DonkeySaddled, + donkey_owner_uuid: DonkeyOwnerUuid, + donkey_chest: DonkeyChest, +} +impl Default for DonkeyMetadataBundle { + fn default() -> Self { + Self { + _marker: Donkey, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + donkey_tamed: DonkeyTamed(false), + donkey_eating: DonkeyEating(false), + donkey_standing: DonkeyStanding(false), + donkey_bred: DonkeyBred(false), + donkey_saddled: DonkeySaddled(false), + donkey_owner_uuid: DonkeyOwnerUuid(None), + donkey_chest: DonkeyChest(false), } } } +#[derive(Component)] +pub struct DragonFireball; impl DragonFireball { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for DragonFireball { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for DragonFireball { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Drowned { - pub zombie: Zombie, -} - -impl Drowned { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let zombie = Zombie::read(metadata)?; - Some(Self { zombie }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.zombie.write()); - metadata - } +#[derive(Bundle)] +pub struct DragonFireballMetadataBundle { + _marker: DragonFireball, + parent: AbstractEntityMetadataBundle, } - -impl Default for Drowned { +impl Default for DragonFireballMetadataBundle { fn default() -> Self { Self { - zombie: Default::default(), + _marker: DragonFireball, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct ZombieBaby(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SpecialType(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct DrownedConversion(pub bool); +#[derive(Component)] +pub struct Drowned; impl Drowned { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.zombie.set_index(index, value) - } -} -impl Deref for Drowned { - type Target = Zombie; - fn deref(&self) -> &Self::Target { - &self.zombie - } -} -impl DerefMut for Drowned { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.zombie - } -} - -#[derive(Debug, Clone)] -pub struct Egg { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl Egg { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for Egg { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => Zombie::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct DrownedMetadataBundle { + _marker: Drowned, + parent: ZombieMetadataBundle, +} +impl Default for DrownedMetadataBundle { + fn default() -> Self { + Self { + _marker: Drowned, + parent: ZombieMetadataBundle { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zombie_baby: ZombieBaby(false), + special_type: SpecialType(0), + drowned_conversion: DrownedConversion(false), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct EggItemStack(pub Slot); +#[derive(Component)] +pub struct Egg; impl Egg { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(EggItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Egg { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Egg { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct ElderGuardian { - pub guardian: Guardian, -} - -impl ElderGuardian { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let guardian = Guardian::read(metadata)?; - Some(Self { guardian }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.guardian.write()); - metadata - } +#[derive(Bundle)] +pub struct EggMetadataBundle { + _marker: Egg, + parent: AbstractEntityMetadataBundle, + egg_item_stack: EggItemStack, } - -impl Default for ElderGuardian { +impl Default for EggMetadataBundle { fn default() -> Self { Self { - guardian: Default::default(), + _marker: Egg, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + egg_item_stack: EggItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Moving(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct AttackTarget(pub i32); +#[derive(Component)] +pub struct ElderGuardian; impl ElderGuardian { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.guardian.set_index(index, value) - } -} -impl Deref for ElderGuardian { - type Target = Guardian; - fn deref(&self) -> &Self::Target { - &self.guardian - } -} -impl DerefMut for ElderGuardian { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.guardian - } -} - -#[derive(Debug, Clone)] -pub struct EndCrystal { - pub abstract_entity: AbstractEntity, - pub beam_target: Option<BlockPos>, - pub show_bottom: bool, -} - -impl EndCrystal { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let beam_target = metadata.pop_front()?.into_optional_block_pos().ok()?; - let show_bottom = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - beam_target, - show_bottom, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::OptionalBlockPos(self.beam_target.clone())); - metadata.push(EntityDataValue::Boolean(self.show_bottom.clone())); - metadata - } -} - -impl Default for EndCrystal { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - beam_target: None, - show_bottom: true, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=17 => Guardian::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ElderGuardianMetadataBundle { + _marker: ElderGuardian, + parent: GuardianMetadataBundle, +} +impl Default for ElderGuardianMetadataBundle { + fn default() -> Self { + Self { + _marker: ElderGuardian, + parent: GuardianMetadataBundle { + _marker: Guardian, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + moving: Moving(false), + attack_target: AttackTarget(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct BeamTarget(pub Option<BlockPos>); +#[derive(Component, Deref, DerefMut)] +pub struct ShowBottom(pub bool); +#[derive(Component)] +pub struct EndCrystal; impl EndCrystal { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.beam_target = value.into_optional_block_pos().ok()?, - 9 => self.show_bottom = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(BeamTarget(d.value.into_optional_block_pos()?)); + } + 9 => { + entity.insert(ShowBottom(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for EndCrystal { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for EndCrystal { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct EnderDragon { - pub abstract_insentient: AbstractInsentient, - pub phase: i32, -} - -impl EnderDragon { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - let phase = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_insentient, - phase, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - metadata.push(EntityDataValue::Int(self.phase.clone())); - metadata - } -} - -impl Default for EnderDragon { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), - phase: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct EndCrystalMetadataBundle { + _marker: EndCrystal, + parent: AbstractEntityMetadataBundle, + beam_target: BeamTarget, + show_bottom: ShowBottom, +} +impl Default for EndCrystalMetadataBundle { + fn default() -> Self { + Self { + _marker: EndCrystal, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + beam_target: BeamTarget(None), + show_bottom: ShowBottom(true), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Phase(pub i32); +#[derive(Component)] +pub struct EnderDragon; impl EnderDragon { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_insentient.set_index(index, value)?, - 16 => self.phase = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, + 16 => { + entity.insert(Phase(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for EnderDragon { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for EnderDragon { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct EnderPearl { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl EnderPearl { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for EnderPearl { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct EnderDragonMetadataBundle { + _marker: EnderDragon, + parent: AbstractInsentientMetadataBundle, + phase: Phase, +} +impl Default for EnderDragonMetadataBundle { + fn default() -> Self { + Self { + _marker: EnderDragon, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + phase: Phase(Default::default()), } } } +#[derive(Component, Deref, DerefMut)] +pub struct EnderPearlItemStack(pub Slot); +#[derive(Component)] +pub struct EnderPearl; impl EnderPearl { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(EnderPearlItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for EnderPearl { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for EnderPearl { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Enderman { - pub abstract_monster: AbstractMonster, - pub carry_state: BlockState, - pub creepy: bool, - pub stared_at: bool, +#[derive(Bundle)] +pub struct EnderPearlMetadataBundle { + _marker: EnderPearl, + parent: AbstractEntityMetadataBundle, + ender_pearl_item_stack: EnderPearlItemStack, } - -impl Enderman { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let carry_state = metadata.pop_front()?.into_block_state().ok()?; - let creepy = metadata.pop_front()?.into_boolean().ok()?; - let stared_at = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - carry_state, - creepy, - stared_at, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::BlockState(self.carry_state.clone())); - metadata.push(EntityDataValue::Boolean(self.creepy.clone())); - metadata.push(EntityDataValue::Boolean(self.stared_at.clone())); - metadata - } -} - -impl Default for Enderman { +impl Default for EnderPearlMetadataBundle { fn default() -> Self { Self { - abstract_monster: Default::default(), - carry_state: BlockState::Air, - creepy: false, - stared_at: false, + _marker: EnderPearl, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + ender_pearl_item_stack: EnderPearlItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct CarryState(pub BlockState); +#[derive(Component, Deref, DerefMut)] +pub struct Creepy(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct StaredAt(pub bool); +#[derive(Component)] +pub struct Enderman; impl Enderman { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.carry_state = value.into_block_state().ok()?, - 17 => self.creepy = value.into_boolean().ok()?, - 18 => self.stared_at = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(CarryState(d.value.into_block_state()?)); + } + 17 => { + entity.insert(Creepy(d.value.into_boolean()?)); + } + 18 => { + entity.insert(StaredAt(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Enderman { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Enderman { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Endermite { - pub abstract_monster: AbstractMonster, -} - -impl Endermite { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - Some(Self { abstract_monster }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata - } -} - -impl Default for Endermite { - fn default() -> Self { - Self { - abstract_monster: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct EndermanMetadataBundle { + _marker: Enderman, + parent: AbstractMonsterMetadataBundle, + carry_state: CarryState, + creepy: Creepy, + stared_at: StaredAt, +} +impl Default for EndermanMetadataBundle { + fn default() -> Self { + Self { + _marker: Enderman, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + carry_state: CarryState(BlockState::Air), + creepy: Creepy(false), + stared_at: StaredAt(false), } } } +#[derive(Component)] +pub struct Endermite; impl Endermite { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_monster.set_index(index, value) - } -} -impl Deref for Endermite { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Endermite { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Evoker { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, - pub spell_casting: u8, -} - -impl Evoker { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - let spell_casting = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - spell_casting, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata.push(EntityDataValue::Byte(self.spell_casting.clone())); - metadata - } -} - -impl Default for Evoker { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, - spell_casting: 0, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct EndermiteMetadataBundle { + _marker: Endermite, + parent: AbstractMonsterMetadataBundle, +} +impl Default for EndermiteMetadataBundle { + fn default() -> Self { + Self { + _marker: Endermite, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct EvokerIsCelebrating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct EvokerSpellCasting(pub u8); +#[derive(Component)] +pub struct Evoker; impl Evoker { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, - 17 => self.spell_casting = value.into_byte().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(EvokerIsCelebrating(d.value.into_boolean()?)); + } + 17 => { + entity.insert(EvokerSpellCasting(d.value.into_byte()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Evoker { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Evoker { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct EvokerFangs { - pub abstract_entity: AbstractEntity, -} - -impl EvokerFangs { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for EvokerFangs { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct EvokerMetadataBundle { + _marker: Evoker, + parent: AbstractMonsterMetadataBundle, + evoker_is_celebrating: EvokerIsCelebrating, + evoker_spell_casting: EvokerSpellCasting, +} +impl Default for EvokerMetadataBundle { + fn default() -> Self { + Self { + _marker: Evoker, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + evoker_is_celebrating: EvokerIsCelebrating(false), + evoker_spell_casting: EvokerSpellCasting(0), } } } +#[derive(Component)] +pub struct EvokerFangs; impl EvokerFangs { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for EvokerFangs { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for EvokerFangs { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct ExperienceBottle { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, +#[derive(Bundle)] +pub struct EvokerFangsMetadataBundle { + _marker: EvokerFangs, + parent: AbstractEntityMetadataBundle, } - -impl ExperienceBottle { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for ExperienceBottle { +impl Default for EvokerFangsMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + _marker: EvokerFangs, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct ExperienceBottleItemStack(pub Slot); +#[derive(Component)] +pub struct ExperienceBottle; impl ExperienceBottle { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(ExperienceBottleItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for ExperienceBottle { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for ExperienceBottle { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct ExperienceOrb { - pub abstract_entity: AbstractEntity, +#[derive(Bundle)] +pub struct ExperienceBottleMetadataBundle { + _marker: ExperienceBottle, + parent: AbstractEntityMetadataBundle, + experience_bottle_item_stack: ExperienceBottleItemStack, } - -impl ExperienceOrb { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for ExperienceOrb { +impl Default for ExperienceBottleMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), + _marker: ExperienceBottle, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + experience_bottle_item_stack: ExperienceBottleItemStack(Slot::Empty), } } } +#[derive(Component)] +pub struct ExperienceOrb; impl ExperienceOrb { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for ExperienceOrb { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for ExperienceOrb { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct EyeOfEnder { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl EyeOfEnder { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } +#[derive(Bundle)] +pub struct ExperienceOrbMetadataBundle { + _marker: ExperienceOrb, + parent: AbstractEntityMetadataBundle, } - -impl Default for EyeOfEnder { +impl Default for ExperienceOrbMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + _marker: ExperienceOrb, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct EyeOfEnderItemStack(pub Slot); +#[derive(Component)] +pub struct EyeOfEnder; impl EyeOfEnder { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(EyeOfEnderItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for EyeOfEnder { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for EyeOfEnder { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct FallingBlock { - pub abstract_entity: AbstractEntity, - pub start_pos: BlockPos, +#[derive(Bundle)] +pub struct EyeOfEnderMetadataBundle { + _marker: EyeOfEnder, + parent: AbstractEntityMetadataBundle, + eye_of_ender_item_stack: EyeOfEnderItemStack, } - -impl FallingBlock { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let start_pos = metadata.pop_front()?.into_block_pos().ok()?; - Some(Self { - abstract_entity, - start_pos, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::BlockPos(self.start_pos.clone())); - metadata - } -} - -impl Default for FallingBlock { +impl Default for EyeOfEnderMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - start_pos: BlockPos::new(0, 0, 0), + _marker: EyeOfEnder, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + eye_of_ender_item_stack: EyeOfEnderItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct StartPos(pub BlockPos); +#[derive(Component)] +pub struct FallingBlock; impl FallingBlock { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.start_pos = value.into_block_pos().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(StartPos(d.value.into_block_pos()?)); + } _ => {} } - Some(()) + Ok(()) } } -impl Deref for FallingBlock { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for FallingBlock { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Fireball { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} -impl Fireball { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } +#[derive(Bundle)] +pub struct FallingBlockMetadataBundle { + _marker: FallingBlock, + parent: AbstractEntityMetadataBundle, + start_pos: StartPos, } - -impl Default for Fireball { +impl Default for FallingBlockMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + _marker: FallingBlock, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + start_pos: StartPos(BlockPos::new(0, 0, 0)), } } } +#[derive(Component, Deref, DerefMut)] +pub struct FireballItemStack(pub Slot); +#[derive(Component)] +pub struct Fireball; impl Fireball { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(FireballItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Fireball { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Fireball { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct FireworkRocket { - pub abstract_entity: AbstractEntity, - pub fireworks_item: Slot, - pub attached_to_target: OptionalUnsignedInt, - pub shot_at_angle: bool, +#[derive(Bundle)] +pub struct FireballMetadataBundle { + _marker: Fireball, + parent: AbstractEntityMetadataBundle, + fireball_item_stack: FireballItemStack, } - -impl FireworkRocket { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let fireworks_item = metadata.pop_front()?.into_item_stack().ok()?; - let attached_to_target = metadata.pop_front()?.into_optional_unsigned_int().ok()?; - let shot_at_angle = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - fireworks_item, - attached_to_target, - shot_at_angle, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.fireworks_item.clone())); - metadata.push(EntityDataValue::OptionalUnsignedInt( - self.attached_to_target.clone(), - )); - metadata.push(EntityDataValue::Boolean(self.shot_at_angle.clone())); - metadata - } -} - -impl Default for FireworkRocket { +impl Default for FireballMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - fireworks_item: Slot::Empty, - attached_to_target: OptionalUnsignedInt(None), - shot_at_angle: false, + _marker: Fireball, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + fireball_item_stack: FireballItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct FireworksItem(pub Slot); +#[derive(Component, Deref, DerefMut)] +pub struct AttachedToTarget(pub OptionalUnsignedInt); +#[derive(Component, Deref, DerefMut)] +pub struct ShotAtAngle(pub bool); +#[derive(Component)] +pub struct FireworkRocket; impl FireworkRocket { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.fireworks_item = value.into_item_stack().ok()?, - 9 => self.attached_to_target = value.into_optional_unsigned_int().ok()?, - 10 => self.shot_at_angle = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(FireworksItem(d.value.into_item_stack()?)); + } + 9 => { + entity.insert(AttachedToTarget(d.value.into_optional_unsigned_int()?)); + } + 10 => { + entity.insert(ShotAtAngle(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for FireworkRocket { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for FireworkRocket { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct FishingBobber { - pub abstract_entity: AbstractEntity, - pub hooked_entity: i32, - pub biting: bool, -} - -impl FishingBobber { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let hooked_entity = metadata.pop_front()?.into_int().ok()?; - let biting = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - hooked_entity, - biting, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Int(self.hooked_entity.clone())); - metadata.push(EntityDataValue::Boolean(self.biting.clone())); - metadata - } -} - -impl Default for FishingBobber { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - hooked_entity: 0, - biting: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct FireworkRocketMetadataBundle { + _marker: FireworkRocket, + parent: AbstractEntityMetadataBundle, + fireworks_item: FireworksItem, + attached_to_target: AttachedToTarget, + shot_at_angle: ShotAtAngle, +} +impl Default for FireworkRocketMetadataBundle { + fn default() -> Self { + Self { + _marker: FireworkRocket, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + fireworks_item: FireworksItem(Slot::Empty), + attached_to_target: AttachedToTarget(OptionalUnsignedInt(None)), + shot_at_angle: ShotAtAngle(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct HookedEntity(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Biting(pub bool); +#[derive(Component)] +pub struct FishingBobber; impl FishingBobber { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.hooked_entity = value.into_int().ok()?, - 9 => self.biting = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(HookedEntity(d.value.into_int()?)); + } + 9 => { + entity.insert(Biting(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for FishingBobber { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for FishingBobber { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Fox { - pub abstract_animal: AbstractAnimal, - pub kind: i32, - pub sitting: bool, - pub faceplanted: bool, - pub sleeping: bool, - pub pouncing: bool, - pub crouching: bool, - pub interested: bool, - pub trusted_id_0: Option<Uuid>, - pub trusted_id_1: Option<Uuid>, -} - -impl Fox { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let kind = metadata.pop_front()?.into_int().ok()?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let sitting = bitfield & 0x1 != 0; - let faceplanted = bitfield & 0x40 != 0; - let sleeping = bitfield & 0x20 != 0; - let pouncing = bitfield & 0x10 != 0; - let crouching = bitfield & 0x4 != 0; - let interested = bitfield & 0x8 != 0; - let trusted_id_0 = metadata.pop_front()?.into_optional_uuid().ok()?; - let trusted_id_1 = metadata.pop_front()?.into_optional_uuid().ok()?; - Some(Self { - abstract_animal, - kind, - sitting, - faceplanted, - sleeping, - pouncing, - crouching, - interested, - trusted_id_0, - trusted_id_1, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Int(self.kind.clone())); - let mut bitfield = 0u8; - if self.sitting { - bitfield &= 0x1; - } - if self.faceplanted { - bitfield &= 0x40; - } - if self.sleeping { - bitfield &= 0x20; - } - if self.pouncing { - bitfield &= 0x10; - } - if self.crouching { - bitfield &= 0x4; - } - if self.interested { - bitfield &= 0x8; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.trusted_id_0.clone())); - metadata.push(EntityDataValue::OptionalUuid(self.trusted_id_1.clone())); - metadata - } -} - -impl Default for Fox { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - kind: 0, - sitting: false, - faceplanted: false, - sleeping: false, - pouncing: false, - crouching: false, - interested: false, - trusted_id_0: None, - trusted_id_1: None, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct FishingBobberMetadataBundle { + _marker: FishingBobber, + parent: AbstractEntityMetadataBundle, + hooked_entity: HookedEntity, + biting: Biting, +} +impl Default for FishingBobberMetadataBundle { + fn default() -> Self { + Self { + _marker: FishingBobber, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + hooked_entity: HookedEntity(0), + biting: Biting(false), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct FoxKind(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct FoxSitting(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Faceplanted(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Sleeping(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Pouncing(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Crouching(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct FoxInterested(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TrustedId0(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct TrustedId1(pub Option<Uuid>); +#[derive(Component)] +pub struct Fox; impl Fox { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.kind = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(FoxKind(d.value.into_int()?)); + } 18 => { - let bitfield = value.into_byte().ok()?; - self.sitting = bitfield & 0x1 != 0; - self.faceplanted = bitfield & 0x40 != 0; - self.sleeping = bitfield & 0x20 != 0; - self.pouncing = bitfield & 0x10 != 0; - self.crouching = bitfield & 0x4 != 0; - self.interested = bitfield & 0x8 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(FoxSitting(bitfield & 0x1 != 0)); + entity.insert(Faceplanted(bitfield & 0x40 != 0)); + entity.insert(Sleeping(bitfield & 0x20 != 0)); + entity.insert(Pouncing(bitfield & 0x10 != 0)); + entity.insert(Crouching(bitfield & 0x4 != 0)); + entity.insert(FoxInterested(bitfield & 0x8 != 0)); + } + 19 => { + entity.insert(TrustedId0(d.value.into_optional_uuid()?)); + } + 20 => { + entity.insert(TrustedId1(d.value.into_optional_uuid()?)); } - 19 => self.trusted_id_0 = value.into_optional_uuid().ok()?, - 20 => self.trusted_id_1 = value.into_optional_uuid().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Fox { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Fox { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Frog { - pub abstract_animal: AbstractAnimal, - pub variant: azalea_registry::FrogVariant, - pub tongue_target: OptionalUnsignedInt, -} - -impl Frog { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let variant = metadata.pop_front()?.into_frog_variant().ok()?; - let tongue_target = metadata.pop_front()?.into_optional_unsigned_int().ok()?; - Some(Self { - abstract_animal, - variant, - tongue_target, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::FrogVariant(self.variant.clone())); - metadata.push(EntityDataValue::OptionalUnsignedInt( - self.tongue_target.clone(), - )); - metadata - } -} - -impl Default for Frog { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - variant: azalea_registry::FrogVariant::Temperate, - tongue_target: OptionalUnsignedInt(None), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct FoxMetadataBundle { + _marker: Fox, + parent: AbstractAnimalMetadataBundle, + fox_kind: FoxKind, + fox_sitting: FoxSitting, + faceplanted: Faceplanted, + sleeping: Sleeping, + pouncing: Pouncing, + crouching: Crouching, + fox_interested: FoxInterested, + trusted_id_0: TrustedId0, + trusted_id_1: TrustedId1, +} +impl Default for FoxMetadataBundle { + fn default() -> Self { + Self { + _marker: Fox, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + fox_kind: FoxKind(0), + fox_sitting: FoxSitting(false), + faceplanted: Faceplanted(false), + sleeping: Sleeping(false), + pouncing: Pouncing(false), + crouching: Crouching(false), + fox_interested: FoxInterested(false), + trusted_id_0: TrustedId0(None), + trusted_id_1: TrustedId1(None), } } } +#[derive(Component, Deref, DerefMut)] +pub struct FrogVariant(pub azalea_registry::FrogVariant); +#[derive(Component, Deref, DerefMut)] +pub struct TongueTarget(pub OptionalUnsignedInt); +#[derive(Component)] +pub struct Frog; impl Frog { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.variant = value.into_frog_variant().ok()?, - 18 => self.tongue_target = value.into_optional_unsigned_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(FrogVariant(d.value.into_frog_variant()?)); + } + 18 => { + entity.insert(TongueTarget(d.value.into_optional_unsigned_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Frog { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Frog { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct FurnaceMinecart { - pub abstract_minecart: AbstractMinecart, - pub fuel: bool, -} - -impl FurnaceMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - let fuel = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_minecart, - fuel, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata.push(EntityDataValue::Boolean(self.fuel.clone())); - metadata - } -} - -impl Default for FurnaceMinecart { - fn default() -> Self { - Self { - abstract_minecart: Default::default(), - fuel: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct FrogMetadataBundle { + _marker: Frog, + parent: AbstractAnimalMetadataBundle, + frog_variant: FrogVariant, + tongue_target: TongueTarget, +} +impl Default for FrogMetadataBundle { + fn default() -> Self { + Self { + _marker: Frog, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + frog_variant: FrogVariant(azalea_registry::FrogVariant::Temperate), + tongue_target: TongueTarget(OptionalUnsignedInt(None)), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Fuel(pub bool); +#[derive(Component)] +pub struct FurnaceMinecart; impl FurnaceMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=13 => self.abstract_minecart.set_index(index, value)?, - 14 => self.fuel = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + 14 => { + entity.insert(Fuel(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for FurnaceMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for FurnaceMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct Ghast { - pub abstract_insentient: AbstractInsentient, - pub is_charging: bool, -} - -impl Ghast { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - let is_charging = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_insentient, - is_charging, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - metadata.push(EntityDataValue::Boolean(self.is_charging.clone())); - metadata - } -} - -impl Default for Ghast { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), - is_charging: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct FurnaceMinecartMetadataBundle { + _marker: FurnaceMinecart, + parent: AbstractMinecartMetadataBundle, + fuel: Fuel, +} +impl Default for FurnaceMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: FurnaceMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, + fuel: Fuel(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct IsCharging(pub bool); +#[derive(Component)] +pub struct Ghast; impl Ghast { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_insentient.set_index(index, value)?, - 16 => self.is_charging = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, + 16 => { + entity.insert(IsCharging(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Ghast { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for Ghast { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct Giant { - pub abstract_monster: AbstractMonster, -} - -impl Giant { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - Some(Self { abstract_monster }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata - } -} - -impl Default for Giant { - fn default() -> Self { - Self { - abstract_monster: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GhastMetadataBundle { + _marker: Ghast, + parent: AbstractInsentientMetadataBundle, + is_charging: IsCharging, +} +impl Default for GhastMetadataBundle { + fn default() -> Self { + Self { + _marker: Ghast, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + is_charging: IsCharging(false), } } } +#[derive(Component)] +pub struct Giant; impl Giant { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_monster.set_index(index, value) - } -} -impl Deref for Giant { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Giant { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct GlowItemFrame { - pub item_frame: ItemFrame, -} - -impl GlowItemFrame { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let item_frame = ItemFrame::read(metadata)?; - Some(Self { item_frame }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.item_frame.write()); - metadata - } -} - -impl Default for GlowItemFrame { - fn default() -> Self { - Self { - item_frame: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GiantMetadataBundle { + _marker: Giant, + parent: AbstractMonsterMetadataBundle, +} +impl Default for GiantMetadataBundle { + fn default() -> Self { + Self { + _marker: Giant, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct ItemFrameItem(pub Slot); +#[derive(Component, Deref, DerefMut)] +pub struct Rotation(pub i32); +#[derive(Component)] +pub struct GlowItemFrame; impl GlowItemFrame { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.item_frame.set_index(index, value) - } -} -impl Deref for GlowItemFrame { - type Target = ItemFrame; - fn deref(&self) -> &Self::Target { - &self.item_frame - } -} -impl DerefMut for GlowItemFrame { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.item_frame - } -} - -#[derive(Debug, Clone)] -pub struct GlowSquid { - pub squid: Squid, - pub dark_ticks_remaining: i32, -} - -impl GlowSquid { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let squid = Squid::read(metadata)?; - let dark_ticks_remaining = metadata.pop_front()?.into_int().ok()?; - Some(Self { - squid, - dark_ticks_remaining, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.squid.write()); - metadata.push(EntityDataValue::Int(self.dark_ticks_remaining.clone())); - metadata - } -} - -impl Default for GlowSquid { - fn default() -> Self { - Self { - squid: Default::default(), - dark_ticks_remaining: 0, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=9 => ItemFrame::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GlowItemFrameMetadataBundle { + _marker: GlowItemFrame, + parent: ItemFrameMetadataBundle, +} +impl Default for GlowItemFrameMetadataBundle { + fn default() -> Self { + Self { + _marker: GlowItemFrame, + parent: ItemFrameMetadataBundle { + _marker: ItemFrame, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + item_frame_item: ItemFrameItem(Slot::Empty), + rotation: Rotation(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct DarkTicksRemaining(pub i32); +#[derive(Component)] +pub struct GlowSquid; impl GlowSquid { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.squid.set_index(index, value)?, - 16 => self.dark_ticks_remaining = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => Squid::apply_metadata(entity, d)?, + 16 => { + entity.insert(DarkTicksRemaining(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for GlowSquid { - type Target = Squid; - fn deref(&self) -> &Self::Target { - &self.squid - } -} -impl DerefMut for GlowSquid { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.squid - } -} - -#[derive(Debug, Clone)] -pub struct Goat { - pub abstract_animal: AbstractAnimal, - pub is_screaming_goat: bool, - pub has_left_horn: bool, - pub has_right_horn: bool, -} - -impl Goat { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let is_screaming_goat = metadata.pop_front()?.into_boolean().ok()?; - let has_left_horn = metadata.pop_front()?.into_boolean().ok()?; - let has_right_horn = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - is_screaming_goat, - has_left_horn, - has_right_horn, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Boolean(self.is_screaming_goat.clone())); - metadata.push(EntityDataValue::Boolean(self.has_left_horn.clone())); - metadata.push(EntityDataValue::Boolean(self.has_right_horn.clone())); - metadata - } -} - -impl Default for Goat { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - is_screaming_goat: false, - has_left_horn: true, - has_right_horn: true, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GlowSquidMetadataBundle { + _marker: GlowSquid, + parent: SquidMetadataBundle, + dark_ticks_remaining: DarkTicksRemaining, +} +impl Default for GlowSquidMetadataBundle { + fn default() -> Self { + Self { + _marker: GlowSquid, + parent: SquidMetadataBundle { + _marker: Squid, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + dark_ticks_remaining: DarkTicksRemaining(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct IsScreamingGoat(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HasLeftHorn(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HasRightHorn(pub bool); +#[derive(Component)] +pub struct Goat; impl Goat { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.is_screaming_goat = value.into_boolean().ok()?, - 18 => self.has_left_horn = value.into_boolean().ok()?, - 19 => self.has_right_horn = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(IsScreamingGoat(d.value.into_boolean()?)); + } + 18 => { + entity.insert(HasLeftHorn(d.value.into_boolean()?)); + } + 19 => { + entity.insert(HasRightHorn(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Goat { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Goat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Guardian { - pub abstract_monster: AbstractMonster, - pub moving: bool, - pub attack_target: i32, -} - -impl Guardian { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let moving = metadata.pop_front()?.into_boolean().ok()?; - let attack_target = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_monster, - moving, - attack_target, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.moving.clone())); - metadata.push(EntityDataValue::Int(self.attack_target.clone())); - metadata - } -} - -impl Default for Guardian { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - moving: false, - attack_target: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GoatMetadataBundle { + _marker: Goat, + parent: AbstractAnimalMetadataBundle, + is_screaming_goat: IsScreamingGoat, + has_left_horn: HasLeftHorn, + has_right_horn: HasRightHorn, +} +impl Default for GoatMetadataBundle { + fn default() -> Self { + Self { + _marker: Goat, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + is_screaming_goat: IsScreamingGoat(false), + has_left_horn: HasLeftHorn(true), + has_right_horn: HasRightHorn(true), } } } +#[derive(Component)] +pub struct Guardian; impl Guardian { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.moving = value.into_boolean().ok()?, - 17 => self.attack_target = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(Moving(d.value.into_boolean()?)); + } + 17 => { + entity.insert(AttackTarget(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Guardian { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Guardian { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Hoglin { - pub abstract_animal: AbstractAnimal, - pub immune_to_zombification: bool, -} - -impl Hoglin { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let immune_to_zombification = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - immune_to_zombification, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Boolean( - self.immune_to_zombification.clone(), - )); - metadata - } -} - -impl Default for Hoglin { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - immune_to_zombification: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct GuardianMetadataBundle { + _marker: Guardian, + parent: AbstractMonsterMetadataBundle, + moving: Moving, + attack_target: AttackTarget, +} +impl Default for GuardianMetadataBundle { + fn default() -> Self { + Self { + _marker: Guardian, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + moving: Moving(false), + attack_target: AttackTarget(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct HoglinImmuneToZombification(pub bool); +#[derive(Component)] +pub struct Hoglin; impl Hoglin { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.immune_to_zombification = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(HoglinImmuneToZombification(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Hoglin { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Hoglin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct HopperMinecart { - pub abstract_minecart: AbstractMinecart, -} - -impl HopperMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - Some(Self { abstract_minecart }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata - } -} - -impl Default for HopperMinecart { - fn default() -> Self { - Self { - abstract_minecart: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct HoglinMetadataBundle { + _marker: Hoglin, + parent: AbstractAnimalMetadataBundle, + hoglin_immune_to_zombification: HoglinImmuneToZombification, +} +impl Default for HoglinMetadataBundle { + fn default() -> Self { + Self { + _marker: Hoglin, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + hoglin_immune_to_zombification: HoglinImmuneToZombification(false), } } } +#[derive(Component)] +pub struct HopperMinecart; impl HopperMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_minecart.set_index(index, value) - } -} -impl Deref for HopperMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for HopperMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct Horse { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, - pub type_variant: i32, -} - -impl Horse { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - let type_variant = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - type_variant, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + _ => {} } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata.push(EntityDataValue::Int(self.type_variant.clone())); - metadata - } -} - -impl Default for Horse { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - type_variant: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct HopperMinecartMetadataBundle { + _marker: HopperMinecart, + parent: AbstractMinecartMetadataBundle, +} +impl Default for HopperMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: HopperMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct HorseTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HorseEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HorseStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HorseBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HorseSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HorseOwnerUuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct HorseTypeVariant(pub i32); +#[derive(Component)] +pub struct Horse; impl Horse { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(HorseTamed(bitfield & 0x2 != 0)); + entity.insert(HorseEating(bitfield & 0x10 != 0)); + entity.insert(HorseStanding(bitfield & 0x20 != 0)); + entity.insert(HorseBred(bitfield & 0x8 != 0)); + entity.insert(HorseSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(HorseOwnerUuid(d.value.into_optional_uuid()?)); + } + 19 => { + entity.insert(HorseTypeVariant(d.value.into_int()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, - 19 => self.type_variant = value.into_int().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Horse { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Horse { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Husk { - pub zombie: Zombie, -} - -impl Husk { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let zombie = Zombie::read(metadata)?; - Some(Self { zombie }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.zombie.write()); - metadata - } -} - -impl Default for Husk { - fn default() -> Self { - Self { - zombie: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct HorseMetadataBundle { + _marker: Horse, + parent: AbstractAnimalMetadataBundle, + horse_tamed: HorseTamed, + horse_eating: HorseEating, + horse_standing: HorseStanding, + horse_bred: HorseBred, + horse_saddled: HorseSaddled, + horse_owner_uuid: HorseOwnerUuid, + horse_type_variant: HorseTypeVariant, +} +impl Default for HorseMetadataBundle { + fn default() -> Self { + Self { + _marker: Horse, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + horse_tamed: HorseTamed(false), + horse_eating: HorseEating(false), + horse_standing: HorseStanding(false), + horse_bred: HorseBred(false), + horse_saddled: HorseSaddled(false), + horse_owner_uuid: HorseOwnerUuid(None), + horse_type_variant: HorseTypeVariant(0), } } } +#[derive(Component)] +pub struct Husk; impl Husk { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.zombie.set_index(index, value) - } -} -impl Deref for Husk { - type Target = Zombie; - fn deref(&self) -> &Self::Target { - &self.zombie - } -} -impl DerefMut for Husk { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.zombie - } -} - -#[derive(Debug, Clone)] -pub struct Illusioner { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, - pub spell_casting: u8, -} - -impl Illusioner { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - let spell_casting = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - spell_casting, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata.push(EntityDataValue::Byte(self.spell_casting.clone())); - metadata - } -} - -impl Default for Illusioner { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, - spell_casting: 0, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => Zombie::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct HuskMetadataBundle { + _marker: Husk, + parent: ZombieMetadataBundle, +} +impl Default for HuskMetadataBundle { + fn default() -> Self { + Self { + _marker: Husk, + parent: ZombieMetadataBundle { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zombie_baby: ZombieBaby(false), + special_type: SpecialType(0), + drowned_conversion: DrownedConversion(false), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct IllusionerIsCelebrating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct IllusionerSpellCasting(pub u8); +#[derive(Component)] +pub struct Illusioner; impl Illusioner { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, - 17 => self.spell_casting = value.into_byte().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(IllusionerIsCelebrating(d.value.into_boolean()?)); + } + 17 => { + entity.insert(IllusionerSpellCasting(d.value.into_byte()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Illusioner { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Illusioner { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct IronGolem { - pub abstract_creature: AbstractCreature, - pub player_created: bool, -} - -impl IronGolem { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let player_created = bitfield & 0x1 != 0; - Some(Self { - abstract_creature, - player_created, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - let mut bitfield = 0u8; - if self.player_created { - bitfield &= 0x1; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for IronGolem { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - player_created: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct IllusionerMetadataBundle { + _marker: Illusioner, + parent: AbstractMonsterMetadataBundle, + illusioner_is_celebrating: IllusionerIsCelebrating, + illusioner_spell_casting: IllusionerSpellCasting, +} +impl Default for IllusionerMetadataBundle { + fn default() -> Self { + Self { + _marker: Illusioner, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + illusioner_is_celebrating: IllusionerIsCelebrating(false), + illusioner_spell_casting: IllusionerSpellCasting(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PlayerCreated(pub bool); +#[derive(Component)] +pub struct IronGolem; impl IronGolem { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, 16 => { - let bitfield = value.into_byte().ok()?; - self.player_created = bitfield & 0x1 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(PlayerCreated(bitfield & 0x1 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for IronGolem { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for IronGolem { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Item { - pub abstract_entity: AbstractEntity, - pub item: Slot, -} - -impl Item { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item.clone())); - metadata - } -} - -impl Default for Item { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item: Slot::Empty, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct IronGolemMetadataBundle { + _marker: IronGolem, + parent: AbstractCreatureMetadataBundle, + player_created: PlayerCreated, +} +impl Default for IronGolemMetadataBundle { + fn default() -> Self { + Self { + _marker: IronGolem, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + player_created: PlayerCreated(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct ItemItem(pub Slot); +#[derive(Component)] +pub struct Item; impl Item { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(ItemItem(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Item { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Item { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct ItemFrame { - pub abstract_entity: AbstractEntity, - pub item: Slot, - pub rotation: i32, +#[derive(Bundle)] +pub struct ItemMetadataBundle { + _marker: Item, + parent: AbstractEntityMetadataBundle, + item_item: ItemItem, } - -impl ItemFrame { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item = metadata.pop_front()?.into_item_stack().ok()?; - let rotation = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_entity, - item, - rotation, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item.clone())); - metadata.push(EntityDataValue::Int(self.rotation.clone())); - metadata - } -} - -impl Default for ItemFrame { +impl Default for ItemMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), - item: Slot::Empty, - rotation: 0, + _marker: Item, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + item_item: ItemItem(Slot::Empty), } } } +#[derive(Component)] +pub struct ItemFrame; impl ItemFrame { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item = value.into_item_stack().ok()?, - 9 => self.rotation = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(ItemFrameItem(d.value.into_item_stack()?)); + } + 9 => { + entity.insert(Rotation(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for ItemFrame { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for ItemFrame { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct LeashKnot { - pub abstract_entity: AbstractEntity, -} - -impl LeashKnot { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for LeashKnot { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ItemFrameMetadataBundle { + _marker: ItemFrame, + parent: AbstractEntityMetadataBundle, + item_frame_item: ItemFrameItem, + rotation: Rotation, +} +impl Default for ItemFrameMetadataBundle { + fn default() -> Self { + Self { + _marker: ItemFrame, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + item_frame_item: ItemFrameItem(Slot::Empty), + rotation: Rotation(0), } } } +#[derive(Component)] +pub struct LeashKnot; impl LeashKnot { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for LeashKnot { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for LeashKnot { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct LightningBolt { - pub abstract_entity: AbstractEntity, +#[derive(Bundle)] +pub struct LeashKnotMetadataBundle { + _marker: LeashKnot, + parent: AbstractEntityMetadataBundle, } - -impl LightningBolt { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for LightningBolt { +impl Default for LeashKnotMetadataBundle { fn default() -> Self { Self { - abstract_entity: Default::default(), + _marker: LeashKnot, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component)] +pub struct LightningBolt; impl LightningBolt { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for LightningBolt { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for LightningBolt { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Llama { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, - pub chest: bool, - pub strength: i32, - pub swag: i32, - pub variant: i32, +#[derive(Bundle)] +pub struct LightningBoltMetadataBundle { + _marker: LightningBolt, + parent: AbstractEntityMetadataBundle, } - -impl Llama { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - let chest = metadata.pop_front()?.into_boolean().ok()?; - let strength = metadata.pop_front()?.into_int().ok()?; - let swag = metadata.pop_front()?.into_int().ok()?; - let variant = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - chest, - strength, - swag, - variant, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata.push(EntityDataValue::Boolean(self.chest.clone())); - metadata.push(EntityDataValue::Int(self.strength.clone())); - metadata.push(EntityDataValue::Int(self.swag.clone())); - metadata.push(EntityDataValue::Int(self.variant.clone())); - metadata - } -} - -impl Default for Llama { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - chest: false, - strength: 0, - swag: -1, - variant: 0, +impl Default for LightningBoltMetadataBundle { + fn default() -> Self { + Self { + _marker: LightningBolt, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct LlamaTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaOwnerUuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaChest(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Strength(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Swag(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct LlamaVariant(pub i32); +#[derive(Component)] +pub struct Llama; impl Llama { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(LlamaTamed(bitfield & 0x2 != 0)); + entity.insert(LlamaEating(bitfield & 0x10 != 0)); + entity.insert(LlamaStanding(bitfield & 0x20 != 0)); + entity.insert(LlamaBred(bitfield & 0x8 != 0)); + entity.insert(LlamaSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(LlamaOwnerUuid(d.value.into_optional_uuid()?)); + } + 19 => { + entity.insert(LlamaChest(d.value.into_boolean()?)); + } + 20 => { + entity.insert(Strength(d.value.into_int()?)); + } + 21 => { + entity.insert(Swag(d.value.into_int()?)); + } + 22 => { + entity.insert(LlamaVariant(d.value.into_int()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, - 19 => self.chest = value.into_boolean().ok()?, - 20 => self.strength = value.into_int().ok()?, - 21 => self.swag = value.into_int().ok()?, - 22 => self.variant = value.into_int().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Llama { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Llama { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct LlamaSpit { - pub abstract_entity: AbstractEntity, -} - -impl LlamaSpit { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for LlamaSpit { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct LlamaMetadataBundle { + _marker: Llama, + parent: AbstractAnimalMetadataBundle, + llama_tamed: LlamaTamed, + llama_eating: LlamaEating, + llama_standing: LlamaStanding, + llama_bred: LlamaBred, + llama_saddled: LlamaSaddled, + llama_owner_uuid: LlamaOwnerUuid, + llama_chest: LlamaChest, + strength: Strength, + swag: Swag, + llama_variant: LlamaVariant, +} +impl Default for LlamaMetadataBundle { + fn default() -> Self { + Self { + _marker: Llama, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + llama_tamed: LlamaTamed(false), + llama_eating: LlamaEating(false), + llama_standing: LlamaStanding(false), + llama_bred: LlamaBred(false), + llama_saddled: LlamaSaddled(false), + llama_owner_uuid: LlamaOwnerUuid(None), + llama_chest: LlamaChest(false), + strength: Strength(0), + swag: Swag(-1), + llama_variant: LlamaVariant(0), } } } +#[derive(Component)] +pub struct LlamaSpit; impl LlamaSpit { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for LlamaSpit { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for LlamaSpit { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct MagmaCube { - pub slime: Slime, +#[derive(Bundle)] +pub struct LlamaSpitMetadataBundle { + _marker: LlamaSpit, + parent: AbstractEntityMetadataBundle, } - -impl MagmaCube { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let slime = Slime::read(metadata)?; - Some(Self { slime }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.slime.write()); - metadata - } -} - -impl Default for MagmaCube { +impl Default for LlamaSpitMetadataBundle { fn default() -> Self { Self { - slime: Default::default(), + _marker: LlamaSpit, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct SlimeSize(pub i32); +#[derive(Component)] +pub struct MagmaCube; impl MagmaCube { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.slime.set_index(index, value) - } -} -impl Deref for MagmaCube { - type Target = Slime; - fn deref(&self) -> &Self::Target { - &self.slime - } -} -impl DerefMut for MagmaCube { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.slime - } -} - -#[derive(Debug, Clone)] -pub struct Marker { - pub abstract_entity: AbstractEntity, -} - -impl Marker { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for Marker { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => Slime::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct MagmaCubeMetadataBundle { + _marker: MagmaCube, + parent: SlimeMetadataBundle, +} +impl Default for MagmaCubeMetadataBundle { + fn default() -> Self { + Self { + _marker: MagmaCube, + parent: SlimeMetadataBundle { + _marker: Slime, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + slime_size: SlimeSize(1), + }, } } } +#[derive(Component)] +pub struct Marker; impl Marker { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for Marker { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Marker { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Minecart { - pub abstract_minecart: AbstractMinecart, -} - -impl Minecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - Some(Self { abstract_minecart }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata - } -} - -impl Default for Minecart { - fn default() -> Self { - Self { - abstract_minecart: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} } + Ok(()) } } -impl Minecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_minecart.set_index(index, value) - } -} -impl Deref for Minecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for Minecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct Mooshroom { - pub cow: Cow, - pub kind: String, -} - -impl Mooshroom { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let cow = Cow::read(metadata)?; - let kind = metadata.pop_front()?.into_string().ok()?; - Some(Self { cow, kind }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.cow.write()); - metadata.push(EntityDataValue::String(self.kind.clone())); - metadata - } +#[derive(Bundle)] +pub struct MarkerMetadataBundle { + _marker: Marker, + parent: AbstractEntityMetadataBundle, } - -impl Default for Mooshroom { +impl Default for MarkerMetadataBundle { fn default() -> Self { Self { - cow: Default::default(), - kind: Default::default(), + _marker: Marker, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } -impl Mooshroom { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.cow.set_index(index, value)?, - 17 => self.kind = value.into_string().ok()?, +#[derive(Component)] +pub struct Minecart; +impl Minecart { + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, _ => {} } - Some(()) - } -} -impl Deref for Mooshroom { - type Target = Cow; - fn deref(&self) -> &Self::Target { - &self.cow - } -} -impl DerefMut for Mooshroom { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.cow - } -} - -#[derive(Debug, Clone)] -pub struct Mule { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, - pub chest: bool, -} - -impl Mule { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - let chest = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - chest, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; + Ok(()) + } +} + +#[derive(Bundle)] +pub struct MinecartMetadataBundle { + _marker: Minecart, + parent: AbstractMinecartMetadataBundle, +} +impl Default for MinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: Minecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata.push(EntityDataValue::Boolean(self.chest.clone())); - metadata } } -impl Default for Mule { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - chest: false, +#[derive(Component, Deref, DerefMut)] +pub struct MooshroomKind(pub String); +#[derive(Component)] +pub struct Mooshroom; +impl Mooshroom { + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => Cow::apply_metadata(entity, d)?, + 17 => { + entity.insert(MooshroomKind(d.value.into_string()?)); + } + _ => {} } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct MooshroomMetadataBundle { + _marker: Mooshroom, + parent: CowMetadataBundle, + mooshroom_kind: MooshroomKind, +} +impl Default for MooshroomMetadataBundle { + fn default() -> Self { + Self { + _marker: Mooshroom, + parent: CowMetadataBundle { + _marker: Cow, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + }, + mooshroom_kind: MooshroomKind(Default::default()), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct MuleTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MuleEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MuleStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MuleBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MuleSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct MuleOwnerUuid(pub Option<Uuid>); +#[derive(Component, Deref, DerefMut)] +pub struct MuleChest(pub bool); +#[derive(Component)] +pub struct Mule; impl Mule { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(MuleTamed(bitfield & 0x2 != 0)); + entity.insert(MuleEating(bitfield & 0x10 != 0)); + entity.insert(MuleStanding(bitfield & 0x20 != 0)); + entity.insert(MuleBred(bitfield & 0x8 != 0)); + entity.insert(MuleSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(MuleOwnerUuid(d.value.into_optional_uuid()?)); + } + 19 => { + entity.insert(MuleChest(d.value.into_boolean()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, - 19 => self.chest = value.into_boolean().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Mule { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Mule { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Ocelot { - pub abstract_animal: AbstractAnimal, - pub trusting: bool, -} - -impl Ocelot { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let trusting = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - trusting, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Boolean(self.trusting.clone())); - metadata - } -} - -impl Default for Ocelot { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - trusting: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct MuleMetadataBundle { + _marker: Mule, + parent: AbstractAnimalMetadataBundle, + mule_tamed: MuleTamed, + mule_eating: MuleEating, + mule_standing: MuleStanding, + mule_bred: MuleBred, + mule_saddled: MuleSaddled, + mule_owner_uuid: MuleOwnerUuid, + mule_chest: MuleChest, +} +impl Default for MuleMetadataBundle { + fn default() -> Self { + Self { + _marker: Mule, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + mule_tamed: MuleTamed(false), + mule_eating: MuleEating(false), + mule_standing: MuleStanding(false), + mule_bred: MuleBred(false), + mule_saddled: MuleSaddled(false), + mule_owner_uuid: MuleOwnerUuid(None), + mule_chest: MuleChest(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Trusting(pub bool); +#[derive(Component)] +pub struct Ocelot; impl Ocelot { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.trusting = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(Trusting(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Ocelot { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Ocelot { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Painting { - pub abstract_entity: AbstractEntity, - pub painting_variant: azalea_registry::PaintingVariant, -} - -impl Painting { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let painting_variant = metadata.pop_front()?.into_painting_variant().ok()?; - Some(Self { - abstract_entity, - painting_variant, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::PaintingVariant( - self.painting_variant.clone(), - )); - metadata - } -} - -impl Default for Painting { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - painting_variant: azalea_registry::PaintingVariant::Kebab, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct OcelotMetadataBundle { + _marker: Ocelot, + parent: AbstractAnimalMetadataBundle, + trusting: Trusting, +} +impl Default for OcelotMetadataBundle { + fn default() -> Self { + Self { + _marker: Ocelot, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + trusting: Trusting(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PaintingVariant(pub azalea_registry::PaintingVariant); +#[derive(Component)] +pub struct Painting; impl Painting { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.painting_variant = value.into_painting_variant().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(PaintingVariant(d.value.into_painting_variant()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Painting { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Painting { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Panda { - pub abstract_animal: AbstractAnimal, - pub unhappy_counter: i32, - pub sneeze_counter: i32, - pub eat_counter: i32, - pub sneezing: bool, - pub sitting: bool, - pub on_back: bool, - pub rolling: bool, - pub hidden_gene: u8, - pub flags: u8, +#[derive(Bundle)] +pub struct PaintingMetadataBundle { + _marker: Painting, + parent: AbstractEntityMetadataBundle, + painting_variant: PaintingVariant, } - -impl Panda { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let unhappy_counter = metadata.pop_front()?.into_int().ok()?; - let sneeze_counter = metadata.pop_front()?.into_int().ok()?; - let eat_counter = metadata.pop_front()?.into_int().ok()?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let sneezing = bitfield & 0x2 != 0; - let sitting = bitfield & 0x8 != 0; - let on_back = bitfield & 0x10 != 0; - let rolling = bitfield & 0x4 != 0; - let hidden_gene = metadata.pop_front()?.into_byte().ok()?; - let flags = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_animal, - unhappy_counter, - sneeze_counter, - eat_counter, - sneezing, - sitting, - on_back, - rolling, - hidden_gene, - flags, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Int(self.unhappy_counter.clone())); - metadata.push(EntityDataValue::Int(self.sneeze_counter.clone())); - metadata.push(EntityDataValue::Int(self.eat_counter.clone())); - let mut bitfield = 0u8; - if self.sneezing { - bitfield &= 0x2; - } - if self.sitting { - bitfield &= 0x8; - } - if self.on_back { - bitfield &= 0x10; - } - if self.rolling { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Byte(self.hidden_gene.clone())); - metadata.push(EntityDataValue::Byte(self.flags.clone())); - metadata - } -} - -impl Default for Panda { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - unhappy_counter: 0, - sneeze_counter: 0, - eat_counter: 0, - sneezing: false, - sitting: false, - on_back: false, - rolling: false, - hidden_gene: 0, - flags: 0, - } - } -} - +impl Default for PaintingMetadataBundle { + fn default() -> Self { + Self { + _marker: Painting, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + painting_variant: PaintingVariant(azalea_registry::PaintingVariant::Kebab), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct PandaUnhappyCounter(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct SneezeCounter(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct EatCounter(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Sneezing(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PandaSitting(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct OnBack(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PandaRolling(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct HiddenGene(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct PandaFlags(pub u8); +#[derive(Component)] +pub struct Panda; impl Panda { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.unhappy_counter = value.into_int().ok()?, - 18 => self.sneeze_counter = value.into_int().ok()?, - 19 => self.eat_counter = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(PandaUnhappyCounter(d.value.into_int()?)); + } + 18 => { + entity.insert(SneezeCounter(d.value.into_int()?)); + } + 19 => { + entity.insert(EatCounter(d.value.into_int()?)); + } 20 => { - let bitfield = value.into_byte().ok()?; - self.sneezing = bitfield & 0x2 != 0; - self.sitting = bitfield & 0x8 != 0; - self.on_back = bitfield & 0x10 != 0; - self.rolling = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Sneezing(bitfield & 0x2 != 0)); + entity.insert(PandaSitting(bitfield & 0x8 != 0)); + entity.insert(OnBack(bitfield & 0x10 != 0)); + entity.insert(PandaRolling(bitfield & 0x4 != 0)); + } + 21 => { + entity.insert(HiddenGene(d.value.into_byte()?)); + } + 22 => { + entity.insert(PandaFlags(d.value.into_byte()?)); } - 21 => self.hidden_gene = value.into_byte().ok()?, - 22 => self.flags = value.into_byte().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Panda { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Panda { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Parrot { - pub abstract_tameable: AbstractTameable, - pub variant: i32, -} - -impl Parrot { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_tameable = AbstractTameable::read(metadata)?; - let variant = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_tameable, - variant, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_tameable.write()); - metadata.push(EntityDataValue::Int(self.variant.clone())); - metadata - } -} - -impl Default for Parrot { - fn default() -> Self { - Self { - abstract_tameable: Default::default(), - variant: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PandaMetadataBundle { + _marker: Panda, + parent: AbstractAnimalMetadataBundle, + panda_unhappy_counter: PandaUnhappyCounter, + sneeze_counter: SneezeCounter, + eat_counter: EatCounter, + sneezing: Sneezing, + panda_sitting: PandaSitting, + on_back: OnBack, + panda_rolling: PandaRolling, + hidden_gene: HiddenGene, + panda_flags: PandaFlags, +} +impl Default for PandaMetadataBundle { + fn default() -> Self { + Self { + _marker: Panda, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + panda_unhappy_counter: PandaUnhappyCounter(0), + sneeze_counter: SneezeCounter(0), + eat_counter: EatCounter(0), + sneezing: Sneezing(false), + panda_sitting: PandaSitting(false), + on_back: OnBack(false), + panda_rolling: PandaRolling(false), + hidden_gene: HiddenGene(0), + panda_flags: PandaFlags(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct ParrotVariant(pub i32); +#[derive(Component)] +pub struct Parrot; impl Parrot { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=18 => self.abstract_tameable.set_index(index, value)?, - 19 => self.variant = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => AbstractTameable::apply_metadata(entity, d)?, + 19 => { + entity.insert(ParrotVariant(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Parrot { - type Target = AbstractTameable; - fn deref(&self) -> &Self::Target { - &self.abstract_tameable - } -} -impl DerefMut for Parrot { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_tameable - } -} - -#[derive(Debug, Clone)] -pub struct Phantom { - pub abstract_insentient: AbstractInsentient, - pub size: i32, -} - -impl Phantom { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - let size = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_insentient, - size, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - metadata.push(EntityDataValue::Int(self.size.clone())); - metadata - } -} - -impl Default for Phantom { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), - size: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ParrotMetadataBundle { + _marker: Parrot, + parent: AbstractTameableMetadataBundle, + parrot_variant: ParrotVariant, +} +impl Default for ParrotMetadataBundle { + fn default() -> Self { + Self { + _marker: Parrot, + parent: AbstractTameableMetadataBundle { + _marker: AbstractTameable, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + tame: Tame(false), + in_sitting_pose: InSittingPose(false), + owneruuid: Owneruuid(None), + }, + parrot_variant: ParrotVariant(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PhantomSize(pub i32); +#[derive(Component)] +pub struct Phantom; impl Phantom { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_insentient.set_index(index, value)?, - 16 => self.size = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, + 16 => { + entity.insert(PhantomSize(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Phantom { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for Phantom { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct Pig { - pub abstract_animal: AbstractAnimal, - pub saddle: bool, - pub boost_time: i32, -} - -impl Pig { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let saddle = metadata.pop_front()?.into_boolean().ok()?; - let boost_time = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_animal, - saddle, - boost_time, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Boolean(self.saddle.clone())); - metadata.push(EntityDataValue::Int(self.boost_time.clone())); - metadata - } -} - -impl Default for Pig { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - saddle: false, - boost_time: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PhantomMetadataBundle { + _marker: Phantom, + parent: AbstractInsentientMetadataBundle, + phantom_size: PhantomSize, +} +impl Default for PhantomMetadataBundle { + fn default() -> Self { + Self { + _marker: Phantom, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + phantom_size: PhantomSize(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PigSaddle(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PigBoostTime(pub i32); +#[derive(Component)] +pub struct Pig; impl Pig { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.saddle = value.into_boolean().ok()?, - 18 => self.boost_time = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(PigSaddle(d.value.into_boolean()?)); + } + 18 => { + entity.insert(PigBoostTime(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Pig { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Pig { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Piglin { - pub abstract_monster: AbstractMonster, - pub immune_to_zombification: bool, - pub baby: bool, - pub is_charging_crossbow: bool, - pub is_dancing: bool, -} - -impl Piglin { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let immune_to_zombification = metadata.pop_front()?.into_boolean().ok()?; - let baby = metadata.pop_front()?.into_boolean().ok()?; - let is_charging_crossbow = metadata.pop_front()?.into_boolean().ok()?; - let is_dancing = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - immune_to_zombification, - baby, - is_charging_crossbow, - is_dancing, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean( - self.immune_to_zombification.clone(), - )); - metadata.push(EntityDataValue::Boolean(self.baby.clone())); - metadata.push(EntityDataValue::Boolean(self.is_charging_crossbow.clone())); - metadata.push(EntityDataValue::Boolean(self.is_dancing.clone())); - metadata - } -} - -impl Default for Piglin { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - immune_to_zombification: false, - baby: false, - is_charging_crossbow: false, - is_dancing: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PigMetadataBundle { + _marker: Pig, + parent: AbstractAnimalMetadataBundle, + pig_saddle: PigSaddle, + pig_boost_time: PigBoostTime, +} +impl Default for PigMetadataBundle { + fn default() -> Self { + Self { + _marker: Pig, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + pig_saddle: PigSaddle(false), + pig_boost_time: PigBoostTime(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PiglinImmuneToZombification(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PiglinBaby(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PiglinIsChargingCrossbow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct IsDancing(pub bool); +#[derive(Component)] +pub struct Piglin; impl Piglin { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.immune_to_zombification = value.into_boolean().ok()?, - 17 => self.baby = value.into_boolean().ok()?, - 18 => self.is_charging_crossbow = value.into_boolean().ok()?, - 19 => self.is_dancing = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(PiglinImmuneToZombification(d.value.into_boolean()?)); + } + 17 => { + entity.insert(PiglinBaby(d.value.into_boolean()?)); + } + 18 => { + entity.insert(PiglinIsChargingCrossbow(d.value.into_boolean()?)); + } + 19 => { + entity.insert(IsDancing(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Piglin { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Piglin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct PiglinBrute { - pub abstract_monster: AbstractMonster, - pub immune_to_zombification: bool, -} - -impl PiglinBrute { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let immune_to_zombification = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - immune_to_zombification, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean( - self.immune_to_zombification.clone(), - )); - metadata - } -} - -impl Default for PiglinBrute { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - immune_to_zombification: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PiglinMetadataBundle { + _marker: Piglin, + parent: AbstractMonsterMetadataBundle, + piglin_immune_to_zombification: PiglinImmuneToZombification, + piglin_baby: PiglinBaby, + piglin_is_charging_crossbow: PiglinIsChargingCrossbow, + is_dancing: IsDancing, +} +impl Default for PiglinMetadataBundle { + fn default() -> Self { + Self { + _marker: Piglin, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + piglin_immune_to_zombification: PiglinImmuneToZombification(false), + piglin_baby: PiglinBaby(false), + piglin_is_charging_crossbow: PiglinIsChargingCrossbow(false), + is_dancing: IsDancing(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PiglinBruteImmuneToZombification(pub bool); +#[derive(Component)] +pub struct PiglinBrute; impl PiglinBrute { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.immune_to_zombification = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(PiglinBruteImmuneToZombification(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for PiglinBrute { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for PiglinBrute { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Pillager { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, - pub is_charging_crossbow: bool, -} - -impl Pillager { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - let is_charging_crossbow = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - is_charging_crossbow, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata.push(EntityDataValue::Boolean(self.is_charging_crossbow.clone())); - metadata - } -} - -impl Default for Pillager { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, - is_charging_crossbow: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PiglinBruteMetadataBundle { + _marker: PiglinBrute, + parent: AbstractMonsterMetadataBundle, + piglin_brute_immune_to_zombification: PiglinBruteImmuneToZombification, +} +impl Default for PiglinBruteMetadataBundle { + fn default() -> Self { + Self { + _marker: PiglinBrute, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + piglin_brute_immune_to_zombification: PiglinBruteImmuneToZombification(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PillagerIsCelebrating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PillagerIsChargingCrossbow(pub bool); +#[derive(Component)] +pub struct Pillager; impl Pillager { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, - 17 => self.is_charging_crossbow = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(PillagerIsCelebrating(d.value.into_boolean()?)); + } + 17 => { + entity.insert(PillagerIsChargingCrossbow(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Pillager { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Pillager { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Player { - pub abstract_living: AbstractLiving, - pub player_absorption: f32, - pub score: i32, - pub player_mode_customisation: u8, - pub player_main_hand: u8, - pub shoulder_left: azalea_nbt::Tag, - pub shoulder_right: azalea_nbt::Tag, -} - -impl Player { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_living = AbstractLiving::read(metadata)?; - let player_absorption = metadata.pop_front()?.into_float().ok()?; - let score = metadata.pop_front()?.into_int().ok()?; - let player_mode_customisation = metadata.pop_front()?.into_byte().ok()?; - let player_main_hand = metadata.pop_front()?.into_byte().ok()?; - let shoulder_left = metadata.pop_front()?.into_compound_tag().ok()?; - let shoulder_right = metadata.pop_front()?.into_compound_tag().ok()?; - Some(Self { - abstract_living, - player_absorption, - score, - player_mode_customisation, - player_main_hand, - shoulder_left, - shoulder_right, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_living.write()); - metadata.push(EntityDataValue::Float(self.player_absorption.clone())); - metadata.push(EntityDataValue::Int(self.score.clone())); - metadata.push(EntityDataValue::Byte( - self.player_mode_customisation.clone(), - )); - metadata.push(EntityDataValue::Byte(self.player_main_hand.clone())); - metadata.push(EntityDataValue::CompoundTag(self.shoulder_left.clone())); - metadata.push(EntityDataValue::CompoundTag(self.shoulder_right.clone())); - metadata - } -} - -impl Default for Player { - fn default() -> Self { - Self { - abstract_living: Default::default(), - player_absorption: 0.0, - score: 0, - player_mode_customisation: 0, - player_main_hand: 1, - shoulder_left: azalea_nbt::Tag::Compound(Default::default()), - shoulder_right: azalea_nbt::Tag::Compound(Default::default()), - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PillagerMetadataBundle { + _marker: Pillager, + parent: AbstractMonsterMetadataBundle, + pillager_is_celebrating: PillagerIsCelebrating, + pillager_is_charging_crossbow: PillagerIsChargingCrossbow, +} +impl Default for PillagerMetadataBundle { + fn default() -> Self { + Self { + _marker: Pillager, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + pillager_is_celebrating: PillagerIsCelebrating(false), + pillager_is_charging_crossbow: PillagerIsChargingCrossbow(false), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct PlayerAbsorption(pub f32); +#[derive(Component, Deref, DerefMut)] +pub struct Score(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct PlayerModeCustomisation(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct PlayerMainHand(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct ShoulderLeft(pub azalea_nbt::Tag); +#[derive(Component, Deref, DerefMut)] +pub struct ShoulderRight(pub azalea_nbt::Tag); +#[derive(Component)] +pub struct Player; impl Player { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=14 => self.abstract_living.set_index(index, value)?, - 15 => self.player_absorption = value.into_float().ok()?, - 16 => self.score = value.into_int().ok()?, - 17 => self.player_mode_customisation = value.into_byte().ok()?, - 18 => self.player_main_hand = value.into_byte().ok()?, - 19 => self.shoulder_left = value.into_compound_tag().ok()?, - 20 => self.shoulder_right = value.into_compound_tag().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=14 => AbstractLiving::apply_metadata(entity, d)?, + 15 => { + entity.insert(PlayerAbsorption(d.value.into_float()?)); + } + 16 => { + entity.insert(Score(d.value.into_int()?)); + } + 17 => { + entity.insert(PlayerModeCustomisation(d.value.into_byte()?)); + } + 18 => { + entity.insert(PlayerMainHand(d.value.into_byte()?)); + } + 19 => { + entity.insert(ShoulderLeft(d.value.into_compound_tag()?)); + } + 20 => { + entity.insert(ShoulderRight(d.value.into_compound_tag()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Player { - type Target = AbstractLiving; - fn deref(&self) -> &Self::Target { - &self.abstract_living - } -} -impl DerefMut for Player { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_living - } -} - -#[derive(Debug, Clone)] -pub struct PolarBear { - pub abstract_animal: AbstractAnimal, - pub standing: bool, -} - -impl PolarBear { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let standing = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - standing, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Boolean(self.standing.clone())); - metadata - } -} - -impl Default for PolarBear { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - standing: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PlayerMetadataBundle { + _marker: Player, + parent: AbstractLivingMetadataBundle, + player_absorption: PlayerAbsorption, + score: Score, + player_mode_customisation: PlayerModeCustomisation, + player_main_hand: PlayerMainHand, + shoulder_left: ShoulderLeft, + shoulder_right: ShoulderRight, +} +impl Default for PlayerMetadataBundle { + fn default() -> Self { + Self { + _marker: Player, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + player_absorption: PlayerAbsorption(0.0), + score: Score(0), + player_mode_customisation: PlayerModeCustomisation(0), + player_main_hand: PlayerMainHand(1), + shoulder_left: ShoulderLeft(azalea_nbt::Tag::Compound(Default::default())), + shoulder_right: ShoulderRight(azalea_nbt::Tag::Compound(Default::default())), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PolarBearStanding(pub bool); +#[derive(Component)] +pub struct PolarBear; impl PolarBear { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.standing = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(PolarBearStanding(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for PolarBear { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for PolarBear { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Potion { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl Potion { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for Potion { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PolarBearMetadataBundle { + _marker: PolarBear, + parent: AbstractAnimalMetadataBundle, + polar_bear_standing: PolarBearStanding, +} +impl Default for PolarBearMetadataBundle { + fn default() -> Self { + Self { + _marker: PolarBear, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + polar_bear_standing: PolarBearStanding(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PotionItemStack(pub Slot); +#[derive(Component)] +pub struct Potion; impl Potion { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(PotionItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Potion { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Potion { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Pufferfish { - pub abstract_creature: AbstractCreature, - pub from_bucket: bool, - pub puff_state: i32, +#[derive(Bundle)] +pub struct PotionMetadataBundle { + _marker: Potion, + parent: AbstractEntityMetadataBundle, + potion_item_stack: PotionItemStack, } - -impl Pufferfish { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - let puff_state = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_creature, - from_bucket, - puff_state, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata.push(EntityDataValue::Int(self.puff_state.clone())); - metadata - } -} - -impl Default for Pufferfish { +impl Default for PotionMetadataBundle { fn default() -> Self { Self { - abstract_creature: Default::default(), - from_bucket: false, - puff_state: 0, + _marker: Potion, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + potion_item_stack: PotionItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct PufferfishFromBucket(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct PuffState(pub i32); +#[derive(Component)] +pub struct Pufferfish; impl Pufferfish { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.from_bucket = value.into_boolean().ok()?, - 17 => self.puff_state = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(PufferfishFromBucket(d.value.into_boolean()?)); + } + 17 => { + entity.insert(PuffState(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Pufferfish { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Pufferfish { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Rabbit { - pub abstract_animal: AbstractAnimal, - pub kind: i32, -} - -impl Rabbit { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let kind = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_animal, - kind, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Int(self.kind.clone())); - metadata - } -} - -impl Default for Rabbit { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - kind: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct PufferfishMetadataBundle { + _marker: Pufferfish, + parent: AbstractCreatureMetadataBundle, + pufferfish_from_bucket: PufferfishFromBucket, + puff_state: PuffState, +} +impl Default for PufferfishMetadataBundle { + fn default() -> Self { + Self { + _marker: Pufferfish, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + pufferfish_from_bucket: PufferfishFromBucket(false), + puff_state: PuffState(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct RabbitKind(pub i32); +#[derive(Component)] +pub struct Rabbit; impl Rabbit { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.kind = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(RabbitKind(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Rabbit { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Rabbit { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Ravager { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, -} - -impl Ravager { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata - } -} - -impl Default for Ravager { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct RabbitMetadataBundle { + _marker: Rabbit, + parent: AbstractAnimalMetadataBundle, + rabbit_kind: RabbitKind, +} +impl Default for RabbitMetadataBundle { + fn default() -> Self { + Self { + _marker: Rabbit, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + rabbit_kind: RabbitKind(Default::default()), } } } +#[derive(Component, Deref, DerefMut)] +pub struct RavagerIsCelebrating(pub bool); +#[derive(Component)] +pub struct Ravager; impl Ravager { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(RavagerIsCelebrating(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Ravager { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Ravager { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Salmon { - pub abstract_creature: AbstractCreature, - pub from_bucket: bool, -} - -impl Salmon { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_creature, - from_bucket, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata - } -} - -impl Default for Salmon { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - from_bucket: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct RavagerMetadataBundle { + _marker: Ravager, + parent: AbstractMonsterMetadataBundle, + ravager_is_celebrating: RavagerIsCelebrating, +} +impl Default for RavagerMetadataBundle { + fn default() -> Self { + Self { + _marker: Ravager, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + ravager_is_celebrating: RavagerIsCelebrating(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct SalmonFromBucket(pub bool); +#[derive(Component)] +pub struct Salmon; impl Salmon { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.from_bucket = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(SalmonFromBucket(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Salmon { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Salmon { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Sheep { - pub abstract_animal: AbstractAnimal, - pub sheared: bool, -} - -impl Sheep { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let sheared = bitfield & 0x10 != 0; - Some(Self { - abstract_animal, - sheared, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.sheared { - bitfield &= 0x10; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for Sheep { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - sheared: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SalmonMetadataBundle { + _marker: Salmon, + parent: AbstractCreatureMetadataBundle, + salmon_from_bucket: SalmonFromBucket, +} +impl Default for SalmonMetadataBundle { + fn default() -> Self { + Self { + _marker: Salmon, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + salmon_from_bucket: SalmonFromBucket(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Sheared(pub bool); +#[derive(Component)] +pub struct Sheep; impl Sheep { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.sheared = bitfield & 0x10 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Sheared(bitfield & 0x10 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for Sheep { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Sheep { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Shulker { - pub abstract_creature: AbstractCreature, - pub attach_face: Direction, - pub peek: u8, - pub color: u8, -} - -impl Shulker { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let attach_face = metadata.pop_front()?.into_direction().ok()?; - let peek = metadata.pop_front()?.into_byte().ok()?; - let color = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_creature, - attach_face, - peek, - color, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Direction(self.attach_face.clone())); - metadata.push(EntityDataValue::Byte(self.peek.clone())); - metadata.push(EntityDataValue::Byte(self.color.clone())); - metadata - } -} - -impl Default for Shulker { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - attach_face: Default::default(), - peek: 0, - color: 16, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SheepMetadataBundle { + _marker: Sheep, + parent: AbstractAnimalMetadataBundle, + sheared: Sheared, +} +impl Default for SheepMetadataBundle { + fn default() -> Self { + Self { + _marker: Sheep, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + sheared: Sheared(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct AttachFace(pub Direction); +#[derive(Component, Deref, DerefMut)] +pub struct Peek(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct ShulkerColor(pub u8); +#[derive(Component)] +pub struct Shulker; impl Shulker { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.attach_face = value.into_direction().ok()?, - 17 => self.peek = value.into_byte().ok()?, - 18 => self.color = value.into_byte().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(AttachFace(d.value.into_direction()?)); + } + 17 => { + entity.insert(Peek(d.value.into_byte()?)); + } + 18 => { + entity.insert(ShulkerColor(d.value.into_byte()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Shulker { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Shulker { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct ShulkerBullet { - pub abstract_entity: AbstractEntity, -} - -impl ShulkerBullet { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - Some(Self { abstract_entity }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata - } -} - -impl Default for ShulkerBullet { - fn default() -> Self { - Self { - abstract_entity: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ShulkerMetadataBundle { + _marker: Shulker, + parent: AbstractCreatureMetadataBundle, + attach_face: AttachFace, + peek: Peek, + shulker_color: ShulkerColor, +} +impl Default for ShulkerMetadataBundle { + fn default() -> Self { + Self { + _marker: Shulker, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + attach_face: AttachFace(Default::default()), + peek: Peek(0), + shulker_color: ShulkerColor(16), } } } +#[derive(Component)] +pub struct ShulkerBullet; impl ShulkerBullet { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_entity.set_index(index, value) - } -} -impl Deref for ShulkerBullet { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for ShulkerBullet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Silverfish { - pub abstract_monster: AbstractMonster, +#[derive(Bundle)] +pub struct ShulkerBulletMetadataBundle { + _marker: ShulkerBullet, + parent: AbstractEntityMetadataBundle, } - -impl Silverfish { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - Some(Self { abstract_monster }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata - } -} - -impl Default for Silverfish { +impl Default for ShulkerBulletMetadataBundle { fn default() -> Self { Self { - abstract_monster: Default::default(), + _marker: ShulkerBullet, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, } } } +#[derive(Component)] +pub struct Silverfish; impl Silverfish { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_monster.set_index(index, value) - } -} -impl Deref for Silverfish { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Silverfish { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Skeleton { - pub abstract_monster: AbstractMonster, - pub stray_conversion: bool, -} - -impl Skeleton { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let stray_conversion = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - stray_conversion, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.stray_conversion.clone())); - metadata - } -} - -impl Default for Skeleton { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - stray_conversion: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SilverfishMetadataBundle { + _marker: Silverfish, + parent: AbstractMonsterMetadataBundle, +} +impl Default for SilverfishMetadataBundle { + fn default() -> Self { + Self { + _marker: Silverfish, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct StrayConversion(pub bool); +#[derive(Component)] +pub struct Skeleton; impl Skeleton { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.stray_conversion = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(StrayConversion(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Skeleton { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Skeleton { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct SkeletonHorse { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, -} - -impl SkeletonHorse { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata - } -} - -impl Default for SkeletonHorse { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SkeletonMetadataBundle { + _marker: Skeleton, + parent: AbstractMonsterMetadataBundle, + stray_conversion: StrayConversion, +} +impl Default for SkeletonMetadataBundle { + fn default() -> Self { + Self { + _marker: Skeleton, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + stray_conversion: StrayConversion(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SkeletonHorseOwnerUuid(pub Option<Uuid>); +#[derive(Component)] +pub struct SkeletonHorse; impl SkeletonHorse { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(SkeletonHorseTamed(bitfield & 0x2 != 0)); + entity.insert(SkeletonHorseEating(bitfield & 0x10 != 0)); + entity.insert(SkeletonHorseStanding(bitfield & 0x20 != 0)); + entity.insert(SkeletonHorseBred(bitfield & 0x8 != 0)); + entity.insert(SkeletonHorseSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(SkeletonHorseOwnerUuid(d.value.into_optional_uuid()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, _ => {} } - Some(()) - } -} -impl Deref for SkeletonHorse { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for SkeletonHorse { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Slime { - pub abstract_insentient: AbstractInsentient, - pub size: i32, -} - -impl Slime { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - let size = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_insentient, - size, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - metadata.push(EntityDataValue::Int(self.size.clone())); - metadata - } -} - -impl Default for Slime { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), - size: 1, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SkeletonHorseMetadataBundle { + _marker: SkeletonHorse, + parent: AbstractAnimalMetadataBundle, + skeleton_horse_tamed: SkeletonHorseTamed, + skeleton_horse_eating: SkeletonHorseEating, + skeleton_horse_standing: SkeletonHorseStanding, + skeleton_horse_bred: SkeletonHorseBred, + skeleton_horse_saddled: SkeletonHorseSaddled, + skeleton_horse_owner_uuid: SkeletonHorseOwnerUuid, +} +impl Default for SkeletonHorseMetadataBundle { + fn default() -> Self { + Self { + _marker: SkeletonHorse, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + skeleton_horse_tamed: SkeletonHorseTamed(false), + skeleton_horse_eating: SkeletonHorseEating(false), + skeleton_horse_standing: SkeletonHorseStanding(false), + skeleton_horse_bred: SkeletonHorseBred(false), + skeleton_horse_saddled: SkeletonHorseSaddled(false), + skeleton_horse_owner_uuid: SkeletonHorseOwnerUuid(None), } } } +#[derive(Component)] +pub struct Slime; impl Slime { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_insentient.set_index(index, value)?, - 16 => self.size = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, + 16 => { + entity.insert(SlimeSize(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Slime { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for Slime { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct SmallFireball { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl SmallFireball { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for SmallFireball { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SlimeMetadataBundle { + _marker: Slime, + parent: AbstractInsentientMetadataBundle, + slime_size: SlimeSize, +} +impl Default for SlimeMetadataBundle { + fn default() -> Self { + Self { + _marker: Slime, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + slime_size: SlimeSize(1), } } } +#[derive(Component, Deref, DerefMut)] +pub struct SmallFireballItemStack(pub Slot); +#[derive(Component)] +pub struct SmallFireball; impl SmallFireball { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(SmallFireballItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for SmallFireball { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for SmallFireball { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct SnowGolem { - pub abstract_creature: AbstractCreature, - pub has_pumpkin: bool, +#[derive(Bundle)] +pub struct SmallFireballMetadataBundle { + _marker: SmallFireball, + parent: AbstractEntityMetadataBundle, + small_fireball_item_stack: SmallFireballItemStack, } - -impl SnowGolem { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let has_pumpkin = bitfield & 0x10 != 0; - Some(Self { - abstract_creature, - has_pumpkin, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - let mut bitfield = 0u8; - if self.has_pumpkin { - bitfield &= 0x10; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for SnowGolem { +impl Default for SmallFireballMetadataBundle { fn default() -> Self { Self { - abstract_creature: Default::default(), - has_pumpkin: true, + _marker: SmallFireball, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + small_fireball_item_stack: SmallFireballItemStack(Slot::Empty), } } } +#[derive(Component, Deref, DerefMut)] +pub struct HasPumpkin(pub bool); +#[derive(Component)] +pub struct SnowGolem; impl SnowGolem { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, 16 => { - let bitfield = value.into_byte().ok()?; - self.has_pumpkin = bitfield & 0x10 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(HasPumpkin(bitfield & 0x10 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for SnowGolem { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for SnowGolem { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Snowball { - pub abstract_entity: AbstractEntity, - pub item_stack: Slot, -} - -impl Snowball { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let item_stack = metadata.pop_front()?.into_item_stack().ok()?; - Some(Self { - abstract_entity, - item_stack, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::ItemStack(self.item_stack.clone())); - metadata - } -} - -impl Default for Snowball { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - item_stack: Slot::Empty, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SnowGolemMetadataBundle { + _marker: SnowGolem, + parent: AbstractCreatureMetadataBundle, + has_pumpkin: HasPumpkin, +} +impl Default for SnowGolemMetadataBundle { + fn default() -> Self { + Self { + _marker: SnowGolem, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + has_pumpkin: HasPumpkin(true), } } } +#[derive(Component, Deref, DerefMut)] +pub struct SnowballItemStack(pub Slot); +#[derive(Component)] +pub struct Snowball; impl Snowball { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.item_stack = value.into_item_stack().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(SnowballItemStack(d.value.into_item_stack()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Snowball { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Snowball { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct SpawnerMinecart { - pub abstract_minecart: AbstractMinecart, -} - -impl SpawnerMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - Some(Self { abstract_minecart }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata - } +#[derive(Bundle)] +pub struct SnowballMetadataBundle { + _marker: Snowball, + parent: AbstractEntityMetadataBundle, + snowball_item_stack: SnowballItemStack, } - -impl Default for SpawnerMinecart { +impl Default for SnowballMetadataBundle { fn default() -> Self { Self { - abstract_minecart: Default::default(), + _marker: Snowball, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + snowball_item_stack: SnowballItemStack(Slot::Empty), } } } +#[derive(Component)] +pub struct SpawnerMinecart; impl SpawnerMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_minecart.set_index(index, value) - } -} -impl Deref for SpawnerMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for SpawnerMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct SpectralArrow { - pub abstract_entity: AbstractEntity, - pub crit_arrow: bool, - pub shot_from_crossbow: bool, - pub no_physics: bool, - pub pierce_level: u8, -} - -impl SpectralArrow { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let crit_arrow = bitfield & 0x1 != 0; - let shot_from_crossbow = bitfield & 0x4 != 0; - let no_physics = bitfield & 0x2 != 0; - let pierce_level = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_entity, - crit_arrow, - shot_from_crossbow, - no_physics, - pierce_level, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - let mut bitfield = 0u8; - if self.crit_arrow { - bitfield &= 0x1; - } - if self.shot_from_crossbow { - bitfield &= 0x4; - } - if self.no_physics { - bitfield &= 0x2; + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + _ => {} } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Byte(self.pierce_level.clone())); - metadata - } -} - -impl Default for SpectralArrow { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - crit_arrow: false, - shot_from_crossbow: false, - no_physics: false, - pierce_level: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SpawnerMinecartMetadataBundle { + _marker: SpawnerMinecart, + parent: AbstractMinecartMetadataBundle, +} +impl Default for SpawnerMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: SpawnerMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct SpectralArrowCritArrow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SpectralArrowShotFromCrossbow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SpectralArrowNoPhysics(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct SpectralArrowPierceLevel(pub u8); +#[derive(Component)] +pub struct SpectralArrow; impl SpectralArrow { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, 8 => { - let bitfield = value.into_byte().ok()?; - self.crit_arrow = bitfield & 0x1 != 0; - self.shot_from_crossbow = bitfield & 0x4 != 0; - self.no_physics = bitfield & 0x2 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(SpectralArrowCritArrow(bitfield & 0x1 != 0)); + entity.insert(SpectralArrowShotFromCrossbow(bitfield & 0x4 != 0)); + entity.insert(SpectralArrowNoPhysics(bitfield & 0x2 != 0)); + } + 9 => { + entity.insert(SpectralArrowPierceLevel(d.value.into_byte()?)); } - 9 => self.pierce_level = value.into_byte().ok()?, _ => {} } - Some(()) - } -} -impl Deref for SpectralArrow { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for SpectralArrow { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct Spider { - pub abstract_monster: AbstractMonster, - pub climbing: bool, -} - -impl Spider { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let climbing = bitfield & 0x1 != 0; - Some(Self { - abstract_monster, - climbing, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - let mut bitfield = 0u8; - if self.climbing { - bitfield &= 0x1; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } -} - -impl Default for Spider { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - climbing: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SpectralArrowMetadataBundle { + _marker: SpectralArrow, + parent: AbstractEntityMetadataBundle, + spectral_arrow_crit_arrow: SpectralArrowCritArrow, + spectral_arrow_shot_from_crossbow: SpectralArrowShotFromCrossbow, + spectral_arrow_no_physics: SpectralArrowNoPhysics, + spectral_arrow_pierce_level: SpectralArrowPierceLevel, +} +impl Default for SpectralArrowMetadataBundle { + fn default() -> Self { + Self { + _marker: SpectralArrow, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + spectral_arrow_crit_arrow: SpectralArrowCritArrow(false), + spectral_arrow_shot_from_crossbow: SpectralArrowShotFromCrossbow(false), + spectral_arrow_no_physics: SpectralArrowNoPhysics(false), + spectral_arrow_pierce_level: SpectralArrowPierceLevel(0), } } } +#[derive(Component)] +pub struct Spider; impl Spider { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, 16 => { - let bitfield = value.into_byte().ok()?; - self.climbing = bitfield & 0x1 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(Climbing(bitfield & 0x1 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for Spider { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Spider { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Squid { - pub abstract_creature: AbstractCreature, -} - -impl Squid { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - Some(Self { abstract_creature }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata - } -} - -impl Default for Squid { - fn default() -> Self { - Self { - abstract_creature: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SpiderMetadataBundle { + _marker: Spider, + parent: AbstractMonsterMetadataBundle, + climbing: Climbing, +} +impl Default for SpiderMetadataBundle { + fn default() -> Self { + Self { + _marker: Spider, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + climbing: Climbing(false), } } } +#[derive(Component)] +pub struct Squid; impl Squid { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_creature.set_index(index, value) - } -} -impl Deref for Squid { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Squid { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Stray { - pub abstract_monster: AbstractMonster, -} - -impl Stray { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - Some(Self { abstract_monster }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata - } -} - -impl Default for Stray { - fn default() -> Self { - Self { - abstract_monster: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct SquidMetadataBundle { + _marker: Squid, + parent: AbstractCreatureMetadataBundle, +} +impl Default for SquidMetadataBundle { + fn default() -> Self { + Self { + _marker: Squid, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, } } } +#[derive(Component)] +pub struct Stray; impl Stray { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_monster.set_index(index, value) - } -} -impl Deref for Stray { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Stray { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Strider { - pub abstract_animal: AbstractAnimal, - pub boost_time: i32, - pub suffocating: bool, - pub saddle: bool, -} - -impl Strider { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let boost_time = metadata.pop_front()?.into_int().ok()?; - let suffocating = metadata.pop_front()?.into_boolean().ok()?; - let saddle = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - boost_time, - suffocating, - saddle, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::Int(self.boost_time.clone())); - metadata.push(EntityDataValue::Boolean(self.suffocating.clone())); - metadata.push(EntityDataValue::Boolean(self.saddle.clone())); - metadata - } -} - -impl Default for Strider { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - boost_time: 0, - suffocating: false, - saddle: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct StrayMetadataBundle { + _marker: Stray, + parent: AbstractMonsterMetadataBundle, +} +impl Default for StrayMetadataBundle { + fn default() -> Self { + Self { + _marker: Stray, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct StriderBoostTime(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Suffocating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct StriderSaddle(pub bool); +#[derive(Component)] +pub struct Strider; impl Strider { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.boost_time = value.into_int().ok()?, - 18 => self.suffocating = value.into_boolean().ok()?, - 19 => self.saddle = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(StriderBoostTime(d.value.into_int()?)); + } + 18 => { + entity.insert(Suffocating(d.value.into_boolean()?)); + } + 19 => { + entity.insert(StriderSaddle(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Strider { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Strider { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Tadpole { - pub abstract_creature: AbstractCreature, - pub from_bucket: bool, -} - -impl Tadpole { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_creature, - from_bucket, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata - } -} - -impl Default for Tadpole { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - from_bucket: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct StriderMetadataBundle { + _marker: Strider, + parent: AbstractAnimalMetadataBundle, + strider_boost_time: StriderBoostTime, + suffocating: Suffocating, + strider_saddle: StriderSaddle, +} +impl Default for StriderMetadataBundle { + fn default() -> Self { + Self { + _marker: Strider, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + strider_boost_time: StriderBoostTime(0), + suffocating: Suffocating(false), + strider_saddle: StriderSaddle(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct TadpoleFromBucket(pub bool); +#[derive(Component)] +pub struct Tadpole; impl Tadpole { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.from_bucket = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(TadpoleFromBucket(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Tadpole { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for Tadpole { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Tnt { - pub abstract_entity: AbstractEntity, - pub fuse: i32, -} - -impl Tnt { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let fuse = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_entity, - fuse, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Int(self.fuse.clone())); - metadata - } -} - -impl Default for Tnt { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - fuse: 80, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TadpoleMetadataBundle { + _marker: Tadpole, + parent: AbstractCreatureMetadataBundle, + tadpole_from_bucket: TadpoleFromBucket, +} +impl Default for TadpoleMetadataBundle { + fn default() -> Self { + Self { + _marker: Tadpole, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + tadpole_from_bucket: TadpoleFromBucket(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Fuse(pub i32); +#[derive(Component)] +pub struct Tnt; impl Tnt { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.fuse = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(Fuse(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Tnt { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Tnt { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct TntMinecart { - pub abstract_minecart: AbstractMinecart, +#[derive(Bundle)] +pub struct TntMetadataBundle { + _marker: Tnt, + parent: AbstractEntityMetadataBundle, + fuse: Fuse, } - -impl TntMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_minecart = AbstractMinecart::read(metadata)?; - Some(Self { abstract_minecart }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_minecart.write()); - metadata - } -} - -impl Default for TntMinecart { +impl Default for TntMetadataBundle { fn default() -> Self { Self { - abstract_minecart: Default::default(), + _marker: Tnt, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + fuse: Fuse(80), } } } +#[derive(Component)] +pub struct TntMinecart; impl TntMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_minecart.set_index(index, value) - } -} -impl Deref for TntMinecart { - type Target = AbstractMinecart; - fn deref(&self) -> &Self::Target { - &self.abstract_minecart - } -} -impl DerefMut for TntMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_minecart - } -} - -#[derive(Debug, Clone)] -pub struct TraderLlama { - pub llama: Llama, -} - -impl TraderLlama { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let llama = Llama::read(metadata)?; - Some(Self { llama }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.llama.write()); - metadata - } -} - -impl Default for TraderLlama { - fn default() -> Self { - Self { - llama: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=13 => AbstractMinecart::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TntMinecartMetadataBundle { + _marker: TntMinecart, + parent: AbstractMinecartMetadataBundle, +} +impl Default for TntMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: TntMinecart, + parent: AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), + }, } } } +#[derive(Component)] +pub struct TraderLlama; impl TraderLlama { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.llama.set_index(index, value) - } -} -impl Deref for TraderLlama { - type Target = Llama; - fn deref(&self) -> &Self::Target { - &self.llama - } -} -impl DerefMut for TraderLlama { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.llama - } -} - -#[derive(Debug, Clone)] -pub struct Trident { - pub abstract_entity: AbstractEntity, - pub crit_arrow: bool, - pub shot_from_crossbow: bool, - pub no_physics: bool, - pub pierce_level: u8, - pub loyalty: u8, - pub foil: bool, -} - -impl Trident { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let crit_arrow = bitfield & 0x1 != 0; - let shot_from_crossbow = bitfield & 0x4 != 0; - let no_physics = bitfield & 0x2 != 0; - let pierce_level = metadata.pop_front()?.into_byte().ok()?; - let loyalty = metadata.pop_front()?.into_byte().ok()?; - let foil = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - crit_arrow, - shot_from_crossbow, - no_physics, - pierce_level, - loyalty, - foil, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - let mut bitfield = 0u8; - if self.crit_arrow { - bitfield &= 0x1; - } - if self.shot_from_crossbow { - bitfield &= 0x4; - } - if self.no_physics { - bitfield &= 0x2; + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=22 => Llama::apply_metadata(entity, d)?, + _ => {} } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Byte(self.pierce_level.clone())); - metadata.push(EntityDataValue::Byte(self.loyalty.clone())); - metadata.push(EntityDataValue::Boolean(self.foil.clone())); - metadata - } -} - -impl Default for Trident { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - crit_arrow: false, - shot_from_crossbow: false, - no_physics: false, - pierce_level: 0, - loyalty: 0, - foil: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TraderLlamaMetadataBundle { + _marker: TraderLlama, + parent: LlamaMetadataBundle, +} +impl Default for TraderLlamaMetadataBundle { + fn default() -> Self { + Self { + _marker: TraderLlama, + parent: LlamaMetadataBundle { + _marker: Llama, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + llama_tamed: LlamaTamed(false), + llama_eating: LlamaEating(false), + llama_standing: LlamaStanding(false), + llama_bred: LlamaBred(false), + llama_saddled: LlamaSaddled(false), + llama_owner_uuid: LlamaOwnerUuid(None), + llama_chest: LlamaChest(false), + strength: Strength(0), + swag: Swag(-1), + llama_variant: LlamaVariant(0), + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct TridentCritArrow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TridentShotFromCrossbow(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TridentNoPhysics(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TridentPierceLevel(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct Loyalty(pub u8); +#[derive(Component, Deref, DerefMut)] +pub struct Foil(pub bool); +#[derive(Component)] +pub struct Trident; impl Trident { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, 8 => { - let bitfield = value.into_byte().ok()?; - self.crit_arrow = bitfield & 0x1 != 0; - self.shot_from_crossbow = bitfield & 0x4 != 0; - self.no_physics = bitfield & 0x2 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(TridentCritArrow(bitfield & 0x1 != 0)); + entity.insert(TridentShotFromCrossbow(bitfield & 0x4 != 0)); + entity.insert(TridentNoPhysics(bitfield & 0x2 != 0)); + } + 9 => { + entity.insert(TridentPierceLevel(d.value.into_byte()?)); + } + 10 => { + entity.insert(Loyalty(d.value.into_byte()?)); + } + 11 => { + entity.insert(Foil(d.value.into_boolean()?)); } - 9 => self.pierce_level = value.into_byte().ok()?, - 10 => self.loyalty = value.into_byte().ok()?, - 11 => self.foil = value.into_boolean().ok()?, _ => {} } - Some(()) - } -} -impl Deref for Trident { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for Trident { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct TropicalFish { - pub abstract_creature: AbstractCreature, - pub from_bucket: bool, - pub type_variant: i32, -} - -impl TropicalFish { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let from_bucket = metadata.pop_front()?.into_boolean().ok()?; - let type_variant = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_creature, - from_bucket, - type_variant, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.from_bucket.clone())); - metadata.push(EntityDataValue::Int(self.type_variant.clone())); - metadata - } -} - -impl Default for TropicalFish { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - from_bucket: false, - type_variant: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TridentMetadataBundle { + _marker: Trident, + parent: AbstractEntityMetadataBundle, + trident_crit_arrow: TridentCritArrow, + trident_shot_from_crossbow: TridentShotFromCrossbow, + trident_no_physics: TridentNoPhysics, + trident_pierce_level: TridentPierceLevel, + loyalty: Loyalty, + foil: Foil, +} +impl Default for TridentMetadataBundle { + fn default() -> Self { + Self { + _marker: Trident, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + trident_crit_arrow: TridentCritArrow(false), + trident_shot_from_crossbow: TridentShotFromCrossbow(false), + trident_no_physics: TridentNoPhysics(false), + trident_pierce_level: TridentPierceLevel(0), + loyalty: Loyalty(0), + foil: Foil(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct TropicalFishFromBucket(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TropicalFishTypeVariant(pub i32); +#[derive(Component)] +pub struct TropicalFish; impl TropicalFish { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.from_bucket = value.into_boolean().ok()?, - 17 => self.type_variant = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(TropicalFishFromBucket(d.value.into_boolean()?)); + } + 17 => { + entity.insert(TropicalFishTypeVariant(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for TropicalFish { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for TropicalFish { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct Turtle { - pub abstract_animal: AbstractAnimal, - pub home_pos: BlockPos, - pub has_egg: bool, - pub laying_egg: bool, - pub travel_pos: BlockPos, - pub going_home: bool, - pub travelling: bool, -} - -impl Turtle { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let home_pos = metadata.pop_front()?.into_block_pos().ok()?; - let has_egg = metadata.pop_front()?.into_boolean().ok()?; - let laying_egg = metadata.pop_front()?.into_boolean().ok()?; - let travel_pos = metadata.pop_front()?.into_block_pos().ok()?; - let going_home = metadata.pop_front()?.into_boolean().ok()?; - let travelling = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_animal, - home_pos, - has_egg, - laying_egg, - travel_pos, - going_home, - travelling, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - metadata.push(EntityDataValue::BlockPos(self.home_pos.clone())); - metadata.push(EntityDataValue::Boolean(self.has_egg.clone())); - metadata.push(EntityDataValue::Boolean(self.laying_egg.clone())); - metadata.push(EntityDataValue::BlockPos(self.travel_pos.clone())); - metadata.push(EntityDataValue::Boolean(self.going_home.clone())); - metadata.push(EntityDataValue::Boolean(self.travelling.clone())); - metadata - } -} - -impl Default for Turtle { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - home_pos: BlockPos::new(0, 0, 0), - has_egg: false, - laying_egg: false, - travel_pos: BlockPos::new(0, 0, 0), - going_home: false, - travelling: false, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TropicalFishMetadataBundle { + _marker: TropicalFish, + parent: AbstractCreatureMetadataBundle, + tropical_fish_from_bucket: TropicalFishFromBucket, + tropical_fish_type_variant: TropicalFishTypeVariant, +} +impl Default for TropicalFishMetadataBundle { + fn default() -> Self { + Self { + _marker: TropicalFish, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + tropical_fish_from_bucket: TropicalFishFromBucket(false), + tropical_fish_type_variant: TropicalFishTypeVariant(0), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct HomePos(pub BlockPos); +#[derive(Component, Deref, DerefMut)] +pub struct HasEgg(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct LayingEgg(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct TravelPos(pub BlockPos); +#[derive(Component, Deref, DerefMut)] +pub struct GoingHome(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct Travelling(pub bool); +#[derive(Component)] +pub struct Turtle; impl Turtle { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => self.home_pos = value.into_block_pos().ok()?, - 18 => self.has_egg = value.into_boolean().ok()?, - 19 => self.laying_egg = value.into_boolean().ok()?, - 20 => self.travel_pos = value.into_block_pos().ok()?, - 21 => self.going_home = value.into_boolean().ok()?, - 22 => self.travelling = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + entity.insert(HomePos(d.value.into_block_pos()?)); + } + 18 => { + entity.insert(HasEgg(d.value.into_boolean()?)); + } + 19 => { + entity.insert(LayingEgg(d.value.into_boolean()?)); + } + 20 => { + entity.insert(TravelPos(d.value.into_block_pos()?)); + } + 21 => { + entity.insert(GoingHome(d.value.into_boolean()?)); + } + 22 => { + entity.insert(Travelling(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Turtle { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for Turtle { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct Vex { - pub abstract_monster: AbstractMonster, - pub flags: u8, -} - -impl Vex { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let flags = metadata.pop_front()?.into_byte().ok()?; - Some(Self { - abstract_monster, - flags, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Byte(self.flags.clone())); - metadata - } -} - -impl Default for Vex { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - flags: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct TurtleMetadataBundle { + _marker: Turtle, + parent: AbstractAnimalMetadataBundle, + home_pos: HomePos, + has_egg: HasEgg, + laying_egg: LayingEgg, + travel_pos: TravelPos, + going_home: GoingHome, + travelling: Travelling, +} +impl Default for TurtleMetadataBundle { + fn default() -> Self { + Self { + _marker: Turtle, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + home_pos: HomePos(BlockPos::new(0, 0, 0)), + has_egg: HasEgg(false), + laying_egg: LayingEgg(false), + travel_pos: TravelPos(BlockPos::new(0, 0, 0)), + going_home: GoingHome(false), + travelling: Travelling(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct VexFlags(pub u8); +#[derive(Component)] +pub struct Vex; impl Vex { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.flags = value.into_byte().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(VexFlags(d.value.into_byte()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Vex { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Vex { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Villager { - pub abstract_ageable: AbstractAgeable, - pub unhappy_counter: i32, - pub villager_data: VillagerData, -} - -impl Villager { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_ageable = AbstractAgeable::read(metadata)?; - let unhappy_counter = metadata.pop_front()?.into_int().ok()?; - let villager_data = metadata.pop_front()?.into_villager_data().ok()?; - Some(Self { - abstract_ageable, - unhappy_counter, - villager_data, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_ageable.write()); - metadata.push(EntityDataValue::Int(self.unhappy_counter.clone())); - metadata.push(EntityDataValue::VillagerData(self.villager_data.clone())); - metadata - } -} - -impl Default for Villager { - fn default() -> Self { - Self { - abstract_ageable: Default::default(), - unhappy_counter: 0, - villager_data: VillagerData { - kind: azalea_registry::VillagerType::Plains, - profession: azalea_registry::VillagerProfession::None, - level: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct VexMetadataBundle { + _marker: Vex, + parent: AbstractMonsterMetadataBundle, + vex_flags: VexFlags, +} +impl Default for VexMetadataBundle { + fn default() -> Self { + Self { + _marker: Vex, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, }, + vex_flags: VexFlags(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct VillagerUnhappyCounter(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct VillagerVillagerData(pub VillagerData); +#[derive(Component)] +pub struct Villager; impl Villager { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_ageable.set_index(index, value)?, - 17 => self.unhappy_counter = value.into_int().ok()?, - 18 => self.villager_data = value.into_villager_data().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAgeable::apply_metadata(entity, d)?, + 17 => { + entity.insert(VillagerUnhappyCounter(d.value.into_int()?)); + } + 18 => { + entity.insert(VillagerVillagerData(d.value.into_villager_data()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Villager { - type Target = AbstractAgeable; - fn deref(&self) -> &Self::Target { - &self.abstract_ageable - } -} -impl DerefMut for Villager { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_ageable - } -} - -#[derive(Debug, Clone)] -pub struct Vindicator { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, -} - -impl Vindicator { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata - } -} - -impl Default for Vindicator { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct VillagerMetadataBundle { + _marker: Villager, + parent: AbstractAgeableMetadataBundle, + villager_unhappy_counter: VillagerUnhappyCounter, + villager_villager_data: VillagerVillagerData, +} +impl Default for VillagerMetadataBundle { + fn default() -> Self { + Self { + _marker: Villager, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + villager_unhappy_counter: VillagerUnhappyCounter(0), + villager_villager_data: VillagerVillagerData(VillagerData { + kind: azalea_registry::VillagerKind::Plains, + profession: azalea_registry::VillagerProfession::None, + level: 0, + }), } } } +#[derive(Component, Deref, DerefMut)] +pub struct VindicatorIsCelebrating(pub bool); +#[derive(Component)] +pub struct Vindicator; impl Vindicator { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(VindicatorIsCelebrating(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Vindicator { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Vindicator { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct WanderingTrader { - pub abstract_ageable: AbstractAgeable, - pub unhappy_counter: i32, -} - -impl WanderingTrader { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_ageable = AbstractAgeable::read(metadata)?; - let unhappy_counter = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_ageable, - unhappy_counter, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_ageable.write()); - metadata.push(EntityDataValue::Int(self.unhappy_counter.clone())); - metadata - } -} - -impl Default for WanderingTrader { - fn default() -> Self { - Self { - abstract_ageable: Default::default(), - unhappy_counter: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct VindicatorMetadataBundle { + _marker: Vindicator, + parent: AbstractMonsterMetadataBundle, + vindicator_is_celebrating: VindicatorIsCelebrating, +} +impl Default for VindicatorMetadataBundle { + fn default() -> Self { + Self { + _marker: Vindicator, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + vindicator_is_celebrating: VindicatorIsCelebrating(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct WanderingTraderUnhappyCounter(pub i32); +#[derive(Component)] +pub struct WanderingTrader; impl WanderingTrader { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_ageable.set_index(index, value)?, - 17 => self.unhappy_counter = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAgeable::apply_metadata(entity, d)?, + 17 => { + entity.insert(WanderingTraderUnhappyCounter(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for WanderingTrader { - type Target = AbstractAgeable; - fn deref(&self) -> &Self::Target { - &self.abstract_ageable - } -} -impl DerefMut for WanderingTrader { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_ageable - } -} - -#[derive(Debug, Clone)] -pub struct Warden { - pub abstract_monster: AbstractMonster, - pub client_anger_level: i32, -} - -impl Warden { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let client_anger_level = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_monster, - client_anger_level, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Int(self.client_anger_level.clone())); - metadata - } -} - -impl Default for Warden { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - client_anger_level: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WanderingTraderMetadataBundle { + _marker: WanderingTrader, + parent: AbstractAgeableMetadataBundle, + wandering_trader_unhappy_counter: WanderingTraderUnhappyCounter, +} +impl Default for WanderingTraderMetadataBundle { + fn default() -> Self { + Self { + _marker: WanderingTrader, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + wandering_trader_unhappy_counter: WanderingTraderUnhappyCounter(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct ClientAngerLevel(pub i32); +#[derive(Component)] +pub struct Warden; impl Warden { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.client_anger_level = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(ClientAngerLevel(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Warden { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Warden { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Witch { - pub abstract_monster: AbstractMonster, - pub is_celebrating: bool, - pub using_item: bool, -} - -impl Witch { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let is_celebrating = metadata.pop_front()?.into_boolean().ok()?; - let using_item = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - is_celebrating, - using_item, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.is_celebrating.clone())); - metadata.push(EntityDataValue::Boolean(self.using_item.clone())); - metadata - } -} - -impl Default for Witch { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - is_celebrating: false, - using_item: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WardenMetadataBundle { + _marker: Warden, + parent: AbstractMonsterMetadataBundle, + client_anger_level: ClientAngerLevel, +} +impl Default for WardenMetadataBundle { + fn default() -> Self { + Self { + _marker: Warden, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + client_anger_level: ClientAngerLevel(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct WitchIsCelebrating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct WitchUsingItem(pub bool); +#[derive(Component)] +pub struct Witch; impl Witch { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.is_celebrating = value.into_boolean().ok()?, - 17 => self.using_item = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(WitchIsCelebrating(d.value.into_boolean()?)); + } + 17 => { + entity.insert(WitchUsingItem(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Witch { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Witch { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Wither { - pub abstract_monster: AbstractMonster, - pub target_a: i32, - pub target_b: i32, - pub target_c: i32, - pub inv: i32, -} - -impl Wither { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let target_a = metadata.pop_front()?.into_int().ok()?; - let target_b = metadata.pop_front()?.into_int().ok()?; - let target_c = metadata.pop_front()?.into_int().ok()?; - let inv = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_monster, - target_a, - target_b, - target_c, - inv, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Int(self.target_a.clone())); - metadata.push(EntityDataValue::Int(self.target_b.clone())); - metadata.push(EntityDataValue::Int(self.target_c.clone())); - metadata.push(EntityDataValue::Int(self.inv.clone())); - metadata - } -} - -impl Default for Wither { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - target_a: 0, - target_b: 0, - target_c: 0, - inv: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WitchMetadataBundle { + _marker: Witch, + parent: AbstractMonsterMetadataBundle, + witch_is_celebrating: WitchIsCelebrating, + witch_using_item: WitchUsingItem, +} +impl Default for WitchMetadataBundle { + fn default() -> Self { + Self { + _marker: Witch, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + witch_is_celebrating: WitchIsCelebrating(false), + witch_using_item: WitchUsingItem(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct TargetA(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct TargetB(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct TargetC(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct Inv(pub i32); +#[derive(Component)] +pub struct Wither; impl Wither { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.target_a = value.into_int().ok()?, - 17 => self.target_b = value.into_int().ok()?, - 18 => self.target_c = value.into_int().ok()?, - 19 => self.inv = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(TargetA(d.value.into_int()?)); + } + 17 => { + entity.insert(TargetB(d.value.into_int()?)); + } + 18 => { + entity.insert(TargetC(d.value.into_int()?)); + } + 19 => { + entity.insert(Inv(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Wither { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Wither { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct WitherSkeleton { - pub abstract_monster: AbstractMonster, -} - -impl WitherSkeleton { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - Some(Self { abstract_monster }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata - } -} - -impl Default for WitherSkeleton { - fn default() -> Self { - Self { - abstract_monster: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WitherMetadataBundle { + _marker: Wither, + parent: AbstractMonsterMetadataBundle, + target_a: TargetA, + target_b: TargetB, + target_c: TargetC, + inv: Inv, +} +impl Default for WitherMetadataBundle { + fn default() -> Self { + Self { + _marker: Wither, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + target_a: TargetA(0), + target_b: TargetB(0), + target_c: TargetC(0), + inv: Inv(0), } } } +#[derive(Component)] +pub struct WitherSkeleton; impl WitherSkeleton { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_monster.set_index(index, value) - } -} -impl Deref for WitherSkeleton { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for WitherSkeleton { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct WitherSkull { - pub abstract_entity: AbstractEntity, - pub dangerous: bool, -} - -impl WitherSkull { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let dangerous = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - dangerous, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Boolean(self.dangerous.clone())); - metadata - } -} - -impl Default for WitherSkull { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - dangerous: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WitherSkeletonMetadataBundle { + _marker: WitherSkeleton, + parent: AbstractMonsterMetadataBundle, +} +impl Default for WitherSkeletonMetadataBundle { + fn default() -> Self { + Self { + _marker: WitherSkeleton, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, } } } +#[derive(Component, Deref, DerefMut)] +pub struct Dangerous(pub bool); +#[derive(Component)] +pub struct WitherSkull; impl WitherSkull { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.dangerous = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(Dangerous(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for WitherSkull { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for WitherSkull { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity + Ok(()) } } -#[derive(Debug, Clone)] -pub struct Wolf { - pub abstract_tameable: AbstractTameable, - pub interested: bool, - pub collar_color: i32, - pub remaining_anger_time: i32, +#[derive(Bundle)] +pub struct WitherSkullMetadataBundle { + _marker: WitherSkull, + parent: AbstractEntityMetadataBundle, + dangerous: Dangerous, } - -impl Wolf { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_tameable = AbstractTameable::read(metadata)?; - let interested = metadata.pop_front()?.into_boolean().ok()?; - let collar_color = metadata.pop_front()?.into_int().ok()?; - let remaining_anger_time = metadata.pop_front()?.into_int().ok()?; - Some(Self { - abstract_tameable, - interested, - collar_color, - remaining_anger_time, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_tameable.write()); - metadata.push(EntityDataValue::Boolean(self.interested.clone())); - metadata.push(EntityDataValue::Int(self.collar_color.clone())); - metadata.push(EntityDataValue::Int(self.remaining_anger_time.clone())); - metadata - } -} - -impl Default for Wolf { +impl Default for WitherSkullMetadataBundle { fn default() -> Self { Self { - abstract_tameable: Default::default(), - interested: false, - collar_color: Default::default(), - remaining_anger_time: 0, + _marker: WitherSkull, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + dangerous: Dangerous(false), } } } +#[derive(Component, Deref, DerefMut)] +pub struct WolfInterested(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct WolfCollarColor(pub i32); +#[derive(Component, Deref, DerefMut)] +pub struct WolfRemainingAngerTime(pub i32); +#[derive(Component)] +pub struct Wolf; impl Wolf { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=18 => self.abstract_tameable.set_index(index, value)?, - 19 => self.interested = value.into_boolean().ok()?, - 20 => self.collar_color = value.into_int().ok()?, - 21 => self.remaining_anger_time = value.into_int().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => AbstractTameable::apply_metadata(entity, d)?, + 19 => { + entity.insert(WolfInterested(d.value.into_boolean()?)); + } + 20 => { + entity.insert(WolfCollarColor(d.value.into_int()?)); + } + 21 => { + entity.insert(WolfRemainingAngerTime(d.value.into_int()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Wolf { - type Target = AbstractTameable; - fn deref(&self) -> &Self::Target { - &self.abstract_tameable - } -} -impl DerefMut for Wolf { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_tameable - } -} - -#[derive(Debug, Clone)] -pub struct Zoglin { - pub abstract_monster: AbstractMonster, - pub baby: bool, -} - -impl Zoglin { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let baby = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - baby, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.baby.clone())); - metadata - } -} - -impl Default for Zoglin { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - baby: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct WolfMetadataBundle { + _marker: Wolf, + parent: AbstractTameableMetadataBundle, + wolf_interested: WolfInterested, + wolf_collar_color: WolfCollarColor, + wolf_remaining_anger_time: WolfRemainingAngerTime, +} +impl Default for WolfMetadataBundle { + fn default() -> Self { + Self { + _marker: Wolf, + parent: AbstractTameableMetadataBundle { + _marker: AbstractTameable, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + tame: Tame(false), + in_sitting_pose: InSittingPose(false), + owneruuid: Owneruuid(None), + }, + wolf_interested: WolfInterested(false), + wolf_collar_color: WolfCollarColor(Default::default()), + wolf_remaining_anger_time: WolfRemainingAngerTime(0), } } } +#[derive(Component, Deref, DerefMut)] +pub struct ZoglinBaby(pub bool); +#[derive(Component)] +pub struct Zoglin; impl Zoglin { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.baby = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(ZoglinBaby(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Zoglin { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Zoglin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct Zombie { - pub abstract_monster: AbstractMonster, - pub baby: bool, - pub special_type: i32, - pub drowned_conversion: bool, -} - -impl Zombie { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_monster = AbstractMonster::read(metadata)?; - let baby = metadata.pop_front()?.into_boolean().ok()?; - let special_type = metadata.pop_front()?.into_int().ok()?; - let drowned_conversion = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_monster, - baby, - special_type, - drowned_conversion, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_monster.write()); - metadata.push(EntityDataValue::Boolean(self.baby.clone())); - metadata.push(EntityDataValue::Int(self.special_type.clone())); - metadata.push(EntityDataValue::Boolean(self.drowned_conversion.clone())); - metadata - } -} - -impl Default for Zombie { - fn default() -> Self { - Self { - abstract_monster: Default::default(), - baby: false, - special_type: 0, - drowned_conversion: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ZoglinMetadataBundle { + _marker: Zoglin, + parent: AbstractMonsterMetadataBundle, + zoglin_baby: ZoglinBaby, +} +impl Default for ZoglinMetadataBundle { + fn default() -> Self { + Self { + _marker: Zoglin, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zoglin_baby: ZoglinBaby(false), } } } +#[derive(Component)] +pub struct Zombie; impl Zombie { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_monster.set_index(index, value)?, - 16 => self.baby = value.into_boolean().ok()?, - 17 => self.special_type = value.into_int().ok()?, - 18 => self.drowned_conversion = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractMonster::apply_metadata(entity, d)?, + 16 => { + entity.insert(ZombieBaby(d.value.into_boolean()?)); + } + 17 => { + entity.insert(SpecialType(d.value.into_int()?)); + } + 18 => { + entity.insert(DrownedConversion(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for Zombie { - type Target = AbstractMonster; - fn deref(&self) -> &Self::Target { - &self.abstract_monster - } -} -impl DerefMut for Zombie { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_monster - } -} - -#[derive(Debug, Clone)] -pub struct ZombieHorse { - pub abstract_animal: AbstractAnimal, - pub tamed: bool, - pub eating: bool, - pub standing: bool, - pub bred: bool, - pub saddled: bool, - pub owner_uuid: Option<Uuid>, -} - -impl ZombieHorse { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tamed = bitfield & 0x2 != 0; - let eating = bitfield & 0x10 != 0; - let standing = bitfield & 0x20 != 0; - let bred = bitfield & 0x8 != 0; - let saddled = bitfield & 0x4 != 0; - let owner_uuid = metadata.pop_front()?.into_optional_uuid().ok()?; - Some(Self { - abstract_animal, - tamed, - eating, - standing, - bred, - saddled, - owner_uuid, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tamed { - bitfield &= 0x2; - } - if self.eating { - bitfield &= 0x10; - } - if self.standing { - bitfield &= 0x20; - } - if self.bred { - bitfield &= 0x8; - } - if self.saddled { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owner_uuid.clone())); - metadata - } -} - -impl Default for ZombieHorse { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tamed: false, - eating: false, - standing: false, - bred: false, - saddled: false, - owner_uuid: None, - } - } -} - + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ZombieMetadataBundle { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle, + zombie_baby: ZombieBaby, + special_type: SpecialType, + drowned_conversion: DrownedConversion, +} +impl Default for ZombieMetadataBundle { + fn default() -> Self { + Self { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zombie_baby: ZombieBaby(false), + special_type: SpecialType(0), + drowned_conversion: DrownedConversion(false), + } + } +} + +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseTamed(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseEating(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseStanding(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseBred(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseSaddled(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieHorseOwnerUuid(pub Option<Uuid>); +#[derive(Component)] +pub struct ZombieHorse; impl ZombieHorse { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, 17 => { - let bitfield = value.into_byte().ok()?; - self.tamed = bitfield & 0x2 != 0; - self.eating = bitfield & 0x10 != 0; - self.standing = bitfield & 0x20 != 0; - self.bred = bitfield & 0x8 != 0; - self.saddled = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(ZombieHorseTamed(bitfield & 0x2 != 0)); + entity.insert(ZombieHorseEating(bitfield & 0x10 != 0)); + entity.insert(ZombieHorseStanding(bitfield & 0x20 != 0)); + entity.insert(ZombieHorseBred(bitfield & 0x8 != 0)); + entity.insert(ZombieHorseSaddled(bitfield & 0x4 != 0)); + } + 18 => { + entity.insert(ZombieHorseOwnerUuid(d.value.into_optional_uuid()?)); } - 18 => self.owner_uuid = value.into_optional_uuid().ok()?, _ => {} } - Some(()) - } -} -impl Deref for ZombieHorse { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for ZombieHorse { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone)] -pub struct ZombieVillager { - pub zombie: Zombie, - pub converting: bool, - pub villager_data: VillagerData, -} - -impl ZombieVillager { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let zombie = Zombie::read(metadata)?; - let converting = metadata.pop_front()?.into_boolean().ok()?; - let villager_data = metadata.pop_front()?.into_villager_data().ok()?; - Some(Self { - zombie, - converting, - villager_data, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.zombie.write()); - metadata.push(EntityDataValue::Boolean(self.converting.clone())); - metadata.push(EntityDataValue::VillagerData(self.villager_data.clone())); - metadata - } -} - -impl Default for ZombieVillager { - fn default() -> Self { - Self { - zombie: Default::default(), - converting: false, - villager_data: VillagerData { - kind: azalea_registry::VillagerType::Plains, - profession: azalea_registry::VillagerProfession::None, - level: 0, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ZombieHorseMetadataBundle { + _marker: ZombieHorse, + parent: AbstractAnimalMetadataBundle, + zombie_horse_tamed: ZombieHorseTamed, + zombie_horse_eating: ZombieHorseEating, + zombie_horse_standing: ZombieHorseStanding, + zombie_horse_bred: ZombieHorseBred, + zombie_horse_saddled: ZombieHorseSaddled, + zombie_horse_owner_uuid: ZombieHorseOwnerUuid, +} +impl Default for ZombieHorseMetadataBundle { + fn default() -> Self { + Self { + _marker: ZombieHorse, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, }, + zombie_horse_tamed: ZombieHorseTamed(false), + zombie_horse_eating: ZombieHorseEating(false), + zombie_horse_standing: ZombieHorseStanding(false), + zombie_horse_bred: ZombieHorseBred(false), + zombie_horse_saddled: ZombieHorseSaddled(false), + zombie_horse_owner_uuid: ZombieHorseOwnerUuid(None), } } } +#[derive(Component, Deref, DerefMut)] +pub struct Converting(pub bool); +#[derive(Component, Deref, DerefMut)] +pub struct ZombieVillagerVillagerData(pub VillagerData); +#[derive(Component)] +pub struct ZombieVillager; impl ZombieVillager { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=18 => self.zombie.set_index(index, value)?, - 19 => self.converting = value.into_boolean().ok()?, - 20 => self.villager_data = value.into_villager_data().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => Zombie::apply_metadata(entity, d)?, + 19 => { + entity.insert(Converting(d.value.into_boolean()?)); + } + 20 => { + entity.insert(ZombieVillagerVillagerData(d.value.into_villager_data()?)); + } _ => {} } - Some(()) - } -} -impl Deref for ZombieVillager { - type Target = Zombie; - fn deref(&self) -> &Self::Target { - &self.zombie - } -} -impl DerefMut for ZombieVillager { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.zombie - } -} - -#[derive(Debug, Clone)] -pub struct ZombifiedPiglin { - pub zombie: Zombie, -} - -impl ZombifiedPiglin { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let zombie = Zombie::read(metadata)?; - Some(Self { zombie }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.zombie.write()); - metadata - } -} - -impl Default for ZombifiedPiglin { - fn default() -> Self { - Self { - zombie: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ZombieVillagerMetadataBundle { + _marker: ZombieVillager, + parent: ZombieMetadataBundle, + converting: Converting, + zombie_villager_villager_data: ZombieVillagerVillagerData, +} +impl Default for ZombieVillagerMetadataBundle { + fn default() -> Self { + Self { + _marker: ZombieVillager, + parent: ZombieMetadataBundle { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zombie_baby: ZombieBaby(false), + special_type: SpecialType(0), + drowned_conversion: DrownedConversion(false), + }, + converting: Converting(false), + zombie_villager_villager_data: ZombieVillagerVillagerData(VillagerData { + kind: azalea_registry::VillagerKind::Plains, + profession: azalea_registry::VillagerProfession::None, + level: 0, + }), } } } +#[derive(Component)] +pub struct ZombifiedPiglin; impl ZombifiedPiglin { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.zombie.set_index(index, value) - } -} -impl Deref for ZombifiedPiglin { - type Target = Zombie; - fn deref(&self) -> &Self::Target { - &self.zombie - } -} -impl DerefMut for ZombifiedPiglin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.zombie - } -} - -#[derive(Debug, Clone)] -pub struct AbstractAgeable { - pub abstract_creature: AbstractCreature, - pub baby: bool, -} - -impl AbstractAgeable { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - let baby = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_creature, - baby, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata.push(EntityDataValue::Boolean(self.baby.clone())); - metadata - } -} - -impl Default for AbstractAgeable { - fn default() -> Self { - Self { - abstract_creature: Default::default(), - baby: false, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=18 => Zombie::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct ZombifiedPiglinMetadataBundle { + _marker: ZombifiedPiglin, + parent: ZombieMetadataBundle, +} +impl Default for ZombifiedPiglinMetadataBundle { + fn default() -> Self { + Self { + _marker: ZombifiedPiglin, + parent: ZombieMetadataBundle { + _marker: Zombie, + parent: AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + }, + zombie_baby: ZombieBaby(false), + special_type: SpecialType(0), + drowned_conversion: DrownedConversion(false), + }, } } } +#[derive(Component)] +pub struct AbstractAgeable; impl AbstractAgeable { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=15 => self.abstract_creature.set_index(index, value)?, - 16 => self.baby = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + 16 => { + entity.insert(AbstractAgeableBaby(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for AbstractAgeable { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for AbstractAgeable { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature - } -} - -#[derive(Debug, Clone)] -pub struct AbstractAnimal { - pub abstract_ageable: AbstractAgeable, -} - -impl AbstractAnimal { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_ageable = AbstractAgeable::read(metadata)?; - Some(Self { abstract_ageable }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_ageable.write()); - metadata - } -} - -impl Default for AbstractAnimal { - fn default() -> Self { - Self { - abstract_ageable: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle, + abstract_ageable_baby: AbstractAgeableBaby, +} +impl Default for AbstractAgeableMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), } } } +#[derive(Component)] +pub struct AbstractAnimal; impl AbstractAnimal { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_ageable.set_index(index, value) - } -} -impl Deref for AbstractAnimal { - type Target = AbstractAgeable; - fn deref(&self) -> &Self::Target { - &self.abstract_ageable - } -} -impl DerefMut for AbstractAnimal { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_ageable - } -} - -#[derive(Debug, Clone)] -pub struct AbstractCreature { - pub abstract_insentient: AbstractInsentient, -} - -impl AbstractCreature { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_insentient = AbstractInsentient::read(metadata)?; - Some(Self { - abstract_insentient, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_insentient.write()); - metadata - } -} - -impl Default for AbstractCreature { - fn default() -> Self { - Self { - abstract_insentient: Default::default(), + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAgeable::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle, +} +impl Default for AbstractAnimalMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, } } } +#[derive(Component)] +pub struct AbstractCreature; impl AbstractCreature { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_insentient.set_index(index, value) - } -} -impl Deref for AbstractCreature { - type Target = AbstractInsentient; - fn deref(&self) -> &Self::Target { - &self.abstract_insentient - } -} -impl DerefMut for AbstractCreature { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_insentient - } -} - -#[derive(Debug, Clone)] -pub struct AbstractEntity { - pub on_fire: bool, - pub shift_key_down: bool, - pub sprinting: bool, - pub swimming: bool, - pub currently_glowing: bool, - pub invisible: bool, - pub fall_flying: bool, - pub air_supply: i32, - pub custom_name: Option<Component>, - pub custom_name_visible: bool, - pub silent: bool, - pub no_gravity: bool, - pub pose: Pose, - pub ticks_frozen: i32, -} - -impl AbstractEntity { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let on_fire = bitfield & 0x1 != 0; - let shift_key_down = bitfield & 0x2 != 0; - let sprinting = bitfield & 0x8 != 0; - let swimming = bitfield & 0x10 != 0; - let currently_glowing = bitfield & 0x40 != 0; - let invisible = bitfield & 0x20 != 0; - let fall_flying = bitfield & 0x80 != 0; - let air_supply = metadata.pop_front()?.into_int().ok()?; - let custom_name = metadata.pop_front()?.into_optional_component().ok()?; - let custom_name_visible = metadata.pop_front()?.into_boolean().ok()?; - let silent = metadata.pop_front()?.into_boolean().ok()?; - let no_gravity = metadata.pop_front()?.into_boolean().ok()?; - let pose = metadata.pop_front()?.into_pose().ok()?; - let ticks_frozen = metadata.pop_front()?.into_int().ok()?; - Some(Self { - on_fire, - shift_key_down, - sprinting, - swimming, - currently_glowing, - invisible, - fall_flying, - air_supply, - custom_name, - custom_name_visible, - silent, - no_gravity, - pose, - ticks_frozen, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - let mut bitfield = 0u8; - if self.on_fire { - bitfield &= 0x1; - } - if self.shift_key_down { - bitfield &= 0x2; - } - if self.sprinting { - bitfield &= 0x8; - } - if self.swimming { - bitfield &= 0x10; - } - if self.currently_glowing { - bitfield &= 0x40; - } - if self.invisible { - bitfield &= 0x20; - } - if self.fall_flying { - bitfield &= 0x80; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Int(self.air_supply.clone())); - metadata.push(EntityDataValue::OptionalComponent(self.custom_name.clone())); - metadata.push(EntityDataValue::Boolean(self.custom_name_visible.clone())); - metadata.push(EntityDataValue::Boolean(self.silent.clone())); - metadata.push(EntityDataValue::Boolean(self.no_gravity.clone())); - metadata.push(EntityDataValue::Pose(self.pose.clone())); - metadata.push(EntityDataValue::Int(self.ticks_frozen.clone())); - metadata - } -} - -impl Default for AbstractEntity { - fn default() -> Self { - Self { - on_fire: false, - shift_key_down: false, - sprinting: false, - swimming: false, - currently_glowing: false, - invisible: false, - fall_flying: false, - air_supply: Default::default(), - custom_name: None, - custom_name_visible: false, - silent: false, - no_gravity: false, - pose: Default::default(), - ticks_frozen: 0, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractInsentient::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle, +} +impl Default for AbstractCreatureMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, } } } +#[derive(Component)] +pub struct AbstractEntity; impl AbstractEntity { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { 0 => { - let bitfield = value.into_byte().ok()?; - self.on_fire = bitfield & 0x1 != 0; - self.shift_key_down = bitfield & 0x2 != 0; - self.sprinting = bitfield & 0x8 != 0; - self.swimming = bitfield & 0x10 != 0; - self.currently_glowing = bitfield & 0x40 != 0; - self.invisible = bitfield & 0x20 != 0; - self.fall_flying = bitfield & 0x80 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(OnFire(bitfield & 0x1 != 0)); + entity.insert(ShiftKeyDown(bitfield & 0x2 != 0)); + entity.insert(Sprinting(bitfield & 0x8 != 0)); + entity.insert(Swimming(bitfield & 0x10 != 0)); + entity.insert(CurrentlyGlowing(bitfield & 0x40 != 0)); + entity.insert(Invisible(bitfield & 0x20 != 0)); + entity.insert(FallFlying(bitfield & 0x80 != 0)); + } + 1 => { + entity.insert(AirSupply(d.value.into_int()?)); + } + 2 => { + entity.insert(CustomName(d.value.into_optional_formatted_text()?)); + } + 3 => { + entity.insert(CustomNameVisible(d.value.into_boolean()?)); + } + 4 => { + entity.insert(Silent(d.value.into_boolean()?)); + } + 5 => { + entity.insert(NoGravity(d.value.into_boolean()?)); + } + 6 => { + entity.insert(d.value.into_pose()?); + } + 7 => { + entity.insert(TicksFrozen(d.value.into_int()?)); } - 1 => self.air_supply = value.into_int().ok()?, - 2 => self.custom_name = value.into_optional_component().ok()?, - 3 => self.custom_name_visible = value.into_boolean().ok()?, - 4 => self.silent = value.into_boolean().ok()?, - 5 => self.no_gravity = value.into_boolean().ok()?, - 6 => self.pose = value.into_pose().ok()?, - 7 => self.ticks_frozen = value.into_int().ok()?, _ => {} } - Some(()) + Ok(()) } } -#[derive(Debug, Clone)] -pub struct AbstractInsentient { - pub abstract_living: AbstractLiving, - pub no_ai: bool, - pub left_handed: bool, - pub aggressive: bool, -} - -impl AbstractInsentient { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_living = AbstractLiving::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let no_ai = bitfield & 0x1 != 0; - let left_handed = bitfield & 0x2 != 0; - let aggressive = bitfield & 0x4 != 0; - Some(Self { - abstract_living, - no_ai, - left_handed, - aggressive, - }) - } - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_living.write()); - let mut bitfield = 0u8; - if self.no_ai { - bitfield &= 0x1; - } - if self.left_handed { - bitfield &= 0x2; - } - if self.aggressive { - bitfield &= 0x4; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata - } +#[derive(Bundle)] +pub struct AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire, + shift_key_down: ShiftKeyDown, + sprinting: Sprinting, + swimming: Swimming, + currently_glowing: CurrentlyGlowing, + invisible: Invisible, + fall_flying: FallFlying, + air_supply: AirSupply, + custom_name: CustomName, + custom_name_visible: CustomNameVisible, + silent: Silent, + no_gravity: NoGravity, + pose: Pose, + ticks_frozen: TicksFrozen, } - -impl Default for AbstractInsentient { +impl Default for AbstractEntityMetadataBundle { fn default() -> Self { Self { - abstract_living: Default::default(), - no_ai: false, - left_handed: false, - aggressive: false, + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), } } } +#[derive(Component)] +pub struct AbstractInsentient; impl AbstractInsentient { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=14 => self.abstract_living.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=14 => AbstractLiving::apply_metadata(entity, d)?, 15 => { - let bitfield = value.into_byte().ok()?; - self.no_ai = bitfield & 0x1 != 0; - self.left_handed = bitfield & 0x2 != 0; - self.aggressive = bitfield & 0x4 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(NoAi(bitfield & 0x1 != 0)); + entity.insert(LeftHanded(bitfield & 0x2 != 0)); + entity.insert(Aggressive(bitfield & 0x4 != 0)); } _ => {} } - Some(()) - } -} -impl Deref for AbstractInsentient { - type Target = AbstractLiving; - fn deref(&self) -> &Self::Target { - &self.abstract_living - } -} -impl DerefMut for AbstractInsentient { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_living - } -} - -#[derive(Debug, Clone)] -pub struct AbstractLiving { - pub abstract_entity: AbstractEntity, - pub auto_spin_attack: bool, - pub using_item: bool, - pub health: f32, - pub effect_color: i32, - pub effect_ambience: bool, - pub arrow_count: i32, - pub stinger_count: i32, - pub sleeping_pos: Option<BlockPos>, -} - -impl AbstractLiving { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let auto_spin_attack = bitfield & 0x4 != 0; - let using_item = bitfield & 0x1 != 0; - let health = metadata.pop_front()?.into_float().ok()?; - let effect_color = metadata.pop_front()?.into_int().ok()?; - let effect_ambience = metadata.pop_front()?.into_boolean().ok()?; - let arrow_count = metadata.pop_front()?.into_int().ok()?; - let stinger_count = metadata.pop_front()?.into_int().ok()?; - let sleeping_pos = metadata.pop_front()?.into_optional_block_pos().ok()?; - Some(Self { - abstract_entity, - auto_spin_attack, - using_item, - health, - effect_color, - effect_ambience, - arrow_count, - stinger_count, - sleeping_pos, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - let mut bitfield = 0u8; - if self.auto_spin_attack { - bitfield &= 0x4; - } - if self.using_item { - bitfield &= 0x1; - } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::Float(self.health.clone())); - metadata.push(EntityDataValue::Int(self.effect_color.clone())); - metadata.push(EntityDataValue::Boolean(self.effect_ambience.clone())); - metadata.push(EntityDataValue::Int(self.arrow_count.clone())); - metadata.push(EntityDataValue::Int(self.stinger_count.clone())); - metadata.push(EntityDataValue::OptionalBlockPos(self.sleeping_pos.clone())); - metadata - } -} - -impl Default for AbstractLiving { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - auto_spin_attack: false, - using_item: false, - health: 1.0, - effect_color: 0, - effect_ambience: false, - arrow_count: 0, - stinger_count: 0, - sleeping_pos: None, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle, + no_ai: NoAi, + left_handed: LeftHanded, + aggressive: Aggressive, +} +impl Default for AbstractInsentientMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), } } } +#[derive(Component)] +pub struct AbstractLiving; impl AbstractLiving { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, 8 => { - let bitfield = value.into_byte().ok()?; - self.auto_spin_attack = bitfield & 0x4 != 0; - self.using_item = bitfield & 0x1 != 0; + let bitfield = d.value.into_byte()?; + entity.insert(AutoSpinAttack(bitfield & 0x4 != 0)); + entity.insert(AbstractLivingUsingItem(bitfield & 0x1 != 0)); + } + 9 => { + entity.insert(Health(d.value.into_float()?)); + } + 10 => { + entity.insert(AbstractLivingEffectColor(d.value.into_int()?)); + } + 11 => { + entity.insert(EffectAmbience(d.value.into_boolean()?)); + } + 12 => { + entity.insert(ArrowCount(d.value.into_int()?)); + } + 13 => { + entity.insert(StingerCount(d.value.into_int()?)); + } + 14 => { + entity.insert(SleepingPos(d.value.into_optional_block_pos()?)); } - 9 => self.health = value.into_float().ok()?, - 10 => self.effect_color = value.into_int().ok()?, - 11 => self.effect_ambience = value.into_boolean().ok()?, - 12 => self.arrow_count = value.into_int().ok()?, - 13 => self.stinger_count = value.into_int().ok()?, - 14 => self.sleeping_pos = value.into_optional_block_pos().ok()?, _ => {} } - Some(()) - } -} -impl Deref for AbstractLiving { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for AbstractLiving { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct AbstractMinecart { - pub abstract_entity: AbstractEntity, - pub hurt: i32, - pub hurtdir: i32, - pub damage: f32, - pub display_block: i32, - pub display_offset: i32, - pub custom_display: bool, -} - -impl AbstractMinecart { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_entity = AbstractEntity::read(metadata)?; - let hurt = metadata.pop_front()?.into_int().ok()?; - let hurtdir = metadata.pop_front()?.into_int().ok()?; - let damage = metadata.pop_front()?.into_float().ok()?; - let display_block = metadata.pop_front()?.into_int().ok()?; - let display_offset = metadata.pop_front()?.into_int().ok()?; - let custom_display = metadata.pop_front()?.into_boolean().ok()?; - Some(Self { - abstract_entity, - hurt, - hurtdir, - damage, - display_block, - display_offset, - custom_display, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_entity.write()); - metadata.push(EntityDataValue::Int(self.hurt.clone())); - metadata.push(EntityDataValue::Int(self.hurtdir.clone())); - metadata.push(EntityDataValue::Float(self.damage.clone())); - metadata.push(EntityDataValue::Int(self.display_block.clone())); - metadata.push(EntityDataValue::Int(self.display_offset.clone())); - metadata.push(EntityDataValue::Boolean(self.custom_display.clone())); - metadata - } -} - -impl Default for AbstractMinecart { - fn default() -> Self { - Self { - abstract_entity: Default::default(), - hurt: 0, - hurtdir: 1, - damage: 0.0, - display_block: Default::default(), - display_offset: 6, - custom_display: false, + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle, + auto_spin_attack: AutoSpinAttack, + abstract_living_using_item: AbstractLivingUsingItem, + health: Health, + abstract_living_effect_color: AbstractLivingEffectColor, + effect_ambience: EffectAmbience, + arrow_count: ArrowCount, + stinger_count: StingerCount, + sleeping_pos: SleepingPos, +} +impl Default for AbstractLivingMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), } } } +#[derive(Component)] +pub struct AbstractMinecart; impl AbstractMinecart { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=7 => self.abstract_entity.set_index(index, value)?, - 8 => self.hurt = value.into_int().ok()?, - 9 => self.hurtdir = value.into_int().ok()?, - 10 => self.damage = value.into_float().ok()?, - 11 => self.display_block = value.into_int().ok()?, - 12 => self.display_offset = value.into_int().ok()?, - 13 => self.custom_display = value.into_boolean().ok()?, + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=7 => AbstractEntity::apply_metadata(entity, d)?, + 8 => { + entity.insert(AbstractMinecartHurt(d.value.into_int()?)); + } + 9 => { + entity.insert(AbstractMinecartHurtdir(d.value.into_int()?)); + } + 10 => { + entity.insert(AbstractMinecartDamage(d.value.into_float()?)); + } + 11 => { + entity.insert(DisplayBlock(d.value.into_int()?)); + } + 12 => { + entity.insert(DisplayOffset(d.value.into_int()?)); + } + 13 => { + entity.insert(CustomDisplay(d.value.into_boolean()?)); + } _ => {} } - Some(()) - } -} -impl Deref for AbstractMinecart { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - &self.abstract_entity - } -} -impl DerefMut for AbstractMinecart { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_entity - } -} - -#[derive(Debug, Clone)] -pub struct AbstractMonster { - pub abstract_creature: AbstractCreature, -} - -impl AbstractMonster { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_creature = AbstractCreature::read(metadata)?; - Some(Self { abstract_creature }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_creature.write()); - metadata - } -} - -impl Default for AbstractMonster { - fn default() -> Self { - Self { - abstract_creature: Default::default(), + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractMinecartMetadataBundle { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle, + abstract_minecart_hurt: AbstractMinecartHurt, + abstract_minecart_hurtdir: AbstractMinecartHurtdir, + abstract_minecart_damage: AbstractMinecartDamage, + display_block: DisplayBlock, + display_offset: DisplayOffset, + custom_display: CustomDisplay, +} +impl Default for AbstractMinecartMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractMinecart, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + abstract_minecart_hurt: AbstractMinecartHurt(0), + abstract_minecart_hurtdir: AbstractMinecartHurtdir(1), + abstract_minecart_damage: AbstractMinecartDamage(0.0), + display_block: DisplayBlock(Default::default()), + display_offset: DisplayOffset(6), + custom_display: CustomDisplay(false), } } } +#[derive(Component)] +pub struct AbstractMonster; impl AbstractMonster { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - self.abstract_creature.set_index(index, value) - } -} -impl Deref for AbstractMonster { - type Target = AbstractCreature; - fn deref(&self) -> &Self::Target { - &self.abstract_creature - } -} -impl DerefMut for AbstractMonster { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_creature + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=15 => AbstractCreature::apply_metadata(entity, d)?, + _ => {} + } + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractMonsterMetadataBundle { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle, +} +impl Default for AbstractMonsterMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractMonster, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + } } } -#[derive(Debug, Clone)] -pub struct AbstractTameable { - pub abstract_animal: AbstractAnimal, - pub tame: bool, - pub in_sitting_pose: bool, - pub owneruuid: Option<Uuid>, -} - +#[derive(Component)] +pub struct AbstractTameable; impl AbstractTameable { - pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> { - let abstract_animal = AbstractAnimal::read(metadata)?; - let bitfield = metadata.pop_front()?.into_byte().ok()?; - let tame = bitfield & 0x4 != 0; - let in_sitting_pose = bitfield & 0x1 != 0; - let owneruuid = metadata.pop_front()?.into_optional_uuid().ok()?; - Some(Self { - abstract_animal, - tame, - in_sitting_pose, - owneruuid, - }) - } - - pub fn write(&self) -> Vec<EntityDataValue> { - let mut metadata = Vec::new(); - metadata.extend(self.abstract_animal.write()); - let mut bitfield = 0u8; - if self.tame { - bitfield &= 0x4; + pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + d: EntityDataItem, + ) -> Result<(), UpdateMetadataError> { + match d.index { + 0..=16 => AbstractAnimal::apply_metadata(entity, d)?, + 17 => { + let bitfield = d.value.into_byte()?; + entity.insert(Tame(bitfield & 0x4 != 0)); + entity.insert(InSittingPose(bitfield & 0x1 != 0)); + } + 18 => { + entity.insert(Owneruuid(d.value.into_optional_uuid()?)); + } + _ => {} } - if self.in_sitting_pose { - bitfield &= 0x1; + Ok(()) + } +} + +#[derive(Bundle)] +pub struct AbstractTameableMetadataBundle { + _marker: AbstractTameable, + parent: AbstractAnimalMetadataBundle, + tame: Tame, + in_sitting_pose: InSittingPose, + owneruuid: Owneruuid, +} +impl Default for AbstractTameableMetadataBundle { + fn default() -> Self { + Self { + _marker: AbstractTameable, + parent: AbstractAnimalMetadataBundle { + _marker: AbstractAnimal, + parent: AbstractAgeableMetadataBundle { + _marker: AbstractAgeable, + parent: AbstractCreatureMetadataBundle { + _marker: AbstractCreature, + parent: AbstractInsentientMetadataBundle { + _marker: AbstractInsentient, + parent: AbstractLivingMetadataBundle { + _marker: AbstractLiving, + parent: AbstractEntityMetadataBundle { + _marker: AbstractEntity, + on_fire: OnFire(false), + shift_key_down: ShiftKeyDown(false), + sprinting: Sprinting(false), + swimming: Swimming(false), + currently_glowing: CurrentlyGlowing(false), + invisible: Invisible(false), + fall_flying: FallFlying(false), + air_supply: AirSupply(Default::default()), + custom_name: CustomName(None), + custom_name_visible: CustomNameVisible(false), + silent: Silent(false), + no_gravity: NoGravity(false), + pose: Pose::default(), + ticks_frozen: TicksFrozen(0), + }, + auto_spin_attack: AutoSpinAttack(false), + abstract_living_using_item: AbstractLivingUsingItem(false), + health: Health(1.0), + abstract_living_effect_color: AbstractLivingEffectColor(0), + effect_ambience: EffectAmbience(false), + arrow_count: ArrowCount(0), + stinger_count: StingerCount(0), + sleeping_pos: SleepingPos(None), + }, + no_ai: NoAi(false), + left_handed: LeftHanded(false), + aggressive: Aggressive(false), + }, + }, + abstract_ageable_baby: AbstractAgeableBaby(false), + }, + }, + tame: Tame(false), + in_sitting_pose: InSittingPose(false), + owneruuid: Owneruuid(None), } - metadata.push(EntityDataValue::Byte(bitfield)); - metadata.push(EntityDataValue::OptionalUuid(self.owneruuid.clone())); - metadata } } -impl Default for AbstractTameable { - fn default() -> Self { - Self { - abstract_animal: Default::default(), - tame: false, - in_sitting_pose: false, - owneruuid: None, +pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + entity_kind: azalea_registry::EntityKind, + items: Vec<EntityDataItem>, +) -> Result<(), UpdateMetadataError> { + match entity_kind { + azalea_registry::EntityKind::Allay => { + for d in items { + Allay::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::AreaEffectCloud => { + for d in items { + AreaEffectCloud::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ArmorStand => { + for d in items { + ArmorStand::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Arrow => { + for d in items { + Arrow::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Axolotl => { + for d in items { + Axolotl::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Bat => { + for d in items { + Bat::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Bee => { + for d in items { + Bee::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Blaze => { + for d in items { + Blaze::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Boat => { + for d in items { + Boat::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Camel => { + for d in items { + Camel::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Cat => { + for d in items { + Cat::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::CaveSpider => { + for d in items { + CaveSpider::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ChestBoat => { + for d in items { + ChestBoat::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ChestMinecart => { + for d in items { + ChestMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Chicken => { + for d in items { + Chicken::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Cod => { + for d in items { + Cod::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::CommandBlockMinecart => { + for d in items { + CommandBlockMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Cow => { + for d in items { + Cow::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Creeper => { + for d in items { + Creeper::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Dolphin => { + for d in items { + Dolphin::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Donkey => { + for d in items { + Donkey::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::DragonFireball => { + for d in items { + DragonFireball::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Drowned => { + for d in items { + Drowned::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Egg => { + for d in items { + Egg::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ElderGuardian => { + for d in items { + ElderGuardian::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::EndCrystal => { + for d in items { + EndCrystal::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::EnderDragon => { + for d in items { + EnderDragon::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::EnderPearl => { + for d in items { + EnderPearl::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Enderman => { + for d in items { + Enderman::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Endermite => { + for d in items { + Endermite::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Evoker => { + for d in items { + Evoker::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::EvokerFangs => { + for d in items { + EvokerFangs::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ExperienceBottle => { + for d in items { + ExperienceBottle::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ExperienceOrb => { + for d in items { + ExperienceOrb::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::EyeOfEnder => { + for d in items { + EyeOfEnder::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::FallingBlock => { + for d in items { + FallingBlock::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Fireball => { + for d in items { + Fireball::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::FireworkRocket => { + for d in items { + FireworkRocket::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::FishingBobber => { + for d in items { + FishingBobber::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Fox => { + for d in items { + Fox::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Frog => { + for d in items { + Frog::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::FurnaceMinecart => { + for d in items { + FurnaceMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Ghast => { + for d in items { + Ghast::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Giant => { + for d in items { + Giant::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::GlowItemFrame => { + for d in items { + GlowItemFrame::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::GlowSquid => { + for d in items { + GlowSquid::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Goat => { + for d in items { + Goat::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Guardian => { + for d in items { + Guardian::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Hoglin => { + for d in items { + Hoglin::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::HopperMinecart => { + for d in items { + HopperMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Horse => { + for d in items { + Horse::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Husk => { + for d in items { + Husk::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Illusioner => { + for d in items { + Illusioner::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::IronGolem => { + for d in items { + IronGolem::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Item => { + for d in items { + Item::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ItemFrame => { + for d in items { + ItemFrame::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::LeashKnot => { + for d in items { + LeashKnot::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::LightningBolt => { + for d in items { + LightningBolt::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Llama => { + for d in items { + Llama::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::LlamaSpit => { + for d in items { + LlamaSpit::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::MagmaCube => { + for d in items { + MagmaCube::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Marker => { + for d in items { + Marker::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Minecart => { + for d in items { + Minecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Mooshroom => { + for d in items { + Mooshroom::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Mule => { + for d in items { + Mule::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Ocelot => { + for d in items { + Ocelot::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Painting => { + for d in items { + Painting::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Panda => { + for d in items { + Panda::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Parrot => { + for d in items { + Parrot::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Phantom => { + for d in items { + Phantom::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Pig => { + for d in items { + Pig::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Piglin => { + for d in items { + Piglin::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::PiglinBrute => { + for d in items { + PiglinBrute::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Pillager => { + for d in items { + Pillager::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Player => { + for d in items { + Player::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::PolarBear => { + for d in items { + PolarBear::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Potion => { + for d in items { + Potion::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Pufferfish => { + for d in items { + Pufferfish::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Rabbit => { + for d in items { + Rabbit::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Ravager => { + for d in items { + Ravager::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Salmon => { + for d in items { + Salmon::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Sheep => { + for d in items { + Sheep::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Shulker => { + for d in items { + Shulker::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ShulkerBullet => { + for d in items { + ShulkerBullet::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Silverfish => { + for d in items { + Silverfish::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Skeleton => { + for d in items { + Skeleton::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::SkeletonHorse => { + for d in items { + SkeletonHorse::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Slime => { + for d in items { + Slime::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::SmallFireball => { + for d in items { + SmallFireball::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::SnowGolem => { + for d in items { + SnowGolem::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Snowball => { + for d in items { + Snowball::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::SpawnerMinecart => { + for d in items { + SpawnerMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::SpectralArrow => { + for d in items { + SpectralArrow::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Spider => { + for d in items { + Spider::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Squid => { + for d in items { + Squid::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Stray => { + for d in items { + Stray::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Strider => { + for d in items { + Strider::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Tadpole => { + for d in items { + Tadpole::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Tnt => { + for d in items { + Tnt::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::TntMinecart => { + for d in items { + TntMinecart::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::TraderLlama => { + for d in items { + TraderLlama::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Trident => { + for d in items { + Trident::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::TropicalFish => { + for d in items { + TropicalFish::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Turtle => { + for d in items { + Turtle::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Vex => { + for d in items { + Vex::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Villager => { + for d in items { + Villager::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Vindicator => { + for d in items { + Vindicator::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::WanderingTrader => { + for d in items { + WanderingTrader::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Warden => { + for d in items { + Warden::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Witch => { + for d in items { + Witch::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Wither => { + for d in items { + Wither::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::WitherSkeleton => { + for d in items { + WitherSkeleton::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::WitherSkull => { + for d in items { + WitherSkull::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Wolf => { + for d in items { + Wolf::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Zoglin => { + for d in items { + Zoglin::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::Zombie => { + for d in items { + Zombie::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ZombieHorse => { + for d in items { + ZombieHorse::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ZombieVillager => { + for d in items { + ZombieVillager::apply_metadata(entity, d)?; + } + } + azalea_registry::EntityKind::ZombifiedPiglin => { + for d in items { + ZombifiedPiglin::apply_metadata(entity, d)?; + } } } + Ok(()) } -impl AbstractTameable { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match index { - 0..=16 => self.abstract_animal.set_index(index, value)?, - 17 => { - let bitfield = value.into_byte().ok()?; - self.tame = bitfield & 0x4 != 0; - self.in_sitting_pose = bitfield & 0x1 != 0; - } - 18 => self.owneruuid = value.into_optional_uuid().ok()?, - _ => {} - } - Some(()) - } -} -impl Deref for AbstractTameable { - type Target = AbstractAnimal; - fn deref(&self) -> &Self::Target { - &self.abstract_animal - } -} -impl DerefMut for AbstractTameable { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.abstract_animal - } -} - -#[derive(Debug, Clone, EnumAsInner)] -pub enum EntityMetadata { - Allay(Allay), - AreaEffectCloud(AreaEffectCloud), - ArmorStand(ArmorStand), - Arrow(Arrow), - Axolotl(Axolotl), - Bat(Bat), - Bee(Bee), - Blaze(Blaze), - Boat(Boat), - Camel(Camel), - Cat(Cat), - CaveSpider(CaveSpider), - ChestBoat(ChestBoat), - ChestMinecart(ChestMinecart), - Chicken(Chicken), - Cod(Cod), - CommandBlockMinecart(CommandBlockMinecart), - Cow(Cow), - Creeper(Creeper), - Dolphin(Dolphin), - Donkey(Donkey), - DragonFireball(DragonFireball), - Drowned(Drowned), - Egg(Egg), - ElderGuardian(ElderGuardian), - EndCrystal(EndCrystal), - EnderDragon(EnderDragon), - EnderPearl(EnderPearl), - Enderman(Enderman), - Endermite(Endermite), - Evoker(Evoker), - EvokerFangs(EvokerFangs), - ExperienceBottle(ExperienceBottle), - ExperienceOrb(ExperienceOrb), - EyeOfEnder(EyeOfEnder), - FallingBlock(FallingBlock), - Fireball(Fireball), - FireworkRocket(FireworkRocket), - FishingBobber(FishingBobber), - Fox(Fox), - Frog(Frog), - FurnaceMinecart(FurnaceMinecart), - Ghast(Ghast), - Giant(Giant), - GlowItemFrame(GlowItemFrame), - GlowSquid(GlowSquid), - Goat(Goat), - Guardian(Guardian), - Hoglin(Hoglin), - HopperMinecart(HopperMinecart), - Horse(Horse), - Husk(Husk), - Illusioner(Illusioner), - IronGolem(IronGolem), - Item(Item), - ItemFrame(ItemFrame), - LeashKnot(LeashKnot), - LightningBolt(LightningBolt), - Llama(Llama), - LlamaSpit(LlamaSpit), - MagmaCube(MagmaCube), - Marker(Marker), - Minecart(Minecart), - Mooshroom(Mooshroom), - Mule(Mule), - Ocelot(Ocelot), - Painting(Painting), - Panda(Panda), - Parrot(Parrot), - Phantom(Phantom), - Pig(Pig), - Piglin(Piglin), - PiglinBrute(PiglinBrute), - Pillager(Pillager), - Player(Player), - PolarBear(PolarBear), - Potion(Potion), - Pufferfish(Pufferfish), - Rabbit(Rabbit), - Ravager(Ravager), - Salmon(Salmon), - Sheep(Sheep), - Shulker(Shulker), - ShulkerBullet(ShulkerBullet), - Silverfish(Silverfish), - Skeleton(Skeleton), - SkeletonHorse(SkeletonHorse), - Slime(Slime), - SmallFireball(SmallFireball), - SnowGolem(SnowGolem), - Snowball(Snowball), - SpawnerMinecart(SpawnerMinecart), - SpectralArrow(SpectralArrow), - Spider(Spider), - Squid(Squid), - Stray(Stray), - Strider(Strider), - Tadpole(Tadpole), - Tnt(Tnt), - TntMinecart(TntMinecart), - TraderLlama(TraderLlama), - Trident(Trident), - TropicalFish(TropicalFish), - Turtle(Turtle), - Vex(Vex), - Villager(Villager), - Vindicator(Vindicator), - WanderingTrader(WanderingTrader), - Warden(Warden), - Witch(Witch), - Wither(Wither), - WitherSkeleton(WitherSkeleton), - WitherSkull(WitherSkull), - Wolf(Wolf), - Zoglin(Zoglin), - Zombie(Zombie), - ZombieHorse(ZombieHorse), - ZombieVillager(ZombieVillager), - ZombifiedPiglin(ZombifiedPiglin), -} - -impl From<azalea_registry::EntityType> for EntityMetadata { - fn from(value: azalea_registry::EntityType) -> Self { - match value { - azalea_registry::EntityType::Allay => EntityMetadata::Allay(Allay::default()), - azalea_registry::EntityType::AreaEffectCloud => { - EntityMetadata::AreaEffectCloud(AreaEffectCloud::default()) - } - azalea_registry::EntityType::ArmorStand => { - EntityMetadata::ArmorStand(ArmorStand::default()) - } - azalea_registry::EntityType::Arrow => EntityMetadata::Arrow(Arrow::default()), - azalea_registry::EntityType::Axolotl => EntityMetadata::Axolotl(Axolotl::default()), - azalea_registry::EntityType::Bat => EntityMetadata::Bat(Bat::default()), - azalea_registry::EntityType::Bee => EntityMetadata::Bee(Bee::default()), - azalea_registry::EntityType::Blaze => EntityMetadata::Blaze(Blaze::default()), - azalea_registry::EntityType::Boat => EntityMetadata::Boat(Boat::default()), - azalea_registry::EntityType::Camel => EntityMetadata::Camel(Camel::default()), - azalea_registry::EntityType::Cat => EntityMetadata::Cat(Cat::default()), - azalea_registry::EntityType::CaveSpider => { - EntityMetadata::CaveSpider(CaveSpider::default()) - } - azalea_registry::EntityType::ChestBoat => { - EntityMetadata::ChestBoat(ChestBoat::default()) - } - azalea_registry::EntityType::ChestMinecart => { - EntityMetadata::ChestMinecart(ChestMinecart::default()) - } - azalea_registry::EntityType::Chicken => EntityMetadata::Chicken(Chicken::default()), - azalea_registry::EntityType::Cod => EntityMetadata::Cod(Cod::default()), - azalea_registry::EntityType::CommandBlockMinecart => { - EntityMetadata::CommandBlockMinecart(CommandBlockMinecart::default()) - } - azalea_registry::EntityType::Cow => EntityMetadata::Cow(Cow::default()), - azalea_registry::EntityType::Creeper => EntityMetadata::Creeper(Creeper::default()), - azalea_registry::EntityType::Dolphin => EntityMetadata::Dolphin(Dolphin::default()), - azalea_registry::EntityType::Donkey => EntityMetadata::Donkey(Donkey::default()), - azalea_registry::EntityType::DragonFireball => { - EntityMetadata::DragonFireball(DragonFireball::default()) - } - azalea_registry::EntityType::Drowned => EntityMetadata::Drowned(Drowned::default()), - azalea_registry::EntityType::Egg => EntityMetadata::Egg(Egg::default()), - azalea_registry::EntityType::ElderGuardian => { - EntityMetadata::ElderGuardian(ElderGuardian::default()) - } - azalea_registry::EntityType::EndCrystal => { - EntityMetadata::EndCrystal(EndCrystal::default()) - } - azalea_registry::EntityType::EnderDragon => { - EntityMetadata::EnderDragon(EnderDragon::default()) - } - azalea_registry::EntityType::EnderPearl => { - EntityMetadata::EnderPearl(EnderPearl::default()) - } - azalea_registry::EntityType::Enderman => EntityMetadata::Enderman(Enderman::default()), - azalea_registry::EntityType::Endermite => { - EntityMetadata::Endermite(Endermite::default()) - } - azalea_registry::EntityType::Evoker => EntityMetadata::Evoker(Evoker::default()), - azalea_registry::EntityType::EvokerFangs => { - EntityMetadata::EvokerFangs(EvokerFangs::default()) - } - azalea_registry::EntityType::ExperienceBottle => { - EntityMetadata::ExperienceBottle(ExperienceBottle::default()) - } - azalea_registry::EntityType::ExperienceOrb => { - EntityMetadata::ExperienceOrb(ExperienceOrb::default()) - } - azalea_registry::EntityType::EyeOfEnder => { - EntityMetadata::EyeOfEnder(EyeOfEnder::default()) - } - azalea_registry::EntityType::FallingBlock => { - EntityMetadata::FallingBlock(FallingBlock::default()) - } - azalea_registry::EntityType::Fireball => EntityMetadata::Fireball(Fireball::default()), - azalea_registry::EntityType::FireworkRocket => { - EntityMetadata::FireworkRocket(FireworkRocket::default()) - } - azalea_registry::EntityType::FishingBobber => { - EntityMetadata::FishingBobber(FishingBobber::default()) - } - azalea_registry::EntityType::Fox => EntityMetadata::Fox(Fox::default()), - azalea_registry::EntityType::Frog => EntityMetadata::Frog(Frog::default()), - azalea_registry::EntityType::FurnaceMinecart => { - EntityMetadata::FurnaceMinecart(FurnaceMinecart::default()) - } - azalea_registry::EntityType::Ghast => EntityMetadata::Ghast(Ghast::default()), - azalea_registry::EntityType::Giant => EntityMetadata::Giant(Giant::default()), - azalea_registry::EntityType::GlowItemFrame => { - EntityMetadata::GlowItemFrame(GlowItemFrame::default()) - } - azalea_registry::EntityType::GlowSquid => { - EntityMetadata::GlowSquid(GlowSquid::default()) - } - azalea_registry::EntityType::Goat => EntityMetadata::Goat(Goat::default()), - azalea_registry::EntityType::Guardian => EntityMetadata::Guardian(Guardian::default()), - azalea_registry::EntityType::Hoglin => EntityMetadata::Hoglin(Hoglin::default()), - azalea_registry::EntityType::HopperMinecart => { - EntityMetadata::HopperMinecart(HopperMinecart::default()) - } - azalea_registry::EntityType::Horse => EntityMetadata::Horse(Horse::default()), - azalea_registry::EntityType::Husk => EntityMetadata::Husk(Husk::default()), - azalea_registry::EntityType::Illusioner => { - EntityMetadata::Illusioner(Illusioner::default()) - } - azalea_registry::EntityType::IronGolem => { - EntityMetadata::IronGolem(IronGolem::default()) - } - azalea_registry::EntityType::Item => EntityMetadata::Item(Item::default()), - azalea_registry::EntityType::ItemFrame => { - EntityMetadata::ItemFrame(ItemFrame::default()) - } - azalea_registry::EntityType::LeashKnot => { - EntityMetadata::LeashKnot(LeashKnot::default()) - } - azalea_registry::EntityType::LightningBolt => { - EntityMetadata::LightningBolt(LightningBolt::default()) - } - azalea_registry::EntityType::Llama => EntityMetadata::Llama(Llama::default()), - azalea_registry::EntityType::LlamaSpit => { - EntityMetadata::LlamaSpit(LlamaSpit::default()) - } - azalea_registry::EntityType::MagmaCube => { - EntityMetadata::MagmaCube(MagmaCube::default()) - } - azalea_registry::EntityType::Marker => EntityMetadata::Marker(Marker::default()), - azalea_registry::EntityType::Minecart => EntityMetadata::Minecart(Minecart::default()), - azalea_registry::EntityType::Mooshroom => { - EntityMetadata::Mooshroom(Mooshroom::default()) - } - azalea_registry::EntityType::Mule => EntityMetadata::Mule(Mule::default()), - azalea_registry::EntityType::Ocelot => EntityMetadata::Ocelot(Ocelot::default()), - azalea_registry::EntityType::Painting => EntityMetadata::Painting(Painting::default()), - azalea_registry::EntityType::Panda => EntityMetadata::Panda(Panda::default()), - azalea_registry::EntityType::Parrot => EntityMetadata::Parrot(Parrot::default()), - azalea_registry::EntityType::Phantom => EntityMetadata::Phantom(Phantom::default()), - azalea_registry::EntityType::Pig => EntityMetadata::Pig(Pig::default()), - azalea_registry::EntityType::Piglin => EntityMetadata::Piglin(Piglin::default()), - azalea_registry::EntityType::PiglinBrute => { - EntityMetadata::PiglinBrute(PiglinBrute::default()) - } - azalea_registry::EntityType::Pillager => EntityMetadata::Pillager(Pillager::default()), - azalea_registry::EntityType::Player => EntityMetadata::Player(Player::default()), - azalea_registry::EntityType::PolarBear => { - EntityMetadata::PolarBear(PolarBear::default()) - } - azalea_registry::EntityType::Potion => EntityMetadata::Potion(Potion::default()), - azalea_registry::EntityType::Pufferfish => { - EntityMetadata::Pufferfish(Pufferfish::default()) - } - azalea_registry::EntityType::Rabbit => EntityMetadata::Rabbit(Rabbit::default()), - azalea_registry::EntityType::Ravager => EntityMetadata::Ravager(Ravager::default()), - azalea_registry::EntityType::Salmon => EntityMetadata::Salmon(Salmon::default()), - azalea_registry::EntityType::Sheep => EntityMetadata::Sheep(Sheep::default()), - azalea_registry::EntityType::Shulker => EntityMetadata::Shulker(Shulker::default()), - azalea_registry::EntityType::ShulkerBullet => { - EntityMetadata::ShulkerBullet(ShulkerBullet::default()) - } - azalea_registry::EntityType::Silverfish => { - EntityMetadata::Silverfish(Silverfish::default()) - } - azalea_registry::EntityType::Skeleton => EntityMetadata::Skeleton(Skeleton::default()), - azalea_registry::EntityType::SkeletonHorse => { - EntityMetadata::SkeletonHorse(SkeletonHorse::default()) - } - azalea_registry::EntityType::Slime => EntityMetadata::Slime(Slime::default()), - azalea_registry::EntityType::SmallFireball => { - EntityMetadata::SmallFireball(SmallFireball::default()) - } - azalea_registry::EntityType::SnowGolem => { - EntityMetadata::SnowGolem(SnowGolem::default()) - } - azalea_registry::EntityType::Snowball => EntityMetadata::Snowball(Snowball::default()), - azalea_registry::EntityType::SpawnerMinecart => { - EntityMetadata::SpawnerMinecart(SpawnerMinecart::default()) - } - azalea_registry::EntityType::SpectralArrow => { - EntityMetadata::SpectralArrow(SpectralArrow::default()) - } - azalea_registry::EntityType::Spider => EntityMetadata::Spider(Spider::default()), - azalea_registry::EntityType::Squid => EntityMetadata::Squid(Squid::default()), - azalea_registry::EntityType::Stray => EntityMetadata::Stray(Stray::default()), - azalea_registry::EntityType::Strider => EntityMetadata::Strider(Strider::default()), - azalea_registry::EntityType::Tadpole => EntityMetadata::Tadpole(Tadpole::default()), - azalea_registry::EntityType::Tnt => EntityMetadata::Tnt(Tnt::default()), - azalea_registry::EntityType::TntMinecart => { - EntityMetadata::TntMinecart(TntMinecart::default()) - } - azalea_registry::EntityType::TraderLlama => { - EntityMetadata::TraderLlama(TraderLlama::default()) - } - azalea_registry::EntityType::Trident => EntityMetadata::Trident(Trident::default()), - azalea_registry::EntityType::TropicalFish => { - EntityMetadata::TropicalFish(TropicalFish::default()) - } - azalea_registry::EntityType::Turtle => EntityMetadata::Turtle(Turtle::default()), - azalea_registry::EntityType::Vex => EntityMetadata::Vex(Vex::default()), - azalea_registry::EntityType::Villager => EntityMetadata::Villager(Villager::default()), - azalea_registry::EntityType::Vindicator => { - EntityMetadata::Vindicator(Vindicator::default()) - } - azalea_registry::EntityType::WanderingTrader => { - EntityMetadata::WanderingTrader(WanderingTrader::default()) - } - azalea_registry::EntityType::Warden => EntityMetadata::Warden(Warden::default()), - azalea_registry::EntityType::Witch => EntityMetadata::Witch(Witch::default()), - azalea_registry::EntityType::Wither => EntityMetadata::Wither(Wither::default()), - azalea_registry::EntityType::WitherSkeleton => { - EntityMetadata::WitherSkeleton(WitherSkeleton::default()) - } - azalea_registry::EntityType::WitherSkull => { - EntityMetadata::WitherSkull(WitherSkull::default()) - } - azalea_registry::EntityType::Wolf => EntityMetadata::Wolf(Wolf::default()), - azalea_registry::EntityType::Zoglin => EntityMetadata::Zoglin(Zoglin::default()), - azalea_registry::EntityType::Zombie => EntityMetadata::Zombie(Zombie::default()), - azalea_registry::EntityType::ZombieHorse => { - EntityMetadata::ZombieHorse(ZombieHorse::default()) - } - azalea_registry::EntityType::ZombieVillager => { - EntityMetadata::ZombieVillager(ZombieVillager::default()) - } - azalea_registry::EntityType::ZombifiedPiglin => { - EntityMetadata::ZombifiedPiglin(ZombifiedPiglin::default()) - } - } - } -} - -impl EntityMetadata { - pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - match self { - EntityMetadata::Allay(entity) => entity.set_index(index, value), - EntityMetadata::AreaEffectCloud(entity) => entity.set_index(index, value), - EntityMetadata::ArmorStand(entity) => entity.set_index(index, value), - EntityMetadata::Arrow(entity) => entity.set_index(index, value), - EntityMetadata::Axolotl(entity) => entity.set_index(index, value), - EntityMetadata::Bat(entity) => entity.set_index(index, value), - EntityMetadata::Bee(entity) => entity.set_index(index, value), - EntityMetadata::Blaze(entity) => entity.set_index(index, value), - EntityMetadata::Boat(entity) => entity.set_index(index, value), - EntityMetadata::Camel(entity) => entity.set_index(index, value), - EntityMetadata::Cat(entity) => entity.set_index(index, value), - EntityMetadata::CaveSpider(entity) => entity.set_index(index, value), - EntityMetadata::ChestBoat(entity) => entity.set_index(index, value), - EntityMetadata::ChestMinecart(entity) => entity.set_index(index, value), - EntityMetadata::Chicken(entity) => entity.set_index(index, value), - EntityMetadata::Cod(entity) => entity.set_index(index, value), - EntityMetadata::CommandBlockMinecart(entity) => entity.set_index(index, value), - EntityMetadata::Cow(entity) => entity.set_index(index, value), - EntityMetadata::Creeper(entity) => entity.set_index(index, value), - EntityMetadata::Dolphin(entity) => entity.set_index(index, value), - EntityMetadata::Donkey(entity) => entity.set_index(index, value), - EntityMetadata::DragonFireball(entity) => entity.set_index(index, value), - EntityMetadata::Drowned(entity) => entity.set_index(index, value), - EntityMetadata::Egg(entity) => entity.set_index(index, value), - EntityMetadata::ElderGuardian(entity) => entity.set_index(index, value), - EntityMetadata::EndCrystal(entity) => entity.set_index(index, value), - EntityMetadata::EnderDragon(entity) => entity.set_index(index, value), - EntityMetadata::EnderPearl(entity) => entity.set_index(index, value), - EntityMetadata::Enderman(entity) => entity.set_index(index, value), - EntityMetadata::Endermite(entity) => entity.set_index(index, value), - EntityMetadata::Evoker(entity) => entity.set_index(index, value), - EntityMetadata::EvokerFangs(entity) => entity.set_index(index, value), - EntityMetadata::ExperienceBottle(entity) => entity.set_index(index, value), - EntityMetadata::ExperienceOrb(entity) => entity.set_index(index, value), - EntityMetadata::EyeOfEnder(entity) => entity.set_index(index, value), - EntityMetadata::FallingBlock(entity) => entity.set_index(index, value), - EntityMetadata::Fireball(entity) => entity.set_index(index, value), - EntityMetadata::FireworkRocket(entity) => entity.set_index(index, value), - EntityMetadata::FishingBobber(entity) => entity.set_index(index, value), - EntityMetadata::Fox(entity) => entity.set_index(index, value), - EntityMetadata::Frog(entity) => entity.set_index(index, value), - EntityMetadata::FurnaceMinecart(entity) => entity.set_index(index, value), - EntityMetadata::Ghast(entity) => entity.set_index(index, value), - EntityMetadata::Giant(entity) => entity.set_index(index, value), - EntityMetadata::GlowItemFrame(entity) => entity.set_index(index, value), - EntityMetadata::GlowSquid(entity) => entity.set_index(index, value), - EntityMetadata::Goat(entity) => entity.set_index(index, value), - EntityMetadata::Guardian(entity) => entity.set_index(index, value), - EntityMetadata::Hoglin(entity) => entity.set_index(index, value), - EntityMetadata::HopperMinecart(entity) => entity.set_index(index, value), - EntityMetadata::Horse(entity) => entity.set_index(index, value), - EntityMetadata::Husk(entity) => entity.set_index(index, value), - EntityMetadata::Illusioner(entity) => entity.set_index(index, value), - EntityMetadata::IronGolem(entity) => entity.set_index(index, value), - EntityMetadata::Item(entity) => entity.set_index(index, value), - EntityMetadata::ItemFrame(entity) => entity.set_index(index, value), - EntityMetadata::LeashKnot(entity) => entity.set_index(index, value), - EntityMetadata::LightningBolt(entity) => entity.set_index(index, value), - EntityMetadata::Llama(entity) => entity.set_index(index, value), - EntityMetadata::LlamaSpit(entity) => entity.set_index(index, value), - EntityMetadata::MagmaCube(entity) => entity.set_index(index, value), - EntityMetadata::Marker(entity) => entity.set_index(index, value), - EntityMetadata::Minecart(entity) => entity.set_index(index, value), - EntityMetadata::Mooshroom(entity) => entity.set_index(index, value), - EntityMetadata::Mule(entity) => entity.set_index(index, value), - EntityMetadata::Ocelot(entity) => entity.set_index(index, value), - EntityMetadata::Painting(entity) => entity.set_index(index, value), - EntityMetadata::Panda(entity) => entity.set_index(index, value), - EntityMetadata::Parrot(entity) => entity.set_index(index, value), - EntityMetadata::Phantom(entity) => entity.set_index(index, value), - EntityMetadata::Pig(entity) => entity.set_index(index, value), - EntityMetadata::Piglin(entity) => entity.set_index(index, value), - EntityMetadata::PiglinBrute(entity) => entity.set_index(index, value), - EntityMetadata::Pillager(entity) => entity.set_index(index, value), - EntityMetadata::Player(entity) => entity.set_index(index, value), - EntityMetadata::PolarBear(entity) => entity.set_index(index, value), - EntityMetadata::Potion(entity) => entity.set_index(index, value), - EntityMetadata::Pufferfish(entity) => entity.set_index(index, value), - EntityMetadata::Rabbit(entity) => entity.set_index(index, value), - EntityMetadata::Ravager(entity) => entity.set_index(index, value), - EntityMetadata::Salmon(entity) => entity.set_index(index, value), - EntityMetadata::Sheep(entity) => entity.set_index(index, value), - EntityMetadata::Shulker(entity) => entity.set_index(index, value), - EntityMetadata::ShulkerBullet(entity) => entity.set_index(index, value), - EntityMetadata::Silverfish(entity) => entity.set_index(index, value), - EntityMetadata::Skeleton(entity) => entity.set_index(index, value), - EntityMetadata::SkeletonHorse(entity) => entity.set_index(index, value), - EntityMetadata::Slime(entity) => entity.set_index(index, value), - EntityMetadata::SmallFireball(entity) => entity.set_index(index, value), - EntityMetadata::SnowGolem(entity) => entity.set_index(index, value), - EntityMetadata::Snowball(entity) => entity.set_index(index, value), - EntityMetadata::SpawnerMinecart(entity) => entity.set_index(index, value), - EntityMetadata::SpectralArrow(entity) => entity.set_index(index, value), - EntityMetadata::Spider(entity) => entity.set_index(index, value), - EntityMetadata::Squid(entity) => entity.set_index(index, value), - EntityMetadata::Stray(entity) => entity.set_index(index, value), - EntityMetadata::Strider(entity) => entity.set_index(index, value), - EntityMetadata::Tadpole(entity) => entity.set_index(index, value), - EntityMetadata::Tnt(entity) => entity.set_index(index, value), - EntityMetadata::TntMinecart(entity) => entity.set_index(index, value), - EntityMetadata::TraderLlama(entity) => entity.set_index(index, value), - EntityMetadata::Trident(entity) => entity.set_index(index, value), - EntityMetadata::TropicalFish(entity) => entity.set_index(index, value), - EntityMetadata::Turtle(entity) => entity.set_index(index, value), - EntityMetadata::Vex(entity) => entity.set_index(index, value), - EntityMetadata::Villager(entity) => entity.set_index(index, value), - EntityMetadata::Vindicator(entity) => entity.set_index(index, value), - EntityMetadata::WanderingTrader(entity) => entity.set_index(index, value), - EntityMetadata::Warden(entity) => entity.set_index(index, value), - EntityMetadata::Witch(entity) => entity.set_index(index, value), - EntityMetadata::Wither(entity) => entity.set_index(index, value), - EntityMetadata::WitherSkeleton(entity) => entity.set_index(index, value), - EntityMetadata::WitherSkull(entity) => entity.set_index(index, value), - EntityMetadata::Wolf(entity) => entity.set_index(index, value), - EntityMetadata::Zoglin(entity) => entity.set_index(index, value), - EntityMetadata::Zombie(entity) => entity.set_index(index, value), - EntityMetadata::ZombieHorse(entity) => entity.set_index(index, value), - EntityMetadata::ZombieVillager(entity) => entity.set_index(index, value), - EntityMetadata::ZombifiedPiglin(entity) => entity.set_index(index, value), - } - } -} - -impl Deref for EntityMetadata { - type Target = AbstractEntity; - fn deref(&self) -> &Self::Target { - match self { - EntityMetadata::Allay(entity) => entity, - EntityMetadata::AreaEffectCloud(entity) => entity, - EntityMetadata::ArmorStand(entity) => entity, - EntityMetadata::Arrow(entity) => entity, - EntityMetadata::Axolotl(entity) => entity, - EntityMetadata::Bat(entity) => entity, - EntityMetadata::Bee(entity) => entity, - EntityMetadata::Blaze(entity) => entity, - EntityMetadata::Boat(entity) => entity, - EntityMetadata::Camel(entity) => entity, - EntityMetadata::Cat(entity) => entity, - EntityMetadata::CaveSpider(entity) => entity, - EntityMetadata::ChestBoat(entity) => entity, - EntityMetadata::ChestMinecart(entity) => entity, - EntityMetadata::Chicken(entity) => entity, - EntityMetadata::Cod(entity) => entity, - EntityMetadata::CommandBlockMinecart(entity) => entity, - EntityMetadata::Cow(entity) => entity, - EntityMetadata::Creeper(entity) => entity, - EntityMetadata::Dolphin(entity) => entity, - EntityMetadata::Donkey(entity) => entity, - EntityMetadata::DragonFireball(entity) => entity, - EntityMetadata::Drowned(entity) => entity, - EntityMetadata::Egg(entity) => entity, - EntityMetadata::ElderGuardian(entity) => entity, - EntityMetadata::EndCrystal(entity) => entity, - EntityMetadata::EnderDragon(entity) => entity, - EntityMetadata::EnderPearl(entity) => entity, - EntityMetadata::Enderman(entity) => entity, - EntityMetadata::Endermite(entity) => entity, - EntityMetadata::Evoker(entity) => entity, - EntityMetadata::EvokerFangs(entity) => entity, - EntityMetadata::ExperienceBottle(entity) => entity, - EntityMetadata::ExperienceOrb(entity) => entity, - EntityMetadata::EyeOfEnder(entity) => entity, - EntityMetadata::FallingBlock(entity) => entity, - EntityMetadata::Fireball(entity) => entity, - EntityMetadata::FireworkRocket(entity) => entity, - EntityMetadata::FishingBobber(entity) => entity, - EntityMetadata::Fox(entity) => entity, - EntityMetadata::Frog(entity) => entity, - EntityMetadata::FurnaceMinecart(entity) => entity, - EntityMetadata::Ghast(entity) => entity, - EntityMetadata::Giant(entity) => entity, - EntityMetadata::GlowItemFrame(entity) => entity, - EntityMetadata::GlowSquid(entity) => entity, - EntityMetadata::Goat(entity) => entity, - EntityMetadata::Guardian(entity) => entity, - EntityMetadata::Hoglin(entity) => entity, - EntityMetadata::HopperMinecart(entity) => entity, - EntityMetadata::Horse(entity) => entity, - EntityMetadata::Husk(entity) => entity, - EntityMetadata::Illusioner(entity) => entity, - EntityMetadata::IronGolem(entity) => entity, - EntityMetadata::Item(entity) => entity, - EntityMetadata::ItemFrame(entity) => entity, - EntityMetadata::LeashKnot(entity) => entity, - EntityMetadata::LightningBolt(entity) => entity, - EntityMetadata::Llama(entity) => entity, - EntityMetadata::LlamaSpit(entity) => entity, - EntityMetadata::MagmaCube(entity) => entity, - EntityMetadata::Marker(entity) => entity, - EntityMetadata::Minecart(entity) => entity, - EntityMetadata::Mooshroom(entity) => entity, - EntityMetadata::Mule(entity) => entity, - EntityMetadata::Ocelot(entity) => entity, - EntityMetadata::Painting(entity) => entity, - EntityMetadata::Panda(entity) => entity, - EntityMetadata::Parrot(entity) => entity, - EntityMetadata::Phantom(entity) => entity, - EntityMetadata::Pig(entity) => entity, - EntityMetadata::Piglin(entity) => entity, - EntityMetadata::PiglinBrute(entity) => entity, - EntityMetadata::Pillager(entity) => entity, - EntityMetadata::Player(entity) => entity, - EntityMetadata::PolarBear(entity) => entity, - EntityMetadata::Potion(entity) => entity, - EntityMetadata::Pufferfish(entity) => entity, - EntityMetadata::Rabbit(entity) => entity, - EntityMetadata::Ravager(entity) => entity, - EntityMetadata::Salmon(entity) => entity, - EntityMetadata::Sheep(entity) => entity, - EntityMetadata::Shulker(entity) => entity, - EntityMetadata::ShulkerBullet(entity) => entity, - EntityMetadata::Silverfish(entity) => entity, - EntityMetadata::Skeleton(entity) => entity, - EntityMetadata::SkeletonHorse(entity) => entity, - EntityMetadata::Slime(entity) => entity, - EntityMetadata::SmallFireball(entity) => entity, - EntityMetadata::SnowGolem(entity) => entity, - EntityMetadata::Snowball(entity) => entity, - EntityMetadata::SpawnerMinecart(entity) => entity, - EntityMetadata::SpectralArrow(entity) => entity, - EntityMetadata::Spider(entity) => entity, - EntityMetadata::Squid(entity) => entity, - EntityMetadata::Stray(entity) => entity, - EntityMetadata::Strider(entity) => entity, - EntityMetadata::Tadpole(entity) => entity, - EntityMetadata::Tnt(entity) => entity, - EntityMetadata::TntMinecart(entity) => entity, - EntityMetadata::TraderLlama(entity) => entity, - EntityMetadata::Trident(entity) => entity, - EntityMetadata::TropicalFish(entity) => entity, - EntityMetadata::Turtle(entity) => entity, - EntityMetadata::Vex(entity) => entity, - EntityMetadata::Villager(entity) => entity, - EntityMetadata::Vindicator(entity) => entity, - EntityMetadata::WanderingTrader(entity) => entity, - EntityMetadata::Warden(entity) => entity, - EntityMetadata::Witch(entity) => entity, - EntityMetadata::Wither(entity) => entity, - EntityMetadata::WitherSkeleton(entity) => entity, - EntityMetadata::WitherSkull(entity) => entity, - EntityMetadata::Wolf(entity) => entity, - EntityMetadata::Zoglin(entity) => entity, - EntityMetadata::Zombie(entity) => entity, - EntityMetadata::ZombieHorse(entity) => entity, - EntityMetadata::ZombieVillager(entity) => entity, - EntityMetadata::ZombifiedPiglin(entity) => entity, - } - } -} -impl DerefMut for EntityMetadata { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - EntityMetadata::Allay(entity) => entity, - EntityMetadata::AreaEffectCloud(entity) => entity, - EntityMetadata::ArmorStand(entity) => entity, - EntityMetadata::Arrow(entity) => entity, - EntityMetadata::Axolotl(entity) => entity, - EntityMetadata::Bat(entity) => entity, - EntityMetadata::Bee(entity) => entity, - EntityMetadata::Blaze(entity) => entity, - EntityMetadata::Boat(entity) => entity, - EntityMetadata::Camel(entity) => entity, - EntityMetadata::Cat(entity) => entity, - EntityMetadata::CaveSpider(entity) => entity, - EntityMetadata::ChestBoat(entity) => entity, - EntityMetadata::ChestMinecart(entity) => entity, - EntityMetadata::Chicken(entity) => entity, - EntityMetadata::Cod(entity) => entity, - EntityMetadata::CommandBlockMinecart(entity) => entity, - EntityMetadata::Cow(entity) => entity, - EntityMetadata::Creeper(entity) => entity, - EntityMetadata::Dolphin(entity) => entity, - EntityMetadata::Donkey(entity) => entity, - EntityMetadata::DragonFireball(entity) => entity, - EntityMetadata::Drowned(entity) => entity, - EntityMetadata::Egg(entity) => entity, - EntityMetadata::ElderGuardian(entity) => entity, - EntityMetadata::EndCrystal(entity) => entity, - EntityMetadata::EnderDragon(entity) => entity, - EntityMetadata::EnderPearl(entity) => entity, - EntityMetadata::Enderman(entity) => entity, - EntityMetadata::Endermite(entity) => entity, - EntityMetadata::Evoker(entity) => entity, - EntityMetadata::EvokerFangs(entity) => entity, - EntityMetadata::ExperienceBottle(entity) => entity, - EntityMetadata::ExperienceOrb(entity) => entity, - EntityMetadata::EyeOfEnder(entity) => entity, - EntityMetadata::FallingBlock(entity) => entity, - EntityMetadata::Fireball(entity) => entity, - EntityMetadata::FireworkRocket(entity) => entity, - EntityMetadata::FishingBobber(entity) => entity, - EntityMetadata::Fox(entity) => entity, - EntityMetadata::Frog(entity) => entity, - EntityMetadata::FurnaceMinecart(entity) => entity, - EntityMetadata::Ghast(entity) => entity, - EntityMetadata::Giant(entity) => entity, - EntityMetadata::GlowItemFrame(entity) => entity, - EntityMetadata::GlowSquid(entity) => entity, - EntityMetadata::Goat(entity) => entity, - EntityMetadata::Guardian(entity) => entity, - EntityMetadata::Hoglin(entity) => entity, - EntityMetadata::HopperMinecart(entity) => entity, - EntityMetadata::Horse(entity) => entity, - EntityMetadata::Husk(entity) => entity, - EntityMetadata::Illusioner(entity) => entity, - EntityMetadata::IronGolem(entity) => entity, - EntityMetadata::Item(entity) => entity, - EntityMetadata::ItemFrame(entity) => entity, - EntityMetadata::LeashKnot(entity) => entity, - EntityMetadata::LightningBolt(entity) => entity, - EntityMetadata::Llama(entity) => entity, - EntityMetadata::LlamaSpit(entity) => entity, - EntityMetadata::MagmaCube(entity) => entity, - EntityMetadata::Marker(entity) => entity, - EntityMetadata::Minecart(entity) => entity, - EntityMetadata::Mooshroom(entity) => entity, - EntityMetadata::Mule(entity) => entity, - EntityMetadata::Ocelot(entity) => entity, - EntityMetadata::Painting(entity) => entity, - EntityMetadata::Panda(entity) => entity, - EntityMetadata::Parrot(entity) => entity, - EntityMetadata::Phantom(entity) => entity, - EntityMetadata::Pig(entity) => entity, - EntityMetadata::Piglin(entity) => entity, - EntityMetadata::PiglinBrute(entity) => entity, - EntityMetadata::Pillager(entity) => entity, - EntityMetadata::Player(entity) => entity, - EntityMetadata::PolarBear(entity) => entity, - EntityMetadata::Potion(entity) => entity, - EntityMetadata::Pufferfish(entity) => entity, - EntityMetadata::Rabbit(entity) => entity, - EntityMetadata::Ravager(entity) => entity, - EntityMetadata::Salmon(entity) => entity, - EntityMetadata::Sheep(entity) => entity, - EntityMetadata::Shulker(entity) => entity, - EntityMetadata::ShulkerBullet(entity) => entity, - EntityMetadata::Silverfish(entity) => entity, - EntityMetadata::Skeleton(entity) => entity, - EntityMetadata::SkeletonHorse(entity) => entity, - EntityMetadata::Slime(entity) => entity, - EntityMetadata::SmallFireball(entity) => entity, - EntityMetadata::SnowGolem(entity) => entity, - EntityMetadata::Snowball(entity) => entity, - EntityMetadata::SpawnerMinecart(entity) => entity, - EntityMetadata::SpectralArrow(entity) => entity, - EntityMetadata::Spider(entity) => entity, - EntityMetadata::Squid(entity) => entity, - EntityMetadata::Stray(entity) => entity, - EntityMetadata::Strider(entity) => entity, - EntityMetadata::Tadpole(entity) => entity, - EntityMetadata::Tnt(entity) => entity, - EntityMetadata::TntMinecart(entity) => entity, - EntityMetadata::TraderLlama(entity) => entity, - EntityMetadata::Trident(entity) => entity, - EntityMetadata::TropicalFish(entity) => entity, - EntityMetadata::Turtle(entity) => entity, - EntityMetadata::Vex(entity) => entity, - EntityMetadata::Villager(entity) => entity, - EntityMetadata::Vindicator(entity) => entity, - EntityMetadata::WanderingTrader(entity) => entity, - EntityMetadata::Warden(entity) => entity, - EntityMetadata::Witch(entity) => entity, - EntityMetadata::Wither(entity) => entity, - EntityMetadata::WitherSkeleton(entity) => entity, - EntityMetadata::WitherSkull(entity) => entity, - EntityMetadata::Wolf(entity) => entity, - EntityMetadata::Zoglin(entity) => entity, - EntityMetadata::Zombie(entity) => entity, - EntityMetadata::ZombieHorse(entity) => entity, - EntityMetadata::ZombieVillager(entity) => entity, - EntityMetadata::ZombifiedPiglin(entity) => entity, +pub fn apply_default_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + kind: azalea_registry::EntityKind, +) { + match kind { + azalea_registry::EntityKind::Allay => { + entity.insert(AllayMetadataBundle::default()); + } + azalea_registry::EntityKind::AreaEffectCloud => { + entity.insert(AreaEffectCloudMetadataBundle::default()); + } + azalea_registry::EntityKind::ArmorStand => { + entity.insert(ArmorStandMetadataBundle::default()); + } + azalea_registry::EntityKind::Arrow => { + entity.insert(ArrowMetadataBundle::default()); + } + azalea_registry::EntityKind::Axolotl => { + entity.insert(AxolotlMetadataBundle::default()); + } + azalea_registry::EntityKind::Bat => { + entity.insert(BatMetadataBundle::default()); + } + azalea_registry::EntityKind::Bee => { + entity.insert(BeeMetadataBundle::default()); + } + azalea_registry::EntityKind::Blaze => { + entity.insert(BlazeMetadataBundle::default()); + } + azalea_registry::EntityKind::Boat => { + entity.insert(BoatMetadataBundle::default()); + } + azalea_registry::EntityKind::Camel => { + entity.insert(CamelMetadataBundle::default()); + } + azalea_registry::EntityKind::Cat => { + entity.insert(CatMetadataBundle::default()); + } + azalea_registry::EntityKind::CaveSpider => { + entity.insert(CaveSpiderMetadataBundle::default()); + } + azalea_registry::EntityKind::ChestBoat => { + entity.insert(ChestBoatMetadataBundle::default()); + } + azalea_registry::EntityKind::ChestMinecart => { + entity.insert(ChestMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::Chicken => { + entity.insert(ChickenMetadataBundle::default()); + } + azalea_registry::EntityKind::Cod => { + entity.insert(CodMetadataBundle::default()); + } + azalea_registry::EntityKind::CommandBlockMinecart => { + entity.insert(CommandBlockMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::Cow => { + entity.insert(CowMetadataBundle::default()); + } + azalea_registry::EntityKind::Creeper => { + entity.insert(CreeperMetadataBundle::default()); + } + azalea_registry::EntityKind::Dolphin => { + entity.insert(DolphinMetadataBundle::default()); + } + azalea_registry::EntityKind::Donkey => { + entity.insert(DonkeyMetadataBundle::default()); + } + azalea_registry::EntityKind::DragonFireball => { + entity.insert(DragonFireballMetadataBundle::default()); + } + azalea_registry::EntityKind::Drowned => { + entity.insert(DrownedMetadataBundle::default()); + } + azalea_registry::EntityKind::Egg => { + entity.insert(EggMetadataBundle::default()); + } + azalea_registry::EntityKind::ElderGuardian => { + entity.insert(ElderGuardianMetadataBundle::default()); + } + azalea_registry::EntityKind::EndCrystal => { + entity.insert(EndCrystalMetadataBundle::default()); + } + azalea_registry::EntityKind::EnderDragon => { + entity.insert(EnderDragonMetadataBundle::default()); + } + azalea_registry::EntityKind::EnderPearl => { + entity.insert(EnderPearlMetadataBundle::default()); + } + azalea_registry::EntityKind::Enderman => { + entity.insert(EndermanMetadataBundle::default()); + } + azalea_registry::EntityKind::Endermite => { + entity.insert(EndermiteMetadataBundle::default()); + } + azalea_registry::EntityKind::Evoker => { + entity.insert(EvokerMetadataBundle::default()); + } + azalea_registry::EntityKind::EvokerFangs => { + entity.insert(EvokerFangsMetadataBundle::default()); + } + azalea_registry::EntityKind::ExperienceBottle => { + entity.insert(ExperienceBottleMetadataBundle::default()); + } + azalea_registry::EntityKind::ExperienceOrb => { + entity.insert(ExperienceOrbMetadataBundle::default()); + } + azalea_registry::EntityKind::EyeOfEnder => { + entity.insert(EyeOfEnderMetadataBundle::default()); + } + azalea_registry::EntityKind::FallingBlock => { + entity.insert(FallingBlockMetadataBundle::default()); + } + azalea_registry::EntityKind::Fireball => { + entity.insert(FireballMetadataBundle::default()); + } + azalea_registry::EntityKind::FireworkRocket => { + entity.insert(FireworkRocketMetadataBundle::default()); + } + azalea_registry::EntityKind::FishingBobber => { + entity.insert(FishingBobberMetadataBundle::default()); + } + azalea_registry::EntityKind::Fox => { + entity.insert(FoxMetadataBundle::default()); + } + azalea_registry::EntityKind::Frog => { + entity.insert(FrogMetadataBundle::default()); + } + azalea_registry::EntityKind::FurnaceMinecart => { + entity.insert(FurnaceMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::Ghast => { + entity.insert(GhastMetadataBundle::default()); + } + azalea_registry::EntityKind::Giant => { + entity.insert(GiantMetadataBundle::default()); + } + azalea_registry::EntityKind::GlowItemFrame => { + entity.insert(GlowItemFrameMetadataBundle::default()); + } + azalea_registry::EntityKind::GlowSquid => { + entity.insert(GlowSquidMetadataBundle::default()); + } + azalea_registry::EntityKind::Goat => { + entity.insert(GoatMetadataBundle::default()); + } + azalea_registry::EntityKind::Guardian => { + entity.insert(GuardianMetadataBundle::default()); + } + azalea_registry::EntityKind::Hoglin => { + entity.insert(HoglinMetadataBundle::default()); + } + azalea_registry::EntityKind::HopperMinecart => { + entity.insert(HopperMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::Horse => { + entity.insert(HorseMetadataBundle::default()); + } + azalea_registry::EntityKind::Husk => { + entity.insert(HuskMetadataBundle::default()); + } + azalea_registry::EntityKind::Illusioner => { + entity.insert(IllusionerMetadataBundle::default()); + } + azalea_registry::EntityKind::IronGolem => { + entity.insert(IronGolemMetadataBundle::default()); + } + azalea_registry::EntityKind::Item => { + entity.insert(ItemMetadataBundle::default()); + } + azalea_registry::EntityKind::ItemFrame => { + entity.insert(ItemFrameMetadataBundle::default()); + } + azalea_registry::EntityKind::LeashKnot => { + entity.insert(LeashKnotMetadataBundle::default()); + } + azalea_registry::EntityKind::LightningBolt => { + entity.insert(LightningBoltMetadataBundle::default()); + } + azalea_registry::EntityKind::Llama => { + entity.insert(LlamaMetadataBundle::default()); + } + azalea_registry::EntityKind::LlamaSpit => { + entity.insert(LlamaSpitMetadataBundle::default()); + } + azalea_registry::EntityKind::MagmaCube => { + entity.insert(MagmaCubeMetadataBundle::default()); + } + azalea_registry::EntityKind::Marker => { + entity.insert(MarkerMetadataBundle::default()); + } + azalea_registry::EntityKind::Minecart => { + entity.insert(MinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::Mooshroom => { + entity.insert(MooshroomMetadataBundle::default()); + } + azalea_registry::EntityKind::Mule => { + entity.insert(MuleMetadataBundle::default()); + } + azalea_registry::EntityKind::Ocelot => { + entity.insert(OcelotMetadataBundle::default()); + } + azalea_registry::EntityKind::Painting => { + entity.insert(PaintingMetadataBundle::default()); + } + azalea_registry::EntityKind::Panda => { + entity.insert(PandaMetadataBundle::default()); + } + azalea_registry::EntityKind::Parrot => { + entity.insert(ParrotMetadataBundle::default()); + } + azalea_registry::EntityKind::Phantom => { + entity.insert(PhantomMetadataBundle::default()); + } + azalea_registry::EntityKind::Pig => { + entity.insert(PigMetadataBundle::default()); + } + azalea_registry::EntityKind::Piglin => { + entity.insert(PiglinMetadataBundle::default()); + } + azalea_registry::EntityKind::PiglinBrute => { + entity.insert(PiglinBruteMetadataBundle::default()); + } + azalea_registry::EntityKind::Pillager => { + entity.insert(PillagerMetadataBundle::default()); + } + azalea_registry::EntityKind::Player => { + entity.insert(PlayerMetadataBundle::default()); + } + azalea_registry::EntityKind::PolarBear => { + entity.insert(PolarBearMetadataBundle::default()); + } + azalea_registry::EntityKind::Potion => { + entity.insert(PotionMetadataBundle::default()); + } + azalea_registry::EntityKind::Pufferfish => { + entity.insert(PufferfishMetadataBundle::default()); + } + azalea_registry::EntityKind::Rabbit => { + entity.insert(RabbitMetadataBundle::default()); + } + azalea_registry::EntityKind::Ravager => { + entity.insert(RavagerMetadataBundle::default()); + } + azalea_registry::EntityKind::Salmon => { + entity.insert(SalmonMetadataBundle::default()); + } + azalea_registry::EntityKind::Sheep => { + entity.insert(SheepMetadataBundle::default()); + } + azalea_registry::EntityKind::Shulker => { + entity.insert(ShulkerMetadataBundle::default()); + } + azalea_registry::EntityKind::ShulkerBullet => { + entity.insert(ShulkerBulletMetadataBundle::default()); + } + azalea_registry::EntityKind::Silverfish => { + entity.insert(SilverfishMetadataBundle::default()); + } + azalea_registry::EntityKind::Skeleton => { + entity.insert(SkeletonMetadataBundle::default()); + } + azalea_registry::EntityKind::SkeletonHorse => { + entity.insert(SkeletonHorseMetadataBundle::default()); + } + azalea_registry::EntityKind::Slime => { + entity.insert(SlimeMetadataBundle::default()); + } + azalea_registry::EntityKind::SmallFireball => { + entity.insert(SmallFireballMetadataBundle::default()); + } + azalea_registry::EntityKind::SnowGolem => { + entity.insert(SnowGolemMetadataBundle::default()); + } + azalea_registry::EntityKind::Snowball => { + entity.insert(SnowballMetadataBundle::default()); + } + azalea_registry::EntityKind::SpawnerMinecart => { + entity.insert(SpawnerMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::SpectralArrow => { + entity.insert(SpectralArrowMetadataBundle::default()); + } + azalea_registry::EntityKind::Spider => { + entity.insert(SpiderMetadataBundle::default()); + } + azalea_registry::EntityKind::Squid => { + entity.insert(SquidMetadataBundle::default()); + } + azalea_registry::EntityKind::Stray => { + entity.insert(StrayMetadataBundle::default()); + } + azalea_registry::EntityKind::Strider => { + entity.insert(StriderMetadataBundle::default()); + } + azalea_registry::EntityKind::Tadpole => { + entity.insert(TadpoleMetadataBundle::default()); + } + azalea_registry::EntityKind::Tnt => { + entity.insert(TntMetadataBundle::default()); + } + azalea_registry::EntityKind::TntMinecart => { + entity.insert(TntMinecartMetadataBundle::default()); + } + azalea_registry::EntityKind::TraderLlama => { + entity.insert(TraderLlamaMetadataBundle::default()); + } + azalea_registry::EntityKind::Trident => { + entity.insert(TridentMetadataBundle::default()); + } + azalea_registry::EntityKind::TropicalFish => { + entity.insert(TropicalFishMetadataBundle::default()); + } + azalea_registry::EntityKind::Turtle => { + entity.insert(TurtleMetadataBundle::default()); + } + azalea_registry::EntityKind::Vex => { + entity.insert(VexMetadataBundle::default()); + } + azalea_registry::EntityKind::Villager => { + entity.insert(VillagerMetadataBundle::default()); + } + azalea_registry::EntityKind::Vindicator => { + entity.insert(VindicatorMetadataBundle::default()); + } + azalea_registry::EntityKind::WanderingTrader => { + entity.insert(WanderingTraderMetadataBundle::default()); + } + azalea_registry::EntityKind::Warden => { + entity.insert(WardenMetadataBundle::default()); + } + azalea_registry::EntityKind::Witch => { + entity.insert(WitchMetadataBundle::default()); + } + azalea_registry::EntityKind::Wither => { + entity.insert(WitherMetadataBundle::default()); + } + azalea_registry::EntityKind::WitherSkeleton => { + entity.insert(WitherSkeletonMetadataBundle::default()); + } + azalea_registry::EntityKind::WitherSkull => { + entity.insert(WitherSkullMetadataBundle::default()); + } + azalea_registry::EntityKind::Wolf => { + entity.insert(WolfMetadataBundle::default()); + } + azalea_registry::EntityKind::Zoglin => { + entity.insert(ZoglinMetadataBundle::default()); + } + azalea_registry::EntityKind::Zombie => { + entity.insert(ZombieMetadataBundle::default()); + } + azalea_registry::EntityKind::ZombieHorse => { + entity.insert(ZombieHorseMetadataBundle::default()); + } + azalea_registry::EntityKind::ZombieVillager => { + entity.insert(ZombieVillagerMetadataBundle::default()); + } + azalea_registry::EntityKind::ZombifiedPiglin => { + entity.insert(ZombifiedPiglinMetadataBundle::default()); } } } diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs index 94362f2f..bf758a12 100644 --- a/azalea-world/src/entity/mod.rs +++ b/azalea-world/src/entity/mod.rs @@ -1,192 +1,190 @@ +#![allow(clippy::derived_hash_with_manual_eq)] + pub mod attributes; mod data; mod dimensions; pub mod metadata; -use self::attributes::{AttributeInstance, AttributeModifiers}; -pub use self::metadata::EntityMetadata; -use crate::WeakWorld; +use crate::ChunkStorage; + +use self::{attributes::AttributeInstance, metadata::Health}; +pub use attributes::Attributes; use azalea_block::BlockState; -use azalea_core::{BlockPos, Vec3, AABB}; +use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB}; +use azalea_ecs::{ + bundle::Bundle, + component::Component, + entity::Entity, + query::Changed, + system::{Commands, Query}, +}; pub use data::*; -pub use dimensions::*; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::ptr::NonNull; +use derive_more::{Deref, DerefMut}; +pub use dimensions::{update_bounding_box, EntityDimensions}; +use std::fmt::Debug; use uuid::Uuid; -/// A reference to an entity in a world. -#[derive(Debug)] -pub struct Entity<'d, D = &'d WeakWorld> { - /// The world this entity is in. - pub world: D, - /// The incrementing numerical id of the entity. - pub id: u32, - pub data: NonNull<EntityData>, - _marker: PhantomData<&'d ()>, +/// An entity ID used by Minecraft. These are not guaranteed to be unique in +/// shared worlds, that's what [`Entity`] is for. +#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)] +pub struct MinecraftEntityId(pub u32); +impl std::hash::Hash for MinecraftEntityId { + fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) { + hasher.write_u32(self.0); + } +} +impl nohash_hasher::IsEnabled for MinecraftEntityId {} +pub fn set_rotation(physics: &mut Physics, y_rot: f32, x_rot: f32) { + physics.y_rot = y_rot % 360.0; + physics.x_rot = x_rot.clamp(-90.0, 90.0) % 360.0; + // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but + // idk what they're used for so } -impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> { - pub fn new(world: D, id: u32, data: NonNull<EntityData>) -> Self { - // TODO: have this be based on the entity type - Self { - world, - id, - data, - _marker: PhantomData, - } - } +pub fn move_relative(physics: &mut Physics, speed: f32, acceleration: &Vec3) { + let input_vector = input_vector(physics, speed, acceleration); + physics.delta += input_vector; } -impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> { - /// Sets the position of the entity. This doesn't update the cache in - /// azalea-world, and should only be used within azalea-world! - /// - /// # Safety - /// Cached position in the world must be updated. - pub unsafe fn move_unchecked(&mut self, new_pos: Vec3) { - self.pos = new_pos; - let bounding_box = self.make_bounding_box(); - self.bounding_box = bounding_box; +pub fn input_vector(physics: &mut Physics, speed: f32, acceleration: &Vec3) -> Vec3 { + let distance = acceleration.length_squared(); + if distance < 1.0E-7 { + return Vec3::default(); } - - pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { - self.y_rot = y_rot % 360.0; - self.x_rot = x_rot.clamp(-90.0, 90.0) % 360.0; - // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but - // idk what they're used for so + let acceleration = if distance > 1.0 { + acceleration.normalize() + } else { + *acceleration } - - pub fn move_relative(&mut self, speed: f32, acceleration: &Vec3) { - let input_vector = self.input_vector(speed, acceleration); - self.delta += input_vector; + .scale(speed as f64); + let y_rot = f32::sin(physics.y_rot * 0.017453292f32); + let x_rot = f32::cos(physics.y_rot * 0.017453292f32); + Vec3 { + x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64), + y: acceleration.y, + z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64), } +} - pub fn input_vector(&self, speed: f32, acceleration: &Vec3) -> Vec3 { - let distance = acceleration.length_squared(); - if distance < 1.0E-7 { - return Vec3::default(); - } - let acceleration = if distance > 1.0 { - acceleration.normalize() - } else { - *acceleration - } - .scale(speed as f64); - let y_rot = f32::sin(self.y_rot * 0.017453292f32); - let x_rot = f32::cos(self.y_rot * 0.017453292f32); - Vec3 { - x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64), - y: acceleration.y, - z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64), - } - } +/// Get the position of the block below the entity, but a little lower. +pub fn on_pos_legacy(chunk_storage: &ChunkStorage, position: &Position) -> BlockPos { + on_pos(0.2, chunk_storage, position) +} - /// Apply the given metadata items to the entity. Everything that isn't - /// included in items will be left unchanged. If an error occured, None - /// will be returned. - /// - /// TODO: this should be changed to have a proper error. - pub fn apply_metadata(&mut self, items: &Vec<EntityDataItem>) -> Option<()> { - for item in items { - self.metadata.set_index(item.index, item.value.clone())?; +// int x = Mth.floor(this.position.x); +// int y = Mth.floor(this.position.y - (double)var1); +// int z = Mth.floor(this.position.z); +// BlockPos var5 = new BlockPos(x, y, z); +// if (this.level.getBlockState(var5).isAir()) { +// BlockPos var6 = var5.below(); +// BlockState var7 = this.level.getBlockState(var6); +// if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || +// var7.getBlock() instanceof FenceGateBlock) { return var6; +// } +// } +// return var5; +pub fn on_pos(offset: f32, chunk_storage: &ChunkStorage, pos: &Position) -> BlockPos { + let x = pos.x.floor() as i32; + let y = (pos.y - offset as f64).floor() as i32; + let z = pos.z.floor() as i32; + let pos = BlockPos { x, y, z }; + + // TODO: check if block below is a fence, wall, or fence gate + let block_pos = pos.down(1); + let block_state = chunk_storage.get_block_state(&block_pos); + if block_state == Some(BlockState::Air) { + let block_pos_below = block_pos.down(1); + let block_state_below = chunk_storage.get_block_state(&block_pos_below); + if let Some(_block_state_below) = block_state_below { + // if block_state_below.is_fence() + // || block_state_below.is_wall() + // || block_state_below.is_fence_gate() + // { + // return block_pos_below; + // } } - Some(()) } + + pos } -impl<'d, D: Deref<Target = WeakWorld>> Entity<'d, D> { - #[inline] - pub fn pos(&self) -> &Vec3 { - &self.pos +/// The Minecraft UUID of the entity. For players, this is their actual player +/// UUID, and for other entities it's just random. +#[derive(Component, Deref, DerefMut, Clone, Copy)] +pub struct EntityUuid(Uuid); +impl Debug for EntityUuid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self.0).fmt(f) } +} - pub fn make_bounding_box(&self) -> AABB { - self.dimensions.make_bounding_box(self.pos()) +/// The position of the entity right now. +/// +/// You are free to change this; there's systems that update the indexes +/// automatically. +#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] +pub struct Position(Vec3); +impl From<Position> for ChunkPos { + fn from(value: Position) -> Self { + ChunkPos::from(&value.0) } - - /// Get the position of the block below the entity, but a little lower. - pub fn on_pos_legacy(&self) -> BlockPos { - self.on_pos(0.2) +} +impl From<Position> for BlockPos { + fn from(value: Position) -> Self { + BlockPos::from(&value.0) } - - // int x = Mth.floor(this.position.x); - // int y = Mth.floor(this.position.y - (double)var1); - // int z = Mth.floor(this.position.z); - // BlockPos var5 = new BlockPos(x, y, z); - // if (this.level.getBlockState(var5).isAir()) { - // BlockPos var6 = var5.below(); - // BlockState var7 = this.level.getBlockState(var6); - // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || - // var7.getBlock() instanceof FenceGateBlock) { return var6; - // } - // } - // return var5; - pub fn on_pos(&self, offset: f32) -> BlockPos { - let x = self.pos().x.floor() as i32; - let y = (self.pos().y - offset as f64).floor() as i32; - let z = self.pos().z.floor() as i32; - let pos = BlockPos { x, y, z }; - - // TODO: check if block below is a fence, wall, or fence gate - let block_pos = pos.down(1); - let block_state = self.world.get_block_state(&block_pos); - if block_state == Some(BlockState::Air) { - let block_pos_below = block_pos.down(1); - let block_state_below = self.world.get_block_state(&block_pos_below); - if let Some(_block_state_below) = block_state_below { - // if block_state_below.is_fence() - // || block_state_below.is_wall() - // || block_state_below.is_fence_gate() - // { - // return block_pos_below; - // } - } - } - - pos +} +impl From<&Position> for ChunkPos { + fn from(value: &Position) -> Self { + ChunkPos::from(value.0) } } - -// impl< -// 'd, -// D: Deref<Target = WeakWorld> + Deref<Target = WeakWorld>, -// D2: Deref<Target = WeakWorld>, -// > From<Entity<'d, D>> for Entity<'d, D2> -// { -// fn from(entity: Entity<'d, D>) -> Entity<'d, D> { -// Entity { -// world: entity.world, -// id: entity.id, -// data: entity.data, -// _marker: PhantomData, -// } -// } -// } - -impl<D: Deref<Target = WeakWorld>> DerefMut for Entity<'_, D> { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.data.as_mut() } +impl From<&Position> for BlockPos { + fn from(value: &Position) -> Self { + BlockPos::from(value.0) } } -impl<D: Deref<Target = WeakWorld>> Deref for Entity<'_, D> { - type Target = EntityData; - - fn deref(&self) -> &Self::Target { - unsafe { self.data.as_ref() } +/// The last position of the entity that was sent to the network. +#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] +pub struct LastSentPosition(Vec3); +impl From<LastSentPosition> for ChunkPos { + fn from(value: LastSentPosition) -> Self { + ChunkPos::from(&value.0) + } +} +impl From<LastSentPosition> for BlockPos { + fn from(value: LastSentPosition) -> Self { + BlockPos::from(&value.0) + } +} +impl From<&LastSentPosition> for ChunkPos { + fn from(value: &LastSentPosition) -> Self { + ChunkPos::from(value.0) + } +} +impl From<&LastSentPosition> for BlockPos { + fn from(value: &LastSentPosition) -> Self { + BlockPos::from(value.0) } } -#[derive(Debug)] -pub struct EntityData { - pub uuid: Uuid, - /// The position of the entity right now. - /// This can be changde with unsafe_move, but the correct way is with - /// world.move_entity - pos: Vec3, - /// The position of the entity last tick. - pub last_pos: Vec3, +/// The name of the world the entity is in. If two entities share the same world +/// name, we assume they're in the same world. +#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)] +pub struct WorldName(ResourceLocation); + +/// A component for entities that can jump. +/// +/// 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, Deref, DerefMut)] +pub struct Jumping(bool); + +/// The physics data relating to the entity, such as position, velocity, and +/// bounding box. +#[derive(Debug, Component)] +pub struct Physics { pub delta: Vec3, /// X acceleration. @@ -211,97 +209,129 @@ pub struct EntityData { /// unlike dimensions. pub bounding_box: AABB, - /// Whether the entity will try to jump every tick - /// (equivalent to the space key being held down in vanilla). - pub jumping: bool, - pub has_impulse: bool, +} - /// Stores some extra data about the entity, including the entity type. - pub metadata: EntityMetadata, +/// Marker component for entities that are dead. +/// +/// "Dead" means that the entity has 0 health. +#[derive(Component, Copy, Clone, Default)] +pub struct Dead; + +/// System that adds the [`Dead`] marker component if an entity's health is set +/// to 0 (or less than 0). This will be present if an entity is doing the death +/// animation. +/// +/// Entities that are dead can not be revived. +/// TODO: fact check this in-game by setting an entity's health to 0 and then +/// not 0 +pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed<Health>>) { + for (entity, health) in query.iter() { + if **health <= 0.0 { + commands.entity(entity).insert(Dead); + } + } +} - /// The attributes and modifiers that the entity has (for example, speed). - pub attributes: AttributeModifiers, +/// A component NewType for [`azalea_registry::EntityKind`]. +/// +/// Most of the time, you should be using `azalea_registry::EntityKind` +/// instead. +#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)] +pub struct EntityKind(azalea_registry::EntityKind); + +/// A bundle of components that every entity has. This doesn't contain metadata, +/// that has to be added separately. +#[derive(Bundle)] +pub struct EntityBundle { + pub kind: EntityKind, + pub uuid: EntityUuid, + pub world_name: WorldName, + pub position: Position, + pub last_sent_position: LastSentPosition, + pub physics: Physics, + pub attributes: Attributes, + pub jumping: Jumping, } -impl EntityData { - pub fn new(uuid: Uuid, pos: Vec3, metadata: EntityMetadata) -> Self { +impl EntityBundle { + pub fn new( + uuid: Uuid, + pos: Vec3, + kind: azalea_registry::EntityKind, + world_name: ResourceLocation, + ) -> Self { + // TODO: get correct entity dimensions by having them codegened somewhere let dimensions = EntityDimensions { width: 0.6, height: 1.8, }; Self { - uuid, - pos, - last_pos: pos, - delta: Vec3::default(), - - xxa: 0., - yya: 0., - zza: 0., + kind: EntityKind(kind), + uuid: EntityUuid(uuid), + world_name: WorldName(world_name), + position: Position(pos), + last_sent_position: LastSentPosition(pos), + physics: Physics { + delta: Vec3::default(), - x_rot: 0., - y_rot: 0., + xxa: 0., + yya: 0., + zza: 0., - y_rot_last: 0., - x_rot_last: 0., + x_rot: 0., + y_rot: 0., - on_ground: false, - last_on_ground: false, + y_rot_last: 0., + x_rot_last: 0., - // TODO: have this be based on the entity type - bounding_box: dimensions.make_bounding_box(&pos), - dimensions, + on_ground: false, + last_on_ground: false, - has_impulse: false, + // TODO: have this be based on the entity type + bounding_box: dimensions.make_bounding_box(&pos), + dimensions, - jumping: false, - - metadata, + has_impulse: false, + }, - attributes: AttributeModifiers { - // TODO: do the correct defaults for everything, some entities have different - // defaults + attributes: Attributes { + // TODO: do the correct defaults for everything, some + // entities have different defaults speed: AttributeInstance::new(0.1), }, - } - } - /// Get the position of the entity in the world. - #[inline] - pub fn pos(&self) -> &Vec3 { - &self.pos - } - - /// Convert this &self into a (mutable) pointer. - /// - /// # Safety - /// The entity MUST exist for at least as long as this pointer exists. - pub unsafe fn as_ptr(&self) -> NonNull<EntityData> { - // this is cursed - NonNull::new_unchecked(self as *const EntityData as *mut EntityData) + jumping: Jumping(false), + } } } -#[cfg(test)] -mod tests { - use super::*; - use crate::PartialWorld; - - #[test] - fn from_mut_entity_to_ref_entity() { - let mut world = PartialWorld::default(); - let uuid = Uuid::from_u128(100); - world.add_entity( - 0, - EntityData::new( - uuid, - Vec3::default(), - EntityMetadata::Player(metadata::Player::default()), - ), - ); - let entity: Entity = world.entity_mut(0).unwrap(); - assert_eq!(entity.uuid, uuid); - } +/// A bundle of the components that are always present for a player. +#[derive(Bundle)] +pub struct PlayerBundle { + pub entity: EntityBundle, + pub metadata: metadata::PlayerMetadataBundle, } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::PartialWorld; + +// #[test] +// fn from_mut_entity_to_ref_entity() { +// let mut world = PartialWorld::default(); +// let uuid = Uuid::from_u128(100); +// world.add_entity( +// 0, +// EntityData::new( +// uuid, +// Vec3::default(), +// EntityMetadata::Player(metadata::Player::default()), +// ), +// ); +// let entity: Entity = world.entity_mut(0).unwrap(); +// assert_eq!(entity.uuid, uuid); +// } +// } diff --git a/azalea-world/src/entity_info.rs b/azalea-world/src/entity_info.rs new file mode 100644 index 00000000..4109e9ce --- /dev/null +++ b/azalea-world/src/entity_info.rs @@ -0,0 +1,357 @@ +use crate::{ + deduplicate_entities, deduplicate_local_entities, + entity::{ + self, add_dead, update_bounding_box, EntityUuid, MinecraftEntityId, Position, WorldName, + }, + update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer, +}; +use azalea_core::ChunkPos; +use azalea_ecs::{ + app::{App, CoreStage, Plugin}, + component::Component, + ecs::Ecs, + ecs::EntityMut, + entity::Entity, + query::{Added, Changed, With, Without}, + schedule::{IntoSystemDescriptor, SystemSet}, + system::{Command, Commands, Query, Res, ResMut, Resource}, +}; +use derive_more::{Deref, DerefMut}; +use log::{debug, warn}; +use nohash_hasher::IntMap; +use parking_lot::RwLock; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, +}; +use uuid::Uuid; + +/// Plugin handling some basic entity functionality. +pub struct EntityPlugin; +impl Plugin for EntityPlugin { + fn build(&self, app: &mut App) { + app.add_system_set( + SystemSet::new() + .after("tick") + .after("packet") + .with_system(update_entity_chunk_positions) + .with_system(remove_despawned_entities_from_indexes) + .with_system(update_bounding_box) + .with_system(add_dead) + .with_system( + add_updates_received + .after("deduplicate_entities") + .after("deduplicate_local_entities") + .label("add_updates_received"), + ) + .with_system( + update_uuid_index + .label("update_uuid_index") + .after("deduplicate_local_entities") + .after("deduplicate_entities"), + ) + .with_system(debug_detect_updates_received_on_local_entities) + .with_system( + update_entity_by_id_index + .label("update_entity_by_id_index") + .after("deduplicate_entities"), + ) + .with_system(debug_new_entity), + ) + .add_system_set_to_stage( + CoreStage::PostUpdate, + SystemSet::new() + .with_system(deduplicate_entities.label("deduplicate_entities")) + .with_system( + deduplicate_local_entities + .label("deduplicate_local_entities") + .before("update_uuid_index") + .before("update_entity_by_id_index"), + ), + ) + .init_resource::<EntityInfos>(); + } +} + +fn debug_new_entity(query: Query<Entity, Added<MinecraftEntityId>>) { + for entity in query.iter() { + debug!("new entity: {:?}", entity); + } +} + +// How entity updates are processed (to avoid issues with shared worlds) +// - each bot contains a map of { entity id: updates received } +// - the shared world also contains a canonical "true" updates received for each +// entity +// - when a client loads an entity, its "updates received" is set to the same as +// the global "updates received" +// - when the shared world sees an entity for the first time, the "updates +// received" is set to 1. +// - clients can force the shared "updates received" to 0 to make it so certain +// entities (i.e. other bots in our swarm) don't get confused and updated by +// other bots +// - when a client gets an update to an entity, we check if our "updates +// received" is the same as the shared world's "updates received": if it is, +// then process the update and increment the client's and shared world's +// "updates received" if not, then we simply increment our local "updates +// received" and do nothing else + +/// Keep track of certain metadatas that are only relevant for this partial +/// world. +#[derive(Debug, Default)] +pub struct PartialEntityInfos { + // note: using MinecraftEntityId for entity ids is acceptable here since + // there's no chance of collisions here + /// The entity id of the player that owns this partial world. This will + /// make [`RelativeEntityUpdate`] pretend the entity doesn't exist so + /// it doesn't get modified from outside sources. + pub owner_entity: Option<Entity>, + /// A counter for each entity that tracks how many updates we've observed + /// for it. + /// + /// This is used for shared worlds (i.e. swarms), to make sure we don't + /// update entities twice on accident. + pub updates_received: IntMap<MinecraftEntityId, u32>, +} + +impl PartialEntityInfos { + pub fn new(owner_entity: Option<Entity>) -> Self { + Self { + owner_entity, + updates_received: IntMap::default(), + } + } +} + +/// A [`Command`] that applies a "relative update" to an entity, which means +/// this update won't be run multiple times by different clients in the same +/// world. +/// +/// This is used to avoid a bug where when there's multiple clients in the same +/// world and an entity sends a relative move packet to all clients, its +/// position gets desynced since the relative move is applied multiple times. +/// +/// Don't use this unless you actually got an entity update packet that all +/// other clients within render distance will get too. You usually don't need +/// this when the change isn't relative either. +pub struct RelativeEntityUpdate { + pub entity: Entity, + pub partial_world: Arc<RwLock<PartialWorld>>, + // a function that takes the entity and updates it + pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>, +} +impl Command for RelativeEntityUpdate { + fn write(self, world: &mut Ecs) { + let partial_entity_infos = &mut self.partial_world.write().entity_infos; + + let mut entity = world.entity_mut(self.entity); + + if Some(self.entity) == partial_entity_infos.owner_entity { + // if the entity owns this partial world, it's always allowed to update itself + (self.update)(&mut entity); + return; + }; + + let entity_id = *entity.get::<MinecraftEntityId>().unwrap(); + + let Some(updates_received) = entity.get_mut::<UpdatesReceived>() else { + // a client tried to update another client, which isn't allowed + return; + }; + + let this_client_updates_received = partial_entity_infos + .updates_received + .get(&entity_id) + .copied(); + + let can_update = this_client_updates_received.unwrap_or(1) == **updates_received; + if can_update { + let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; + partial_entity_infos + .updates_received + .insert(entity_id, new_updates_received); + + **entity.get_mut::<UpdatesReceived>().unwrap() = new_updates_received; + + let mut entity = world.entity_mut(self.entity); + (self.update)(&mut entity); + } + } +} + +/// Things that are shared between all the partial worlds. +#[derive(Resource, Default)] +pub struct EntityInfos { + /// An index of entities by their UUIDs + pub(crate) entity_by_uuid: HashMap<Uuid, Entity>, +} + +impl EntityInfos { + pub fn new() -> Self { + Self { + entity_by_uuid: HashMap::default(), + } + } + + pub fn get_entity_by_uuid(&self, uuid: &Uuid) -> Option<Entity> { + self.entity_by_uuid.get(uuid).copied() + } +} + +/// Update the chunk position indexes in [`EntityInfos`]. +fn update_entity_chunk_positions( + mut query: Query< + ( + Entity, + &entity::Position, + &mut entity::LastSentPosition, + &entity::WorldName, + ), + Changed<entity::Position>, + >, + world_container: Res<WorldContainer>, +) { + for (entity, pos, last_pos, world_name) in query.iter_mut() { + let world_lock = world_container.get(world_name).unwrap(); + let mut world = world_lock.write(); + + let old_chunk = ChunkPos::from(*last_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) = world.entities_by_chunk.get_mut(&old_chunk) { + entities.remove(&entity); + } + world + .entities_by_chunk + .entry(new_chunk) + .or_default() + .insert(entity); + } + } +} +/// A component that lists all the local player entities that have this entity +/// loaded. If this is empty, the entity will be removed from the ECS. +#[derive(Component, Clone, Deref, DerefMut)] +pub struct LoadedBy(pub HashSet<Entity>); + +/// A component that counts the number of times this entity has been modified. +/// This is used for making sure two clients don't do the same relative update +/// on an entity. +/// +/// If an entity is local (i.e. it's a client/localplayer), this component +/// should NOT be present in the entity. +#[derive(Component, Debug, Deref, DerefMut)] +pub struct UpdatesReceived(u32); + +#[allow(clippy::type_complexity)] +pub fn add_updates_received( + mut commands: Commands, + query: Query< + Entity, + ( + Changed<MinecraftEntityId>, + (Without<UpdatesReceived>, Without<Local>), + ), + >, +) { + for entity in query.iter() { + // entities always start with 1 update received + commands.entity(entity).insert(UpdatesReceived(1)); + } +} + +/// A marker component that signifies that this entity is "local" and shouldn't +/// be updated by other clients. +#[derive(Component)] +pub struct Local; + +/// The [`UpdatesReceived`] component should never be on [`Local`] entities. +/// This warns if an entity has both components. +fn debug_detect_updates_received_on_local_entities( + query: Query<Entity, (With<Local>, With<UpdatesReceived>)>, +) { + for entity in &query { + warn!("Entity {:?} has both Local and UpdatesReceived", entity); + } +} + +/// Despawn entities that aren't being loaded by anything. +fn remove_despawned_entities_from_indexes( + mut commands: Commands, + mut entity_infos: ResMut<EntityInfos>, + world_container: Res<WorldContainer>, + query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed<LoadedBy>>, +) { + for (entity, uuid, position, world_name, loaded_by) in &query { + let world_lock = world_container.get(world_name).unwrap(); + let mut world = world_lock.write(); + + // if the entity has no references left, despawn it + if !loaded_by.is_empty() { + continue; + } + + // remove the entity from the chunk index + let chunk = ChunkPos::from(*position); + if let Some(entities_in_chunk) = world.entities_by_chunk.get_mut(&chunk) { + if entities_in_chunk.remove(&entity) { + // remove the chunk if there's no entities in it anymore + if entities_in_chunk.is_empty() { + world.entities_by_chunk.remove(&chunk); + } + } else { + warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there."); + } + } else { + warn!("Tried to remove entity from chunk {chunk:?} but the chunk was not found."); + } + // remove it from the uuid index + if entity_infos.entity_by_uuid.remove(uuid).is_none() { + warn!("Tried to remove entity {entity:?} from the uuid index but it was not there."); + } + // and now remove the entity from the ecs + commands.entity(entity).despawn(); + debug!("Despawned entity {entity:?} because it was not loaded by anything."); + return; + } +} + +impl Debug for EntityInfos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EntityInfos").finish() + } +} + +// #[cfg(test)] +// mod tests { +// use crate::entity::metadata; + +// use super::*; +// use azalea_core::Vec3; + +// #[test] +// fn test_store_entity() { +// let mut storage = PartialEntityInfos::default(); +// assert!(storage.limited_get_by_id(0).is_none()); +// assert!(storage.shared.read().get_by_id(0).is_none()); + +// let uuid = Uuid::from_u128(100); +// storage.insert( +// 0, +// EntityData::new( +// uuid, +// Vec3::default(), +// EntityMetadata::Player(metadata::Player::default()), +// ), +// ); +// assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid); +// assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid); + +// storage.remove_by_id(0); +// assert!(storage.limited_get_by_id(0).is_none()); +// assert!(storage.shared.read().get_by_id(0).is_none()); +// } +// } diff --git a/azalea-world/src/entity_storage.rs b/azalea-world/src/entity_storage.rs deleted file mode 100755 index 1bc1adc5..00000000 --- a/azalea-world/src/entity_storage.rs +++ /dev/null @@ -1,416 +0,0 @@ -use crate::entity::EntityData; -use azalea_core::ChunkPos; -use log::warn; -use nohash_hasher::{IntMap, IntSet}; -use parking_lot::RwLock; -use std::{ - collections::HashMap, - sync::{Arc, Weak}, -}; -use uuid::Uuid; - -// How entity updates are processed (to avoid issues with shared worlds) -// - each bot contains a map of { entity id: updates received } -// - the shared world also contains a canonical "true" updates received for each -// entity -// - when a client loads an entity, its "updates received" is set to the same as -// the global "updates received" -// - when the shared world sees an entity for the first time, the "updates -// received" is set to 1. -// - clients can force the shared "updates received" to 0 to make it so certain -// entities (i.e. other bots in our swarm) don't get confused and updated by -// other bots -// - when a client gets an update to an entity, we check if our "updates -// received" is the same as the shared world's "updates received": if it is, -// then process the update and increment the client's and shared world's -// "updates received" if not, then we simply increment our local "updates -// received" and do nothing else - -/// Store a map of entities by ID. To get an iterator over all entities, use -/// `storage.shared.read().entities` [`WeakEntityStorage::entities`]. -/// -/// This is meant to be used with shared worlds. -/// -/// You can access the shared storage with `world.shared.read()`. -#[derive(Debug, Default)] -pub struct PartialEntityStorage { - pub shared: Arc<RwLock<WeakEntityStorage>>, - - /// The entity id of the player that owns this partial world. This will - /// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so - /// it doesn't get modified from outside sources. - /// - /// [`PartialWorld::entity_mut`]: crate::PartialWorld::entity_mut - pub owner_entity_id: Option<u32>, - pub updates_received: IntMap<u32, u32>, - /// Strong references to the entities we have loaded. - data_by_id: IntMap<u32, Arc<EntityData>>, -} - -/// Weakly store entities in a world. If the entities aren't being referenced -/// by anything else (like an [`PartialEntityStorage`]), they'll be forgotten. -#[derive(Debug, Default)] -pub struct WeakEntityStorage { - data_by_id: IntMap<u32, Weak<EntityData>>, - /// An index of all the entity ids we know are in a chunk - ids_by_chunk: HashMap<ChunkPos, IntSet<u32>>, - /// An index of entity ids by their UUIDs - id_by_uuid: HashMap<Uuid, u32>, - - pub updates_received: IntMap<u32, u32>, -} - -impl PartialEntityStorage { - pub fn new(shared: Arc<RwLock<WeakEntityStorage>>, owner_entity_id: Option<u32>) -> Self { - if let Some(owner_entity_id) = owner_entity_id { - shared.write().updates_received.insert(owner_entity_id, 0); - } - Self { - shared, - owner_entity_id, - updates_received: IntMap::default(), - data_by_id: IntMap::default(), - } - } - - /// Add an entity to the storage. - #[inline] - pub fn insert(&mut self, id: u32, entity: EntityData) { - // if the entity is already in the shared world, we don't need to do anything - if self.shared.read().data_by_id.contains_key(&id) { - return; - } - - // add the entity to the "indexes" - let mut shared = self.shared.write(); - shared - .ids_by_chunk - .entry(ChunkPos::from(entity.pos())) - .or_default() - .insert(id); - shared.id_by_uuid.insert(entity.uuid, id); - - // now store the actual entity data - let entity = Arc::new(entity); - shared.data_by_id.insert(id, Arc::downgrade(&entity)); - self.data_by_id.insert(id, entity); - // set our updates_received to the shared updates_received, unless it's - // not there in which case set both to 1 - if let Some(&shared_updates_received) = shared.updates_received.get(&id) { - // 0 means we're never tracking updates for this entity - if shared_updates_received != 0 || Some(id) == self.owner_entity_id { - self.updates_received.insert(id, 1); - } - } else { - shared.updates_received.insert(id, 1); - self.updates_received.insert(id, 1); - } - } - - /// Remove an entity from this storage by its id. It will only be removed - /// from the shared storage if there are no other references to it. - #[inline] - pub fn remove_by_id(&mut self, id: u32) { - if let Some(entity) = self.data_by_id.remove(&id) { - let chunk = ChunkPos::from(entity.pos()); - let uuid = entity.uuid; - self.updates_received.remove(&id); - drop(entity); - // maybe remove it from the storage - self.shared.write().remove_entity_if_unused(id, uuid, chunk); - } else { - warn!("Tried to remove entity with id {id} but it was not found.") - } - } - - /// Whether the entity with the given id is being loaded by this storage. - /// If you want to check whether the entity is in the shared storage, use - /// [`WeakEntityStorage::contains_id`]. - #[inline] - pub fn limited_contains_id(&self, id: &u32) -> bool { - self.data_by_id.contains_key(id) - } - - /// Get a reference to an entity by its id, if it's being loaded by this - /// storage. - #[inline] - pub fn limited_get_by_id(&self, id: u32) -> Option<&Arc<EntityData>> { - self.data_by_id.get(&id) - } - - /// Get a mutable reference to an entity by its id, if it's being loaded by - /// this storage. - #[inline] - pub fn limited_get_mut_by_id(&mut self, id: u32) -> Option<&mut Arc<EntityData>> { - self.data_by_id.get_mut(&id) - } - - /// Returns whether we're allowed to update this entity (to prevent two - /// clients in a shared world updating it twice), and acknowleges that - /// we WILL update it if it's true. Don't call this unless you actually - /// got an entity update that all other clients within render distance - /// will get too. - pub fn maybe_update(&mut self, id: u32) -> bool { - let this_client_updates_received = self.updates_received.get(&id).copied(); - let shared_updates_received = self.shared.read().updates_received.get(&id).copied(); - - let can_update = this_client_updates_received == shared_updates_received; - if can_update { - let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; - self.updates_received.insert(id, new_updates_received); - self.shared - .write() - .updates_received - .insert(id, new_updates_received); - true - } else { - false - } - } - - /// Get a reference to an entity by its UUID, if it's being loaded by this - /// storage. - #[inline] - pub fn limited_get_by_uuid(&self, uuid: &Uuid) -> Option<&Arc<EntityData>> { - self.shared - .read() - .id_by_uuid - .get(uuid) - .and_then(|id| self.data_by_id.get(id)) - } - - /// Get a mutable reference to an entity by its UUID, if it's being loaded - /// by this storage. - #[inline] - pub fn limited_get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Arc<EntityData>> { - self.shared - .read() - .id_by_uuid - .get(uuid) - .and_then(|id| self.data_by_id.get_mut(id)) - } - - /// Clear all entities in a chunk. This will not clear them from the - /// shared storage, unless there are no other references to them. - pub fn clear_chunk(&mut self, chunk: &ChunkPos) { - if let Some(entities) = self.shared.read().ids_by_chunk.get(chunk) { - for id in entities.iter() { - if let Some(entity) = self.data_by_id.remove(id) { - let uuid = entity.uuid; - drop(entity); - // maybe remove it from the storage - self.shared - .write() - .remove_entity_if_unused(*id, uuid, *chunk); - } - } - // for entity_id in entities { - // self.remove_by_id(entity_id); - // } - } - } - - /// Move an entity from its old chunk to a new chunk. - #[inline] - pub fn update_entity_chunk( - &mut self, - entity_id: u32, - old_chunk: &ChunkPos, - new_chunk: &ChunkPos, - ) { - self.shared - .write() - .update_entity_chunk(entity_id, old_chunk, new_chunk); - } -} - -impl WeakEntityStorage { - pub fn new() -> Self { - Self { - data_by_id: IntMap::default(), - ids_by_chunk: HashMap::default(), - id_by_uuid: HashMap::default(), - updates_received: IntMap::default(), - } - } - - /// Remove an entity from the storage if it has no strong references left. - /// Returns whether the entity was removed. - pub fn remove_entity_if_unused(&mut self, id: u32, uuid: Uuid, chunk: ChunkPos) -> bool { - if self.data_by_id.get(&id).and_then(|e| e.upgrade()).is_some() { - // if we could get the entity, that means there are still strong - // references to it - false - } else { - if self.ids_by_chunk.remove(&chunk).is_none() { - warn!("Tried to remove entity with id {id} from chunk {chunk:?} but it was not found."); - } - if self.id_by_uuid.remove(&uuid).is_none() { - warn!( - "Tried to remove entity with id {id} from uuid {uuid:?} but it was not found." - ); - } - if self.updates_received.remove(&id).is_none() { - // if this happens it means we weren't tracking the updates_received for the - // client (bad) - warn!( - "Tried to remove entity with id {id} from updates_received but it was not found." - ); - } - true - } - } - - /// Remove a chunk from the storage if the entities in it have no strong - /// references left. - pub fn remove_chunk_if_unused(&mut self, chunk: &ChunkPos) { - if let Some(entities) = self.ids_by_chunk.get(chunk) { - if entities.is_empty() { - self.ids_by_chunk.remove(chunk); - } - } - } - - /// Get an iterator over all entities in the shared storage. The iterator - /// is over `Weak<EntityData>`s, so you'll have to manually try to upgrade. - /// - /// # Examples - /// - /// ```rust - /// # use std::sync::Arc; - /// # use azalea_world::{PartialEntityStorage, entity::{EntityData, EntityMetadata, metadata}}; - /// # use azalea_core::Vec3; - /// # use uuid::Uuid; - /// # - /// let mut storage = PartialEntityStorage::default(); - /// storage.insert( - /// 0, - /// EntityData::new( - /// Uuid::nil(), - /// Vec3::default(), - /// EntityMetadata::Player(metadata::Player::default()), - /// ), - /// ); - /// for entity in storage.shared.read().entities() { - /// if let Some(entity) = entity.upgrade() { - /// println!("Entity: {:?}", entity); - /// } - /// } - /// ``` - pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Weak<EntityData>> { - self.data_by_id.values() - } - - /// Whether the entity with the given id is in the shared storage. - #[inline] - pub fn contains_id(&self, id: &u32) -> bool { - self.data_by_id.contains_key(id) - } - - /// Get an entity by its id, if it exists. - #[inline] - pub fn get_by_id(&self, id: u32) -> Option<Arc<EntityData>> { - self.data_by_id.get(&id).and_then(|e| e.upgrade()) - } - - /// Get an entity in the shared storage by its UUID, if it exists. - #[inline] - pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<Arc<EntityData>> { - self.id_by_uuid - .get(uuid) - .and_then(|id| self.data_by_id.get(id).and_then(|e| e.upgrade())) - } - - pub fn entity_by<F>(&self, mut f: F) -> Option<Arc<EntityData>> - where - F: FnMut(&Arc<EntityData>) -> bool, - { - for entity in self.entities() { - if let Some(entity) = entity.upgrade() { - if f(&entity) { - return Some(entity); - } - } - } - None - } - - pub fn entities_by<F>(&self, mut f: F) -> Vec<Arc<EntityData>> - where - F: FnMut(&Arc<EntityData>) -> bool, - { - let mut entities = Vec::new(); - for entity in self.entities() { - if let Some(entity) = entity.upgrade() { - if f(&entity) { - entities.push(entity); - } - } - } - entities - } - - pub fn entity_by_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<Arc<EntityData>> - where - F: FnMut(&EntityData) -> bool, - { - if let Some(entities) = self.ids_by_chunk.get(chunk) { - for entity_id in entities { - if let Some(entity) = self.data_by_id.get(entity_id).and_then(|e| e.upgrade()) { - if f(&entity) { - return Some(entity); - } - } - } - } - None - } - - /// Move an entity from its old chunk to a new chunk. - #[inline] - pub fn update_entity_chunk( - &mut self, - entity_id: u32, - old_chunk: &ChunkPos, - new_chunk: &ChunkPos, - ) { - if let Some(entities) = self.ids_by_chunk.get_mut(old_chunk) { - entities.remove(&entity_id); - } - self.ids_by_chunk - .entry(*new_chunk) - .or_default() - .insert(entity_id); - } -} - -#[cfg(test)] -mod tests { - use crate::entity::{metadata, EntityMetadata}; - - use super::*; - use azalea_core::Vec3; - - #[test] - fn test_store_entity() { - let mut storage = PartialEntityStorage::default(); - assert!(storage.limited_get_by_id(0).is_none()); - assert!(storage.shared.read().get_by_id(0).is_none()); - - let uuid = Uuid::from_u128(100); - storage.insert( - 0, - EntityData::new( - uuid, - Vec3::default(), - EntityMetadata::Player(metadata::Player::default()), - ), - ); - assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid); - assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid); - - storage.remove_by_id(0); - assert!(storage.limited_get_by_id(0).is_none()); - assert!(storage.shared.read().get_by_id(0).is_none()); - } -} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 5ea5db17..88715f44 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -7,16 +7,18 @@ mod bit_storage; mod chunk_storage; mod container; pub mod entity; -mod entity_storage; +mod entity_info; mod palette; mod world; use std::backtrace::Backtrace; pub use bit_storage::BitStorage; -pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage}; +pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage}; pub use container::*; -pub use entity_storage::{PartialEntityStorage, WeakEntityStorage}; +pub use entity_info::{ + EntityInfos, EntityPlugin, LoadedBy, Local, PartialEntityInfos, RelativeEntityUpdate, +}; use thiserror::Error; pub use world::*; diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index f78b2082..fef5f207 100755 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -87,7 +87,7 @@ impl PalettedContainer { /// want `.set` instead. pub fn set_at_index(&mut self, index: usize, value: u32) { let paletted_value = self.id_for(value); - self.storage.set(index, paletted_value as u64) + self.storage.set(index, paletted_value as u64); } /// Sets the id at the given coordinates and return the previous id diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 6a3652f8..5ec2bbac 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -1,229 +1,237 @@ use crate::{ - entity::{Entity, EntityData}, - Chunk, MoveEntityError, PartialChunkStorage, PartialEntityStorage, WeakChunkStorage, - WeakEntityStorage, + entity::{EntityUuid, MinecraftEntityId, WorldName}, + entity_info::LoadedBy, + ChunkStorage, EntityInfos, Local, PartialChunkStorage, PartialEntityInfos, WorldContainer, +}; +use azalea_core::ChunkPos; +use azalea_ecs::{ + entity::Entity, + query::{Changed, With, Without}, + system::{Commands, Query, Res, ResMut}, +}; +use log::{debug, error, info}; +use nohash_hasher::IntMap; +use std::fmt::Formatter; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, }; -use azalea_block::BlockState; -use azalea_buf::BufReadError; -use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3}; -use parking_lot::RwLock; -use std::{backtrace::Backtrace, fmt::Debug}; -use std::{fmt::Formatter, io::Cursor, sync::Arc}; -use uuid::Uuid; /// PartialWorlds are usually owned by clients, and hold strong references to -/// chunks and entities in [`WeakWorld`]s. +/// chunks and entities in [`World`]s. /// /// Basically, they hold the chunks and entities that are within render /// distance but can still access chunks and entities owned by other -/// `PartialWorld`s that have the same `WeakWorld`. +/// `PartialWorld`s that have the same `World`. /// /// This is primarily useful for having multiple clients in the same world. pub struct PartialWorld { - // we just need to keep a strong reference to `shared` so it doesn't get - // dropped, we don't need to do anything with it - pub shared: Arc<WeakWorld>, - - pub chunk_storage: PartialChunkStorage, - pub entity_storage: PartialEntityStorage, -} - -/// A world where the chunks are stored as weak pointers. This is used for -/// shared worlds. -#[derive(Default, Debug)] -pub struct WeakWorld { - pub chunk_storage: Arc<RwLock<WeakChunkStorage>>, - pub entity_storage: Arc<RwLock<WeakEntityStorage>>, + pub chunks: PartialChunkStorage, + /// Some metadata about entities, like what entities are in certain chunks. + /// This does not contain the entity data itself, that's in the ECS. + pub entity_infos: PartialEntityInfos, } impl PartialWorld { - pub fn new(chunk_radius: u32, shared: Arc<WeakWorld>, owner_entity_id: Option<u32>) -> Self { + pub fn new(chunk_radius: u32, owner_entity: Option<Entity>) -> Self { PartialWorld { - shared: shared.clone(), - chunk_storage: PartialChunkStorage::new(chunk_radius, shared.chunk_storage.clone()), - entity_storage: PartialEntityStorage::new( - shared.entity_storage.clone(), - owner_entity_id, - ), - } - } - - pub fn replace_with_packet_data( - &mut self, - pos: &ChunkPos, - data: &mut Cursor<&[u8]>, - ) -> Result<(), BufReadError> { - self.chunk_storage.replace_with_packet_data(pos, data) - } - - pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> { - self.chunk_storage.get(pos) - } - - pub fn set_chunk(&mut self, pos: &ChunkPos, chunk: Option<Chunk>) -> Result<(), BufReadError> { - self.chunk_storage - .set(pos, chunk.map(|c| Arc::new(RwLock::new(c)))); - Ok(()) - } - - pub fn update_view_center(&mut self, pos: &ChunkPos) { - self.chunk_storage.view_center = *pos; - } - - pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> Option<BlockState> { - self.chunk_storage.set_block_state(pos, state) - } - - /// Returns a mutable reference to the entity with the given ID. - pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &WeakWorld>> { - // no entity for you (we're processing this entity somewhere else) - if Some(id) != self.entity_storage.owner_entity_id && !self.entity_storage.maybe_update(id) - { - return None; + chunks: PartialChunkStorage::new(chunk_radius), + entity_infos: PartialEntityInfos::new(owner_entity), } - - self.shared.entity(id) - } - - pub fn add_entity(&mut self, id: u32, entity: EntityData) { - self.entity_storage.insert(id, entity); - } - - pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> { - let mut entity = self - .entity_mut(entity_id) - .ok_or_else(|| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?; - let old_chunk = ChunkPos::from(entity.pos()); - let new_chunk = ChunkPos::from(&new_pos); - // this is fine because we update the chunk below - unsafe { entity.move_unchecked(new_pos) }; - if old_chunk != new_chunk { - self.entity_storage - .update_entity_chunk(entity_id, &old_chunk, &new_chunk); - } - Ok(()) } +} - pub fn move_entity_with_delta( - &mut self, - entity_id: u32, - delta: &PositionDelta8, - ) -> Result<(), MoveEntityError> { - let mut entity = self - .entity_mut(entity_id) - .ok_or_else(|| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?; - let new_pos = entity.pos().with_delta(delta); - - let old_chunk = ChunkPos::from(entity.pos()); - let new_chunk = ChunkPos::from(&new_pos); - // this is fine because we update the chunk below - - unsafe { entity.move_unchecked(new_pos) }; - if old_chunk != new_chunk { - self.entity_storage - .update_entity_chunk(entity_id, &old_chunk, &new_chunk); +/// Remove new entities that have the same id as an existing entity, and +/// increase the reference counts. +/// +/// This is the reason why spawning entities into the ECS when you get a spawn +/// entity packet is okay. This system will make sure the new entity gets +/// combined into the old one. +#[allow(clippy::type_complexity)] +pub fn deduplicate_entities( + mut commands: Commands, + mut query: Query< + (Entity, &MinecraftEntityId, &WorldName), + (Changed<MinecraftEntityId>, Without<Local>), + >, + mut loaded_by_query: Query<&mut LoadedBy>, + world_container: Res<WorldContainer>, +) { + // if this entity already exists, remove it + for (new_entity, id, world_name) in query.iter_mut() { + if let Some(world_lock) = world_container.get(world_name) { + let world = world_lock.write(); + if let Some(old_entity) = world.entity_by_id.get(id) { + if old_entity == &new_entity { + continue; + } + + // this entity already exists!!! remove the one we just added but increase + // the reference count + let new_loaded_by = loaded_by_query + .get(new_entity) + .unwrap_or_else(|_| panic!( + "Entities should always have the LoadedBy component ({new_entity:?} did not)" + )) + .clone(); + let old_loaded_by = loaded_by_query.get_mut(*old_entity); + // merge them if possible + if let Ok(mut old_loaded_by) = old_loaded_by { + old_loaded_by.extend(new_loaded_by.iter()); + } + commands.entity(new_entity).despawn(); + info!( + "Entity with id {id:?} / {new_entity:?} already existed in the world, merging it with {old_entity:?}" + ); + break; + } + } else { + error!("Entity was inserted into a world that doesn't exist."); } - Ok(()) } } -impl WeakWorld { - pub fn new(height: u32, min_y: i32) -> Self { - WeakWorld { - chunk_storage: Arc::new(RwLock::new(WeakChunkStorage::new(height, min_y))), - entity_storage: Arc::new(RwLock::new(WeakEntityStorage::new())), +// when a local entity is added, if there was already an entity with the same id +// then delete the old entity +#[allow(clippy::type_complexity)] +pub fn deduplicate_local_entities( + mut commands: Commands, + mut query: Query< + (Entity, &MinecraftEntityId, &WorldName), + (Changed<MinecraftEntityId>, With<Local>), + >, + world_container: Res<WorldContainer>, +) { + // if this entity already exists, remove the old one + for (new_entity, id, world_name) in query.iter_mut() { + if let Some(world_lock) = world_container.get(world_name) { + let world = world_lock.write(); + if let Some(old_entity) = world.entity_by_id.get(id) { + if old_entity == &new_entity { + // lol + continue; + } + + commands.entity(*old_entity).despawn(); + debug!( + "Added local entity {id:?} / {new_entity:?} but already existed in world as {old_entity:?}, despawning {old_entity:?}" + ); + break; + } + } else { + error!("Entity was inserted into a world that doesn't exist."); } } +} - /// Read the total height of the world. You can add this to [`Self::min_y`] - /// to get the highest possible y coordinate a block can be placed at. - pub fn height(&self) -> u32 { - self.chunk_storage.read().height - } - - /// Get the lowest possible y coordinate a block can be placed at. - pub fn min_y(&self) -> i32 { - self.chunk_storage.read().min_y - } - - pub fn entity_data_by_id(&self, id: u32) -> Option<Arc<EntityData>> { - self.entity_storage.read().get_by_id(id) - } - - /// Returns a entity with the given ID. - /// - /// The returned Entity can technically be mutated, but you should avoid - /// doing any relative mutations. - pub fn entity(&self, id: u32) -> Option<Entity<&WeakWorld>> { - let entity_data = self.entity_storage.read().get_by_id(id)?; - let entity_ptr = unsafe { entity_data.as_ptr() }; - Some(Entity::new(self, id, entity_ptr)) +pub fn update_uuid_index( + mut entity_infos: ResMut<EntityInfos>, + query: Query<(Entity, &EntityUuid), Changed<EntityUuid>>, +) { + for (entity, &uuid) in query.iter() { + // only add it if it doesn't already exist in + // entity_infos.entity_by_uuid + // if entity_infos.entity_by_uuid.contains_key(&uuid) { + // warn!("Entity with UUID {uuid:?} already existed in the world, not adding + // to index (ecs id: {entity:?})", uuid=*uuid); continue; + // } + entity_infos.entity_by_uuid.insert(*uuid, entity); } +} - pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<Arc<EntityData>> { - self.entity_storage.read().get_by_uuid(uuid) - } +// /// Clear all entities in a chunk. This will not clear them from the +// /// shared storage unless there are no other references to them. +// pub fn clear_entities_in_chunk( +// mut commands: Commands, +// partial_entity_infos: &mut PartialEntityInfos, +// chunk: &ChunkPos, +// world_container: &WorldContainer, +// world_name: &WorldName, +// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>, +// ) { +// let world_lock = world_container.get(world_name).unwrap(); +// let world = world_lock.read(); + +// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() { +// for &entity in &entities { +// let (id, mut reference_count) = query.get_mut(entity).unwrap(); +// if partial_entity_infos.loaded_entity_ids.remove(id) { +// // decrease the reference count +// **reference_count -= 1; +// } +// } +// } +// } - pub fn entity_by<F>(&self, mut f: F) -> Option<Arc<EntityData>> - where - F: FnMut(&EntityData) -> bool, - { - self.entity_storage.read().entity_by(|e| f(e)) - } - - pub fn entities_by<F>(&self, mut f: F) -> Vec<Arc<EntityData>> - where - F: FnMut(&EntityData) -> bool, - { - self.entity_storage.read().entities_by(|e| f(e)) - } +/// A world where the chunks are stored as weak pointers. This is used for +/// shared worlds. +#[derive(Default, Debug)] +pub struct World { + pub chunks: ChunkStorage, - pub fn set_entity_pos(&self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> { - let mut entity = self - .entity(entity_id) - .ok_or_else(|| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?; - let old_chunk = ChunkPos::from(entity.pos()); - let new_chunk = ChunkPos::from(&new_pos); - // this is fine because we update the chunk below - unsafe { entity.move_unchecked(new_pos) }; - if old_chunk != new_chunk { - self.entity_storage - .write() - .update_entity_chunk(entity_id, &old_chunk, &new_chunk); - } - Ok(()) - } + /// An index of all the entities we know are in the chunks of the world + pub entities_by_chunk: HashMap<ChunkPos, HashSet<Entity>>, - pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> { - self.chunk_storage.read().get_block_state(pos) - } + /// An index of Minecraft entity IDs to Azalea ECS entities. + pub entity_by_id: IntMap<MinecraftEntityId, Entity>, +} - pub fn get_chunk(&self, pos: &ChunkPos) -> Option<Arc<RwLock<Chunk>>> { - self.chunk_storage.read().get(pos) +impl World { + /// Get an ECS [`Entity`] from a Minecraft entity ID. + pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> { + self.entity_by_id.get(entity_id).copied() } } impl Debug for PartialWorld { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("World") - .field("chunk_storage", &self.chunk_storage) - .field("entity_storage", &self.entity_storage) - .field("shared", &self.shared) + .field("chunk_storage", &self.chunks) + .field("entity_storage", &self.entity_infos) .finish() } } impl Default for PartialWorld { + /// Creates a completely self-contained `PartialWorld`. This is only for + /// testing and shouldn't be used in actual code! fn default() -> Self { let chunk_storage = PartialChunkStorage::default(); - let entity_storage = PartialEntityStorage::default(); + let entity_storage = PartialEntityInfos::default(); + Self { + chunks: chunk_storage, + entity_infos: entity_storage, + } + } +} + +/// System to keep the entity_by_id index up-to-date. +pub fn update_entity_by_id_index( + mut query: Query<(Entity, &MinecraftEntityId, &WorldName), Changed<MinecraftEntityId>>, + world_container: Res<WorldContainer>, +) { + for (entity, id, world_name) in query.iter_mut() { + let world_lock = world_container.get(world_name).unwrap(); + let mut world = world_lock.write(); + // if let Some(old_entity) = world.entity_by_id.get(id) { + // warn!( + // "Entity with ID {id:?} already existed in the world, not adding to + // index (old ecs id: {old_entity:?} / new ecs id: {entity:?})" ); + // continue; + // } + world.entity_by_id.insert(*id, entity); + debug!("Added {entity:?} to {world_name:?} with {id:?}."); + } +} + +impl From<ChunkStorage> for World { + /// Make an empty world from this `ChunkStorage`. This is meant to be a + /// convenience function for tests. + fn from(chunks: ChunkStorage) -> Self { Self { - shared: Arc::new(WeakWorld { - chunk_storage: chunk_storage.shared.clone(), - entity_storage: entity_storage.shared.clone(), - }), - chunk_storage, - entity_storage, + chunks, + entities_by_chunk: HashMap::new(), + entity_by_id: IntMap::default(), } } } diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 27217017..83067d0b 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -8,24 +8,30 @@ version = "0.5.0" [package.metadata.release] pre-release-replacements = [ - { file = "src/lib.rs", search = "//! `azalea = \"[a-z0-9\\.-]+\"`", replace = "//! `azalea = \"{{version}}\"`" }, + {file = "src/lib.rs", search = "//! `azalea = \"[a-z0-9\\.-]+\"`", replace = "//! `azalea = \"{{version}}\"`"}, ] [dependencies] anyhow = "^1.0.65" async-trait = "0.1.58" -azalea-block = { version = "0.5.0", path = "../azalea-block" } -azalea-chat = { version = "0.5.0", path = "../azalea-chat" } -azalea-client = { version = "0.5.0", path = "../azalea-client" } -azalea-core = { version = "0.5.0", path = "../azalea-core" } -azalea-physics = { version = "0.5.0", path = "../azalea-physics" } -azalea-protocol = { version = "0.5.0", path = "../azalea-protocol" } -azalea-world = { version = "0.5.0", path = "../azalea-world" } +azalea-block = {version = "0.5.0", path = "../azalea-block"} +azalea-chat = {version = "0.5.0", path = "../azalea-chat"} +azalea-client = {version = "0.5.0", path = "../azalea-client"} +azalea-core = {version = "0.5.0", path = "../azalea-core"} +azalea-ecs = { version = "0.5.0", path = "../azalea-ecs" } +azalea-physics = {version = "0.5.0", path = "../azalea-physics"} +azalea-protocol = {version = "0.5.0", path = "../azalea-protocol"} +azalea-registry = {version = "0.5.0", path = "../azalea-registry"} +azalea-world = {version = "0.5.0", path = "../azalea-world"} +bevy_tasks = "0.9.1" +derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} futures = "0.3.25" +futures-lite = "1.12.0" +iyes_loopless = "0.9.1" log = "0.4.17" nohash-hasher = "0.2.0" num-traits = "0.2.15" -parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] } +parking_lot = {version = "^0.12.1", features = ["deadlock_detection"]} priority-queue = "1.3.0" thiserror = "^1.0.37" tokio = "^1.24.2" diff --git a/azalea/README.md b/azalea/README.md index afd2feb4..ef822d9f 100755 --- a/azalea/README.md +++ b/azalea/README.md @@ -1,4 +1,85 @@ Azalea is a framework for creating Minecraft bots. -Internally, it's just a wrapper over azalea-client, adding useful functions for making bots. +Internally, it's just a wrapper over [`azalea_client`], adding useful +functions for making bots. Because of this, lots of the documentation will +refer to `azalea_client`. You can just replace these with `azalea` in your +code, since everything from azalea_client is re-exported in azalea. +# Installation + +First, install Rust nightly with `rustup install nightly` and `rustup +default nightly`. + +Then, add one of the following lines to your Cargo.toml: + +Latest bleeding-edge version: +`azalea = { git="https://github.com/mat-1/azalea" }`\ +Latest "stable" release: +`azalea = "0.5.0"` + +## Optimization + +For faster compile times, make a `.cargo/config.toml` file in your project +and copy +[this file](https://github.com/mat-1/azalea/blob/main/.cargo/config.toml) +into it. You may have to install the LLD linker. + +For faster performance in debug mode, add the following code to your +Cargo.toml: +```toml +[profile.dev] +opt-level = 1 +[profile.dev.package."*"] +opt-level = 3 +``` + + +# Examples + +```rust,no_run +A bot that logs chat messages sent in the server to the console. + +use azalea::prelude::*; +use parking_lot::Mutex; +use std::sync::Arc; + +#[tokio::main] +async fn main() { + let account = Account::offline("bot"); + // or Account::microsoft("example@example.com").await.unwrap(); + + azalea::start(azalea::Options { + account, + address: "localhost", + state: State::default(), + plugins: plugins![], + handle, + }) + .await + .unwrap(); +} + +#[derive(Default, Clone, Component)] +pub struct State {} + +async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { + match event { + Event::Chat(m) => { + println!("{}", m.message().to_ansi()); + } + _ => {} + } + + Ok(()) +} +``` + +# Plugins + +Azalea uses [Bevy ECS](https://docs.rs/bevy_ecs) internally to store information about the world and clients. Bevy plugins are more powerful than async handler functions, but more difficult to use. See [pathfinder](azalea/src/pathfinder/mod.rs) as an example of how to make a plugin. You can then use a plugin by adding `.add_plugin(ExamplePlugin)` in the client or swarm builder. + +Also note that just because something is an entity in the ECS doesn't mean that it's a Minecraft entity. You can filter for that by having `With<MinecraftEntityId>` as a filter. + +See the [https://bevy-cheatbook.github.io/programming/ecs-intro.html](Bevy Cheatbook) to learn more about Bevy ECS (and ECS in general). + +[`azalea_client`]: https://docs.rs/azalea-client
\ No newline at end of file diff --git a/azalea/examples/craft_dig_straight_down.rs b/azalea/examples/craft_dig_straight_down.rs index 864b9809..76979406 100755 --- a/azalea/examples/craft_dig_straight_down.rs +++ b/azalea/examples/craft_dig_straight_down.rs @@ -64,8 +64,12 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { crafting_table.close().await; bot.hold(&pickaxe); + loop { - if let Err(e) = bot.dig(bot.entity().feet_pos().down(1)).await { + if let Err(e) = bot + .dig(azalea::entity::feet_pos(bot.entity()).down(1)) + .await + { println!("{:?}", e); break; } diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs index 2093ff4e..f9bafebd 100755 --- a/azalea/examples/echo.rs +++ b/azalea/examples/echo.rs @@ -18,7 +18,7 @@ async fn main() { .unwrap(); } -#[derive(Default, Clone)] +#[derive(Default, Clone, Component)] pub struct State {} async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> { diff --git a/azalea/examples/mine_a_chunk.rs b/azalea/examples/mine_a_chunk.rs index b48fb99c..72d27ff3 100644 --- a/azalea/examples/mine_a_chunk.rs +++ b/azalea/examples/mine_a_chunk.rs @@ -7,7 +7,7 @@ async fn main() { let mut states = Vec::new(); for i in 0..10 { - accounts.push(Account::offline(&format!("bot{}", i))); + accounts.push(Account::offline(&format!("bot{o}"))); states.push(State::default()); } diff --git a/azalea/examples/potatobot/autoeat.rs b/azalea/examples/potatobot/autoeat.rs index 89934fa2..34b418f9 100755 --- a/azalea/examples/potatobot/autoeat.rs +++ b/azalea/examples/potatobot/autoeat.rs @@ -1,6 +1,7 @@ //! Automatically eat when we get hungry. use async_trait::async_trait; +use azalea::prelude::*; use azalea::{Client, Event}; use parking_lot::Mutex; use std::sync::Arc; @@ -10,7 +11,7 @@ pub struct Plugin { pub state: State, } -#[derive(Default, Clone)] +#[derive(Default, Clone, Component)] pub struct State {} #[async_trait] diff --git a/azalea/examples/pvp.rs b/azalea/examples/pvp.rs index 9d2fdc35..28e54f35 100755 --- a/azalea/examples/pvp.rs +++ b/azalea/examples/pvp.rs @@ -7,7 +7,7 @@ async fn main() { let mut states = Vec::new(); for i in 0..10 { - accounts.push(Account::offline(&format!("bot{}", i))); + accounts.push(Account::offline(&format!("bot{i}"))); states.push(State::default()); } @@ -46,16 +46,16 @@ async fn swarm_handle( ) -> anyhow::Result<()> { match event { SwarmEvent::Tick => { - // choose an arbitrary player within render distance to target - if let Some(target) = swarm - .worlds - .read() - .entity_by(|e| e.id == "minecraft:player") + if let Some(target_entity) = + swarm.entity_by::<Player>(|name: &Name| name == "Herobrine") { + let target_bounding_box = + swarm.map_entity(target_entity, |bb: &BoundingBox| bb.clone()); + for (bot, bot_state) in swarm { - bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box)); + bot.tick_goto_goal(pathfinder::Goals::Reach(target_bounding_box)); // if target.bounding_box.distance(bot.eyes) < bot.reach_distance() { - if bot.entity().can_reach(target.bounding_box) { + if azalea::entities::can_reach(bot.entity(), target_bounding_box) { bot.swing(); } if !bot.using_held_item() && bot.hunger() <= 17 { diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 0674c692..510449d3 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -1,56 +1,115 @@ -use crate::{Client, Event}; -use async_trait::async_trait; use azalea_core::Vec3; -use parking_lot::Mutex; -use std::{f64::consts::PI, sync::Arc}; +use azalea_ecs::{ + app::{App, Plugin, PluginGroup, PluginGroupBuilder}, + component::Component, + entity::Entity, + event::EventReader, + query::{With, Without}, + schedule::IntoSystemDescriptor, + system::{Commands, Query}, + AppTickExt, +}; +use azalea_world::{ + entity::{metadata::Player, set_rotation, Jumping, Physics, Position}, + Local, +}; +use std::f64::consts::PI; -#[derive(Clone, Default)] -pub struct Plugin; -impl crate::Plugin for Plugin { - type State = State; +use crate::pathfinder::PathfinderPlugin; - fn build(&self) -> State { - State::default() +#[derive(Clone, Default)] +pub struct BotPlugin; +impl Plugin for BotPlugin { + fn build(&self, app: &mut App) { + app.add_event::<LookAtEvent>() + .add_event::<JumpEvent>() + .add_system(insert_bot.before("deduplicate_entities")) + .add_system(look_at_listener) + .add_system(jump_listener.label("jump_listener").before("ai_step")) + .add_tick_system(stop_jumping.after("ai_step")); } } -#[derive(Default, Clone)] -pub struct State { - jumping_once: Arc<Mutex<bool>>, +/// Component for all bots. +#[derive(Default, Component)] +pub struct Bot { + jumping_once: bool, +} + +/// Insert the [`Bot`] component for any local players that don't have it. +#[allow(clippy::type_complexity)] +fn insert_bot( + mut commands: Commands, + mut query: Query<Entity, (Without<Bot>, With<Local>, With<Player>)>, +) { + for entity in &mut query { + commands.entity(entity).insert(Bot::default()); + } } -#[async_trait] -impl crate::PluginState for State { - async fn handle(self: Box<Self>, event: Event, mut bot: Client) { - if let Event::Tick = event { - if *self.jumping_once.lock() && bot.jumping() { - *self.jumping_once.lock() = false; - bot.set_jumping(false); - } +fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) { + for (mut jumping, mut bot) in &mut query { + if bot.jumping_once && **jumping { + bot.jumping_once = false; + **jumping = false; } } } -pub trait BotTrait { +pub trait BotClientExt { fn jump(&mut self); - fn look_at(&mut self, pos: &Vec3); + fn look_at(&mut self, pos: Vec3); } -impl BotTrait for azalea_client::Client { +impl BotClientExt for azalea_client::Client { /// Queue a jump for the next tick. fn jump(&mut self) { - self.set_jumping(true); - let state = self.plugins.get::<State>().unwrap().clone(); - *state.jumping_once.lock() = true; + let mut ecs = self.ecs.lock(); + ecs.send_event(JumpEvent(self.entity)); } /// Turn the bot's head to look at the coordinate in the world. - fn look_at(&mut self, pos: &Vec3) { - let (y_rot, x_rot) = direction_looking_at(self.entity().pos(), pos); - self.set_rotation(y_rot, x_rot); + fn look_at(&mut self, position: Vec3) { + let mut ecs = self.ecs.lock(); + ecs.send_event(LookAtEvent { + entity: self.entity, + position, + }); + } +} + +/// Event to jump once. +pub struct JumpEvent(pub Entity); + +fn jump_listener(mut query: Query<(&mut Jumping, &mut Bot)>, mut events: EventReader<JumpEvent>) { + for event in events.iter() { + if let Ok((mut jumping, mut bot)) = query.get_mut(event.0) { + **jumping = true; + bot.jumping_once = true; + } } } +/// Make an entity look towards a certain position in the world. +pub struct LookAtEvent { + pub entity: Entity, + /// The position we want the entity to be looking at. + pub position: Vec3, +} +fn look_at_listener( + mut events: EventReader<LookAtEvent>, + mut query: Query<(&Position, &mut Physics)>, +) { + for event in events.iter() { + if let Ok((position, mut physics)) = query.get_mut(event.entity) { + let (y_rot, x_rot) = direction_looking_at(position, &event.position); + set_rotation(&mut physics, y_rot, x_rot); + } + } +} + +/// Return the (`y_rot`, `x_rot`) that would make a client at `current` be +/// looking at `target`. fn direction_looking_at(current: &Vec3, target: &Vec3) -> (f32, f32) { // borrowed from mineflayer's Bot.lookAt because i didn't want to do math let delta = target - current; @@ -59,3 +118,15 @@ fn direction_looking_at(current: &Vec3, target: &Vec3) -> (f32, f32) { let x_rot = f64::atan2(delta.y, ground_distance) * -(180.0 / PI); (y_rot as f32, x_rot as f32) } + +/// A [`PluginGroup`] for the plugins that add extra bot functionality to the +/// client. +pub struct DefaultBotPlugins; + +impl PluginGroup for DefaultBotPlugins { + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::start::<Self>() + .add(BotPlugin) + .add(PathfinderPlugin) + } +} diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 4ceb5b2e..026a35f5 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -1,94 +1,139 @@ -//! Azalea is a framework for creating Minecraft bots. -//! -//! Internally, it's just a wrapper over [`azalea_client`], adding useful -//! functions for making bots. Because of this, lots of the documentation will -//! refer to `azalea_client`. You can just replace these with `azalea` in your -//! code, since everything from azalea_client is re-exported in azalea. -//! -//! # Installation -//! -//! First, install Rust nightly with `rustup install nightly` and `rustup -//! default nightly`. -//! -//! Then, add one of the following lines to your Cargo.toml: -//! -//! Latest bleeding-edge version: -//! `azalea = { git="https://github.com/mat-1/azalea" }`\ -//! Latest "stable" release: -//! `azalea = "0.5.0"` -//! -//! ## Optimization -//! -//! For faster compile times, make a `.cargo/config.toml` file in your project -//! and copy -//! [this file](https://github.com/mat-1/azalea/blob/main/.cargo/config.toml) -//! into it. -//! -//! For faster performance in debug mode, add -//! ```toml -//! [profile.dev] -//! opt-level = 1 -//! [profile.dev.package."*"] -//! opt-level = 3 -//! ``` -//! to your Cargo.toml. You may have to install the LLD linker. -//! -//! # Examples -//! -//! ```rust,no_run -//! //! A bot that logs chat messages sent in the server to the console. -//! -//! use azalea::prelude::*; -//! use parking_lot::Mutex; -//! use std::sync::Arc; -//! -//! #[tokio::main] -//! async fn main() { -//! let account = Account::offline("bot"); -//! // or Account::microsoft("example@example.com").await.unwrap(); -//! -//! azalea::start(azalea::Options { -//! account, -//! address: "localhost", -//! state: State::default(), -//! plugins: plugins![], -//! handle, -//! }) -//! .await -//! .unwrap(); -//! } -//! -//! #[derive(Default, Clone)] -//! pub struct State {} -//! -//! async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { -//! match event { -//! Event::Chat(m) => { -//! println!("{}", m.message().to_ansi()); -//! } -//! _ => {} -//! } -//! -//! Ok(()) -//! } -//! ``` -//! -//! [`azalea_client`]: https://docs.rs/azalea-client - -#![feature(trait_upcasting)] +#![doc = include_str!("../README.md")] #![feature(async_closure)] -#![allow(incomplete_features)] mod bot; pub mod pathfinder; pub mod prelude; -mod start; mod swarm; -pub use azalea_block::*; +pub use azalea_block as blocks; pub use azalea_client::*; pub use azalea_core::{BlockPos, Vec3}; -pub use start::{start, Options}; +use azalea_ecs::{ + app::{App, Plugin}, + component::Component, +}; +pub use azalea_protocol as protocol; +pub use azalea_registry::EntityKind; +pub use azalea_world::{entity, World}; +use bot::DefaultBotPlugins; +use ecs::app::PluginGroup; +use futures::Future; +use protocol::{ + resolver::{self, ResolverError}, + ServerAddress, +}; pub use swarm::*; +use thiserror::Error; +use tokio::sync::mpsc; pub type HandleFn<Fut, S> = fn(Client, Event, S) -> Fut; + +#[derive(Error, Debug)] +pub enum StartError { + #[error("Invalid address")] + InvalidAddress, + #[error(transparent)] + ResolveAddress(#[from] ResolverError), + #[error("Join error: {0}")] + Join(#[from] azalea_client::JoinError), +} + +pub struct ClientBuilder<S, Fut> +where + S: Default + Send + Sync + Clone + 'static, + Fut: Future<Output = Result<(), anyhow::Error>>, +{ + app: App, + /// The function that's called every time a bot receives an [`Event`]. + handler: Option<HandleFn<Fut, S>>, + state: S, +} +impl<S, Fut> ClientBuilder<S, Fut> +where + S: Default + Send + Sync + Clone + Component + 'static, + Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, +{ + /// Start building a client that can join the world. + #[must_use] + pub fn new() -> Self { + Self { + // we create the app here so plugins can add onto it. + // the schedules won't run until [`Self::start`] is called. + app: init_ecs_app(), + + handler: None, + state: S::default(), + } + .add_plugins(DefaultBotPlugins) + } + /// Set the function that's called every time a bot receives an [`Event`]. + /// This is the way to handle normal per-bot events. + /// + /// You can only have one client handler, calling this again will replace + /// the old client handler function (you can have a client handler and swarm + /// handler separately though). + #[must_use] + pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self { + self.handler = Some(handler); + self + } + /// Add a plugin to the client. + #[must_use] + pub fn add_plugin<T: Plugin>(mut self, plugin: T) -> Self { + self.app.add_plugin(plugin); + self + } + /// Add a group of plugins to the client. + #[must_use] + pub fn add_plugins<T: PluginGroup>(mut self, plugin_group: T) -> Self { + self.app.add_plugins(plugin_group); + self + } + + /// Build this `ClientBuilder` into an actual [`Client`] and join the given + /// server. + /// + /// The `address` argument can be a `&str`, [`ServerAddress`], or anything + /// that implements `TryInto<ServerAddress>`. + /// + /// [`ServerAddress`]: azalea_protocol::ServerAddress + pub async fn start( + self, + account: Account, + address: impl TryInto<ServerAddress>, + ) -> Result<(), StartError> { + let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?; + let resolved_address = resolver::resolve_address(&address).await?; + + // An event that causes the schedule to run. This is only used internally. + let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1); + let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone()); + + let (bot, mut rx) = Client::start_client( + ecs_lock, + &account, + &address, + &resolved_address, + run_schedule_sender, + ) + .await?; + + while let Some(event) = rx.recv().await { + if let Some(handler) = self.handler { + tokio::spawn((handler)(bot.clone(), event.clone(), self.state.clone())); + } + } + + Ok(()) + } +} +impl<S, Fut> Default for ClientBuilder<S, Fut> +where + S: Default + Send + Sync + Clone + Component + 'static, + Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, +{ + fn default() -> Self { + Self::new() + } +} diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index de62e9a7..5246290d 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -1,147 +1,255 @@ mod moves; mod mtdstarlite; -use crate::{prelude::*, SprintDirection, WalkDirection}; -use crate::{Client, Event}; -use async_trait::async_trait; +use crate::bot::{JumpEvent, LookAtEvent}; +use crate::{SprintDirection, WalkDirection}; + +use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_core::{BlockPos, CardinalDirection}; -use azalea_world::entity::EntityData; +use azalea_ecs::{ + app::{App, Plugin}, + component::Component, + entity::Entity, + event::{EventReader, EventWriter}, + query::{With, Without}, + schedule::IntoSystemDescriptor, + system::{Commands, Query, Res}, + AppTickExt, +}; +use azalea_world::entity::metadata::Player; +use azalea_world::Local; +use azalea_world::{ + entity::{Physics, Position, WorldName}, + WorldContainer, +}; +use bevy_tasks::{AsyncComputeTaskPool, Task}; +use futures_lite::future; use log::{debug, error}; use mtdstarlite::Edge; pub use mtdstarlite::MTDStarLite; -use parking_lot::Mutex; use std::collections::VecDeque; use std::sync::Arc; #[derive(Clone, Default)] -pub struct Plugin; -impl crate::Plugin for Plugin { - type State = State; - - fn build(&self) -> State { - State::default() +pub struct PathfinderPlugin; +impl Plugin for PathfinderPlugin { + fn build(&self, app: &mut App) { + app.add_event::<GotoEvent>() + .add_event::<PathFoundEvent>() + .add_tick_system(tick_execute_path.before("walk_listener")) + .add_system(goto_listener) + .add_system(add_default_pathfinder.after("deduplicate_entities")) + .add_system(handle_tasks) + .add_system(path_found_listener); } } -#[derive(Default, Clone)] -pub struct State { - // pathfinder: Option<MTDStarLite<Node, f32>>, - pub path: Arc<Mutex<VecDeque<Node>>>, +/// A component that makes this entity able to pathfind. +#[derive(Component, Default)] +pub struct Pathfinder { + pub path: VecDeque<Node>, +} +#[allow(clippy::type_complexity)] +fn add_default_pathfinder( + mut commands: Commands, + mut query: Query<Entity, (Without<Pathfinder>, With<Local>, With<Player>)>, +) { + for entity in &mut query { + commands.entity(entity).insert(Pathfinder::default()); + } } -#[async_trait] -impl crate::PluginState for State { - async fn handle(self: Box<Self>, event: Event, mut bot: Client) { - if let Event::Tick = event { - let mut path = self.path.lock(); +pub trait PathfinderClientExt { + fn goto(&self, goal: impl Goal + Send + Sync + 'static); +} - if !path.is_empty() { - tick_execute_path(&mut bot, &mut path); - } - } +impl PathfinderClientExt for azalea_client::Client { + fn goto(&self, goal: impl Goal + Send + Sync + 'static) { + self.ecs.lock().send_event(GotoEvent { + entity: self.entity, + goal: Arc::new(goal), + }); } } - -pub trait Trait { - fn goto(&self, goal: impl Goal); +pub struct GotoEvent { + pub entity: Entity, + pub goal: Arc<dyn Goal + Send + Sync>, } +pub struct PathFoundEvent { + pub entity: Entity, + pub path: VecDeque<Node>, +} + +#[derive(Component)] +pub struct ComputePath(Task<Option<PathFoundEvent>>); -impl Trait for azalea_client::Client { - fn goto(&self, goal: impl Goal) { +fn goto_listener( + mut commands: Commands, + mut events: EventReader<GotoEvent>, + mut query: Query<(&Position, &WorldName)>, + world_container: Res<WorldContainer>, +) { + let thread_pool = AsyncComputeTaskPool::get(); + + for event in events.iter() { + let (position, world_name) = query + .get_mut(event.entity) + .expect("Called goto on an entity that's not in the world"); let start = Node { - pos: BlockPos::from(self.entity().pos()), + pos: BlockPos::from(position), vertical_vel: VerticalVel::None, }; - let end = goal.goal_node(); - debug!("start: {start:?}, end: {end:?}"); - - let possible_moves: Vec<&dyn moves::Move> = vec![ - &moves::ForwardMove(CardinalDirection::North), - &moves::ForwardMove(CardinalDirection::East), - &moves::ForwardMove(CardinalDirection::South), - &moves::ForwardMove(CardinalDirection::West), - // - &moves::AscendMove(CardinalDirection::North), - &moves::AscendMove(CardinalDirection::East), - &moves::AscendMove(CardinalDirection::South), - &moves::AscendMove(CardinalDirection::West), - // - &moves::DescendMove(CardinalDirection::North), - &moves::DescendMove(CardinalDirection::East), - &moves::DescendMove(CardinalDirection::South), - &moves::DescendMove(CardinalDirection::West), - // - &moves::DiagonalMove(CardinalDirection::North), - &moves::DiagonalMove(CardinalDirection::East), - &moves::DiagonalMove(CardinalDirection::South), - &moves::DiagonalMove(CardinalDirection::West), - ]; - - let successors = |node: &Node| { - let mut edges = Vec::new(); - - let world = &self.world.read().shared; - for possible_move in possible_moves.iter() { - edges.push(Edge { - target: possible_move.next_node(node), - cost: possible_move.cost(world, node), - }); + + let world_lock = world_container + .get(world_name) + .expect("Entity tried to pathfind but the entity isn't in a valid world"); + let end = event.goal.goal_node(); + + let goal = event.goal.clone(); + let entity = event.entity; + + let task = thread_pool.spawn(async move { + debug!("start: {start:?}, end: {end:?}"); + + let possible_moves: Vec<&dyn moves::Move> = vec![ + &moves::ForwardMove(CardinalDirection::North), + &moves::ForwardMove(CardinalDirection::East), + &moves::ForwardMove(CardinalDirection::South), + &moves::ForwardMove(CardinalDirection::West), + // + &moves::AscendMove(CardinalDirection::North), + &moves::AscendMove(CardinalDirection::East), + &moves::AscendMove(CardinalDirection::South), + &moves::AscendMove(CardinalDirection::West), + // + &moves::DescendMove(CardinalDirection::North), + &moves::DescendMove(CardinalDirection::East), + &moves::DescendMove(CardinalDirection::South), + &moves::DescendMove(CardinalDirection::West), + // + &moves::DiagonalMove(CardinalDirection::North), + &moves::DiagonalMove(CardinalDirection::East), + &moves::DiagonalMove(CardinalDirection::South), + &moves::DiagonalMove(CardinalDirection::West), + ]; + + let successors = |node: &Node| { + let mut edges = Vec::new(); + + let world = world_lock.read(); + for possible_move in &possible_moves { + edges.push(Edge { + target: possible_move.next_node(node), + cost: possible_move.cost(&world, node), + }); + } + edges + }; + + let mut pf = MTDStarLite::new( + start, + end, + |n| goal.heuristic(n), + successors, + successors, + |n| goal.success(n), + ); + + let start_time = std::time::Instant::now(); + let p = pf.find_path(); + let end_time = std::time::Instant::now(); + debug!("path: {p:?}"); + debug!("time: {:?}", end_time - start_time); + + // convert the Option<Vec<Node>> to a VecDeque<Node> + if let Some(p) = p { + let path = p.into_iter().collect::<VecDeque<_>>(); + // commands.entity(event.entity).insert(Pathfinder { path: p }); + Some(PathFoundEvent { entity, path }) + } else { + error!("no path found"); + None } - edges - }; + }); - let mut pf = MTDStarLite::new( - start, - end, - |n| goal.heuristic(n), - successors, - successors, - |n| goal.success(n), - ); - - let start = std::time::Instant::now(); - let p = pf.find_path(); - let end = std::time::Instant::now(); - debug!("path: {p:?}"); - debug!("time: {:?}", end - start); - - let state = self - .plugins - .get::<State>() - .expect("Pathfinder plugin not installed!") - .clone(); - // convert the Option<Vec<Node>> to a VecDeque<Node> - if let Some(p) = p { - *state.path.lock() = p.into_iter().collect(); - } else { - error!("no path found"); + commands.spawn(ComputePath(task)); + } +} + +// poll the tasks and send the PathFoundEvent if they're done +fn handle_tasks( + mut commands: Commands, + mut transform_tasks: Query<(Entity, &mut ComputePath)>, + mut path_found_events: EventWriter<PathFoundEvent>, +) { + for (entity, mut task) in &mut transform_tasks { + if let Some(optional_path_found_event) = future::block_on(future::poll_once(&mut task.0)) { + if let Some(path_found_event) = optional_path_found_event { + path_found_events.send(path_found_event); + } + + // Task is complete, so remove task component from entity + commands.entity(entity).remove::<ComputePath>(); } } } -fn tick_execute_path(bot: &mut Client, path: &mut VecDeque<Node>) { - let target = if let Some(target) = path.front() { - target - } else { - return; - }; - let center = target.pos.center(); - // println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos()); - bot.look_at(¢er); - bot.sprint(SprintDirection::Forward); - // check if we should jump - if target.pos.y > bot.entity().pos().y.floor() as i32 { - bot.jump(); +// set the path for the target entity when we get the PathFoundEvent +fn path_found_listener(mut events: EventReader<PathFoundEvent>, mut query: Query<&mut Pathfinder>) { + for event in events.iter() { + let mut pathfinder = query + .get_mut(event.entity) + .expect("Path found for an entity that doesn't have a pathfinder"); + pathfinder.path = event.path.clone(); } +} - if target.is_reached(&bot.entity()) { - // println!("ok target {target:?} reached"); - path.pop_front(); - if path.is_empty() { - bot.walk(WalkDirection::None); +fn tick_execute_path( + mut query: Query<(Entity, &mut Pathfinder, &Position, &Physics)>, + mut look_at_events: EventWriter<LookAtEvent>, + mut sprint_events: EventWriter<StartSprintEvent>, + mut walk_events: EventWriter<StartWalkEvent>, + mut jump_events: EventWriter<JumpEvent>, +) { + for (entity, mut pathfinder, position, physics) in &mut query { + loop { + let Some(target) = pathfinder.path.front() else { + break; + }; + let center = target.pos.center(); + // println!("going to {center:?} (at {pos:?})", pos = bot.entity().pos()); + look_at_events.send(LookAtEvent { + entity, + position: center, + }); + debug!( + "tick: pathfinder {entity:?}; going to {:?}; currently at {position:?}", + target.pos + ); + sprint_events.send(StartSprintEvent { + entity, + direction: SprintDirection::Forward, + }); + // check if we should jump + if target.pos.y > position.y.floor() as i32 { + jump_events.send(JumpEvent(entity)); + } + + if target.is_reached(position, physics) { + // println!("reached target"); + pathfinder.path.pop_front(); + if pathfinder.path.is_empty() { + // println!("reached goal"); + walk_events.send(StartWalkEvent { + entity, + direction: WalkDirection::None, + }); + } + // tick again, maybe we already reached the next node! + } else { + break; + } } - // tick again, maybe we already reached the next node! - tick_execute_path(bot, path); } } @@ -172,7 +280,8 @@ pub trait Goal { impl Node { /// Returns whether the entity is at the node and should start going to the /// next node. - pub fn is_reached(&self, entity: &EntityData) -> bool { + #[must_use] + pub fn is_reached(&self, position: &Position, physics: &Physics) -> bool { // println!( // "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}", // entity.delta.y, @@ -180,11 +289,11 @@ impl Node { // self.pos, // self.vertical_vel // ); - BlockPos::from(entity.pos()) == self.pos + BlockPos::from(position) == self.pos && match self.vertical_vel { - VerticalVel::NoneMidair => entity.delta.y > -0.1 && entity.delta.y < 0.1, - VerticalVel::None => entity.on_ground, - VerticalVel::FallingLittle => entity.delta.y < -0.1, + VerticalVel::NoneMidair => physics.delta.y > -0.1 && physics.delta.y < 0.1, + VerticalVel::None => physics.on_ground, + VerticalVel::FallingLittle => physics.delta.y < -0.1, } } } diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs index ccf8ba1a..9bb5c7c1 100644 --- a/azalea/src/pathfinder/moves.rs +++ b/azalea/src/pathfinder/moves.rs @@ -1,11 +1,11 @@ use super::{Node, VerticalVel}; use azalea_core::{BlockPos, CardinalDirection}; use azalea_physics::collision::{self, BlockWithShape}; -use azalea_world::WeakWorld; +use azalea_world::World; /// whether this block is passable -fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool { - if let Some(block) = world.get_block_state(pos) { +fn is_block_passable(pos: &BlockPos, world: &World) -> bool { + if let Some(block) = world.chunks.get_block_state(pos) { block.shape() == &collision::empty_shape() } else { false @@ -13,8 +13,8 @@ fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool { } /// whether this block has a solid hitbox (i.e. we can stand on it) -fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool { - if let Some(block) = world.get_block_state(pos) { +fn is_block_solid(pos: &BlockPos, world: &World) -> bool { + if let Some(block) = world.chunks.get_block_state(pos) { block.shape() == &collision::block_shape() } else { false @@ -22,22 +22,22 @@ fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool { } /// Whether this block and the block above are passable -fn is_passable(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_passable(pos: &BlockPos, world: &World) -> bool { is_block_passable(pos, world) && is_block_passable(&pos.up(1), world) } /// Whether we can stand in this position. Checks if the block below is solid, /// and that the two blocks above that are passable. -fn is_standable(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_standable(pos: &BlockPos, world: &World) -> bool { is_block_solid(&pos.down(1), world) && is_passable(pos, world) } const JUMP_COST: f32 = 0.5; const WALK_ONE_BLOCK_COST: f32 = 1.0; -pub trait Move { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32; +pub trait Move: Send + Sync { + fn cost(&self, world: &World, node: &Node) -> f32; /// Returns by how much the entity's position should be changed when this /// move is executed. fn offset(&self) -> BlockPos; @@ -51,7 +51,7 @@ pub trait Move { pub struct ForwardMove(pub CardinalDirection); impl Move for ForwardMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if is_standable(&(node.pos + self.offset()), world) && node.vertical_vel == VerticalVel::None { @@ -67,7 +67,7 @@ impl Move for ForwardMove { pub struct AscendMove(pub CardinalDirection); impl Move for AscendMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if node.vertical_vel == VerticalVel::None && is_block_passable(&node.pos.up(2), world) && is_standable(&(node.pos + self.offset()), world) @@ -89,7 +89,7 @@ impl Move for AscendMove { } pub struct DescendMove(pub CardinalDirection); impl Move for DescendMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { // check whether 3 blocks vertically forward are passable if node.vertical_vel == VerticalVel::None && is_standable(&(node.pos + self.offset()), world) @@ -112,7 +112,7 @@ impl Move for DescendMove { } pub struct DiagonalMove(pub CardinalDirection); impl Move for DiagonalMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if node.vertical_vel != VerticalVel::None { return f32::INFINITY; } @@ -151,56 +151,92 @@ mod tests { use super::*; use azalea_block::BlockState; use azalea_core::ChunkPos; - use azalea_world::{Chunk, PartialWorld}; + use azalea_world::{Chunk, ChunkStorage, PartialWorld}; #[test] fn test_is_passable() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone); - world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air); - - assert_eq!( - is_block_passable(&BlockPos::new(0, 0, 0), &world.shared), - false + let mut partial_world = PartialWorld::default(); + let mut chunk_storage = ChunkStorage::default(); + + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 0, 0), + BlockState::Stone, + &mut chunk_storage, ); - assert_eq!( - is_block_passable(&BlockPos::new(0, 1, 0), &world.shared), - true + partial_world.chunks.set_block_state( + &BlockPos::new(0, 1, 0), + BlockState::Air, + &mut chunk_storage, ); + + let world = chunk_storage.into(); + assert_eq!(is_block_passable(&BlockPos::new(0, 0, 0), &world), false); + assert_eq!(is_block_passable(&BlockPos::new(0, 1, 0), &world), true); } #[test] fn test_is_solid() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone); - world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air); - - assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world.shared), true); - assert_eq!( - is_block_solid(&BlockPos::new(0, 1, 0), &world.shared), - false + let mut partial_world = PartialWorld::default(); + let mut chunk_storage = ChunkStorage::default(); + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut chunk_storage, ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 0, 0), + BlockState::Stone, + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 1, 0), + BlockState::Air, + &mut chunk_storage, + ); + + let world = chunk_storage.into(); + assert_eq!(is_block_solid(&BlockPos::new(0, 0, 0), &world), true); + assert_eq!(is_block_solid(&BlockPos::new(0, 1, 0), &world), false); } #[test] fn test_is_standable() { - let mut world = PartialWorld::default(); - world - .set_chunk(&ChunkPos { x: 0, z: 0 }, Some(Chunk::default())) - .unwrap(); - world.set_block_state(&BlockPos::new(0, 0, 0), BlockState::Stone); - world.set_block_state(&BlockPos::new(0, 1, 0), BlockState::Air); - world.set_block_state(&BlockPos::new(0, 2, 0), BlockState::Air); - world.set_block_state(&BlockPos::new(0, 3, 0), BlockState::Air); - - assert!(is_standable(&BlockPos::new(0, 1, 0), &world.shared)); - assert!(!is_standable(&BlockPos::new(0, 0, 0), &world.shared)); - assert!(!is_standable(&BlockPos::new(0, 2, 0), &world.shared)); + let mut partial_world = PartialWorld::default(); + let mut chunk_storage = ChunkStorage::default(); + partial_world.chunks.set( + &ChunkPos { x: 0, z: 0 }, + Some(Chunk::default()), + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 0, 0), + BlockState::Stone, + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 1, 0), + BlockState::Air, + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 2, 0), + BlockState::Air, + &mut chunk_storage, + ); + partial_world.chunks.set_block_state( + &BlockPos::new(0, 3, 0), + BlockState::Air, + &mut chunk_storage, + ); + + let world = chunk_storage.into(); + assert!(is_standable(&BlockPos::new(0, 1, 0), &world)); + assert!(!is_standable(&BlockPos::new(0, 0, 0), &world)); + assert!(!is_standable(&BlockPos::new(0, 2, 0), &world)); } } diff --git a/azalea/src/pathfinder/mtdstarlite.rs b/azalea/src/pathfinder/mtdstarlite.rs index 50a467df..ce463279 100644 --- a/azalea/src/pathfinder/mtdstarlite.rs +++ b/azalea/src/pathfinder/mtdstarlite.rs @@ -3,9 +3,9 @@ //! //! Future optimization attempt ideas: //! - Use a different priority queue (e.g. fibonacci heap) -//! - Use FxHash instead of the default hasher +//! - Use `FxHash` instead of the default hasher //! - Have `par` be a raw pointer -//! - Try borrowing vs copying the Node in several places (like state_mut) +//! - Try borrowing vs copying the Node in several places (like `state_mut`) //! - Store edge costs in their own map use priority_queue::DoublePriorityQueue; @@ -260,9 +260,7 @@ impl< // identify a path from sstart to sgoal using the parent pointers let mut target = self.state(&self.goal).par; while !(Some(self.start) == target) { - let this_target = if let Some(this_target) = target { - this_target - } else { + let Some(this_target) = target else { break; }; // hunter follows path from start to goal; diff --git a/azalea/src/prelude.rs b/azalea/src/prelude.rs index bedf724f..3d8cc13e 100644 --- a/azalea/src/prelude.rs +++ b/azalea/src/prelude.rs @@ -1,7 +1,7 @@ //! The Azalea prelude. Things that are necessary for a bare-bones bot are //! re-exported here. -pub use crate::bot::BotTrait; -pub use crate::pathfinder::Trait; -pub use crate::{plugins, swarm_plugins, Plugin}; +pub use crate::bot::BotClientExt; +pub use crate::pathfinder::PathfinderClientExt; pub use azalea_client::{Account, Client, Event}; +pub use azalea_ecs::component::Component; diff --git a/azalea/src/start.rs b/azalea/src/start.rs deleted file mode 100644 index e2374fb8..00000000 --- a/azalea/src/start.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::{bot, pathfinder, HandleFn}; -use azalea_client::{Account, Client, Plugins}; -use azalea_protocol::ServerAddress; -use std::{future::Future, sync::Arc}; -use thiserror::Error; - -/// A helper macro that generates a [`Plugins`] struct from a list of objects -/// that implement [`Plugin`]. -/// -/// ```rust,no_run -/// plugins![azalea_pathfinder::Plugin]; -/// ``` -/// -/// [`Plugin`]: crate::Plugin -#[macro_export] -macro_rules! plugins { - ($($plugin:expr),*) => { - { - let mut plugins = azalea::Plugins::new(); - $( - plugins.add($plugin); - )* - plugins - } - }; -} - -/// The options that are passed to [`azalea::start`]. -/// -/// [`azalea::start`]: crate::start() -pub struct Options<S, A, Fut> -where - A: TryInto<ServerAddress>, - Fut: Future<Output = Result<(), anyhow::Error>>, -{ - /// The address of the server that we're connecting to. This can be a - /// `&str`, [`ServerAddress`], or anything that implements - /// `TryInto<ServerAddress>`. - /// - /// [`ServerAddress`]: azalea_protocol::ServerAddress - pub address: A, - /// The account that's going to join the server. - pub account: Account, - /// The plugins that are going to be used. Plugins are external crates that - /// add extra functionality to Azalea. You should use the [`plugins`] macro - /// for this field. - /// - /// ```rust,no_run - /// plugins![azalea_pathfinder::Plugin] - /// ``` - pub plugins: Plugins, - /// A struct that contains the data that you want your bot to remember - /// across events. - /// - /// # Examples - /// - /// ```rust - /// use parking_lot::Mutex; - /// use std::sync::Arc; - /// - /// #[derive(Default, Clone)] - /// struct State { - /// farming: Arc<Mutex<bool>>, - /// } - /// ``` - pub state: S, - /// The function that's called whenever we get an event. - /// - /// # Examples - /// - /// ```rust - /// use azalea::prelude::*; - /// - /// async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { - /// Ok(()) - /// } - /// ``` - pub handle: HandleFn<Fut, S>, -} - -#[derive(Error, Debug)] -pub enum StartError { - #[error("Invalid address")] - InvalidAddress, - #[error("Join error: {0}")] - Join(#[from] azalea_client::JoinError), -} - -/// Join a server and start handling events. This function will run forever -/// until it gets disconnected from the server. -/// -/// # Examples -/// -/// ```rust,no_run -/// let error = azalea::start(azalea::Options { -/// account, -/// address: "localhost", -/// state: State::default(), -/// plugins: plugins![azalea_pathfinder::Plugin], -/// handle, -/// }).await; -/// ``` -pub async fn start< - S: Send + Sync + Clone + 'static, - A: Send + TryInto<ServerAddress>, - Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, ->( - options: Options<S, A, Fut>, -) -> Result<(), StartError> { - let address = match options.address.try_into() { - Ok(address) => address, - Err(_) => return Err(StartError::InvalidAddress), - }; - - let (mut bot, mut rx) = Client::join(&options.account, address).await?; - - let mut plugins = options.plugins; - // DEFAULT PLUGINS - plugins.add(bot::Plugin); - plugins.add(pathfinder::Plugin); - - bot.plugins = Arc::new(plugins.build()); - - let state = options.state; - - while let Some(event) = rx.recv().await { - let cloned_plugins = (*bot.plugins).clone(); - for plugin in cloned_plugins.into_iter() { - tokio::spawn(plugin.handle(event.clone(), bot.clone())); - } - - tokio::spawn((options.handle)(bot.clone(), event.clone(), state.clone())); - } - - Ok(()) -} diff --git a/azalea/src/swarm/chat.rs b/azalea/src/swarm/chat.rs index 4582c59e..a55e9bf6 100644 --- a/azalea/src/swarm/chat.rs +++ b/azalea/src/swarm/chat.rs @@ -1,4 +1,4 @@ -//! Implements SwarmEvent::Chat +//! Implements `SwarmEvent::Chat`. // How the chat event works (to avoid firing the event multiple times): // --- @@ -13,71 +13,81 @@ // in Swarm that's set to the smallest index of all the bots, and we remove all // messages from the queue that are before that index. +use azalea_client::{packet_handling::ChatReceivedEvent, ChatPacket}; +use azalea_ecs::{ + app::{App, Plugin}, + component::Component, + event::{EventReader, EventWriter}, + schedule::IntoSystemDescriptor, + system::{Commands, Query, Res, ResMut, Resource}, +}; +use std::collections::VecDeque; + use crate::{Swarm, SwarmEvent}; -use async_trait::async_trait; -use azalea_client::{ChatPacket, Client, Event}; -use parking_lot::Mutex; -use std::{collections::VecDeque, sync::Arc}; -use tokio::sync::broadcast::{Receiver, Sender}; #[derive(Clone)] -pub struct Plugin { - pub swarm_state: SwarmState, - pub tx: Sender<ChatPacket>, -} - -impl crate::Plugin for Plugin { - type State = State; - - fn build(&self) -> State { - State { - chat_index: Arc::new(Mutex::new(0)), - swarm_state: self.swarm_state.clone(), - tx: self.tx.clone(), - } +pub struct SwarmChatPlugin; +impl Plugin for SwarmChatPlugin { + fn build(&self, app: &mut App) { + app.add_event::<NewChatMessageEvent>() + .add_system(chat_listener.label("chat_listener")) + .add_system(update_min_index_and_shrink_queue.after("chat_listener")) + .insert_resource(GlobalChatState { + chat_queue: VecDeque::new(), + chat_min_index: 0, + }); } } -#[derive(Clone)] -pub struct State { - pub chat_index: Arc<Mutex<usize>>, - pub tx: Sender<ChatPacket>, - pub swarm_state: SwarmState, +#[derive(Component, Debug)] +pub struct ClientChatState { + pub chat_index: usize, } -#[derive(Clone)] -pub struct SwarmState { - pub chat_queue: Arc<Mutex<VecDeque<ChatPacket>>>, - pub chat_min_index: Arc<Mutex<usize>>, - pub rx: Arc<tokio::sync::Mutex<Receiver<ChatPacket>>>, +/// A chat message that no other bots have seen yet was received by a bot. +#[derive(Debug)] +pub struct NewChatMessageEvent(ChatPacket); + +#[derive(Resource)] +pub struct GlobalChatState { + pub chat_queue: VecDeque<ChatPacket>, + pub chat_min_index: usize, } -impl State { - pub fn handle_chat(&self, message: ChatPacket) { +fn chat_listener( + mut commands: Commands, + mut query: Query<&mut ClientChatState>, + mut events: EventReader<ChatReceivedEvent>, + mut global_chat_state: ResMut<GlobalChatState>, + mut new_chat_messages_events: EventWriter<NewChatMessageEvent>, +) { + for event in events.iter() { + let mut client_chat_state = query.get_mut(event.entity); + let mut client_chat_index = if let Ok(ref client_chat_state) = client_chat_state { + client_chat_state.chat_index + } else { + // if the client has no chat state, we default to this and insert it at the end + global_chat_state.chat_min_index + }; + // When a bot receives a chat messages, it looks into the queue to find the // earliest instance of the message content that's after the bot's chat index. // If it finds it, then its personal index is simply updated. Otherwise, fire // the event and add to the queue. - let mut chat_queue = self.swarm_state.chat_queue.lock(); - let chat_min_index = self.swarm_state.chat_min_index.lock(); - let mut chat_index = self.chat_index.lock(); - - if *chat_min_index > *chat_index { - // if this happens it's because this bot just logged in, so - // ignore it and let another bot handle it - println!("chat_min_index ({chat_min_index}) > chat_index ({chat_index})"); - *chat_index = *chat_min_index; - return; - } - let actual_vec_index = *chat_index - *chat_min_index; + let actual_vec_index = client_chat_index - global_chat_state.chat_min_index; // go through the queue and find the first message that's after the bot's index let mut found = false; - for (i, past_message) in chat_queue.iter().enumerate().skip(actual_vec_index) { - if past_message == &message { + for (i, past_message) in global_chat_state + .chat_queue + .iter() + .enumerate() + .skip(actual_vec_index) + { + if past_message == &event.packet { // found the message, update the index - *chat_index = i + *chat_min_index + 1; + client_chat_index = i + global_chat_state.chat_min_index + 1; found = true; break; } @@ -85,185 +95,157 @@ impl State { if !found { // didn't find the message, so fire the swarm event and add to the queue - self.tx - .send(message.clone()) - .expect("failed to send chat message to swarm"); - chat_queue.push_back(message); - *chat_index = chat_queue.len() + *chat_min_index; + new_chat_messages_events.send(NewChatMessageEvent(event.packet.clone())); + global_chat_state.chat_queue.push_back(event.packet.clone()); + client_chat_index = + global_chat_state.chat_queue.len() + global_chat_state.chat_min_index; } - } -} - -#[async_trait] -impl crate::PluginState for State { - async fn handle(self: Box<Self>, event: Event, _bot: Client) { - // we're allowed to access Plugin::swarm_state since it's shared for every bot - if let Event::Chat(m) = event { - self.handle_chat(m); + if let Ok(ref mut client_chat_state) = client_chat_state { + client_chat_state.chat_index = client_chat_index; + } else { + commands.entity(event.entity).insert(ClientChatState { + chat_index: client_chat_index, + }); } } } -impl SwarmState { - pub fn new<S>(swarm: Swarm<S>) -> (Self, Sender<ChatPacket>) - where - S: Send + Sync + Clone + 'static, - { - let (tx, rx) = tokio::sync::broadcast::channel(1); - - let swarm_state = SwarmState { - chat_queue: Arc::new(Mutex::new(VecDeque::new())), - chat_min_index: Arc::new(Mutex::new(0)), - rx: Arc::new(tokio::sync::Mutex::new(rx)), - }; - tokio::spawn(swarm_state.clone().start(swarm)); - - (swarm_state, tx) - } - async fn start<S>(self, swarm: Swarm<S>) - where - S: Send + Sync + Clone + 'static, - { - // it should never be locked unless we reused the same plugin for two swarms - // (bad) - let mut rx = self.rx.lock().await; - while let Ok(m) = rx.recv().await { - swarm.swarm_tx.send(SwarmEvent::Chat(m)).unwrap(); - let bot_states = swarm - .bot_datas - .lock() - .iter() - .map(|(bot, _)| { - bot.plugins - .get::<State>() - .expect("Chat plugin not installed") - .clone() - }) - .collect::<Vec<_>>(); - self.handle_new_chat_message(&bot_states); +fn update_min_index_and_shrink_queue( + query: Query<&ClientChatState>, + mut global_chat_state: ResMut<GlobalChatState>, + mut events: EventReader<NewChatMessageEvent>, + swarm: Option<Res<Swarm>>, +) { + for event in events.iter() { + if let Some(swarm) = &swarm { + // it should also work if Swarm isn't present (so the tests don't need it) + swarm + .swarm_tx + .send(SwarmEvent::Chat(event.0.clone())) + .unwrap(); } - } -} - -impl SwarmState { - pub fn handle_new_chat_message(&self, bot_states: &[State]) { // To make sure the queue doesn't grow too large, we keep a `chat_min_index` // in Swarm that's set to the smallest index of all the bots, and we remove all // messages from the queue that are before that index. - let chat_min_index = *self.chat_min_index.lock(); - let mut new_chat_min_index = usize::MAX; - for bot_state in bot_states { - let this_chat_index = *bot_state.chat_index.lock(); + let mut new_chat_min_index = global_chat_state.chat_min_index; + for client_chat_state in query.iter() { + let this_chat_index = client_chat_state.chat_index; if this_chat_index < new_chat_min_index { new_chat_min_index = this_chat_index; } } - let mut chat_queue = self.chat_queue.lock(); - if chat_min_index > new_chat_min_index { - println!( - "chat_min_index ({chat_min_index}) > new_chat_min_index ({new_chat_min_index})" - ); + if global_chat_state.chat_min_index > new_chat_min_index { return; } // remove all messages from the queue that are before the min index - for _ in 0..(new_chat_min_index - chat_min_index) { - chat_queue.pop_front(); + for _ in 0..(new_chat_min_index - global_chat_state.chat_min_index) { + global_chat_state.chat_queue.pop_front(); } // update the min index - *self.chat_min_index.lock() = new_chat_min_index; + global_chat_state.chat_min_index = new_chat_min_index; } } #[cfg(test)] mod tests { + use azalea_ecs::{ecs::Ecs, event::Events, system::SystemState}; + use super::*; + fn make_test_app() -> App { + let mut app = App::new(); + // we add the events like this instead of with .add_event so we can have our own + // event mangement in drain_events + app.init_resource::<Events<ChatReceivedEvent>>() + .init_resource::<Events<NewChatMessageEvent>>() + .add_system(chat_listener.label("chat_listener")) + .add_system(update_min_index_and_shrink_queue.after("chat_listener")) + .insert_resource(GlobalChatState { + chat_queue: VecDeque::new(), + chat_min_index: 0, + }); + app + } + + fn drain_events(ecs: &mut Ecs) -> Vec<ChatPacket> { + let mut system_state: SystemState<ResMut<Events<NewChatMessageEvent>>> = + SystemState::new(ecs); + let mut events = system_state.get_mut(ecs); + + events.drain().map(|e| e.0.clone()).collect::<Vec<_>>() + } + #[tokio::test] async fn test_swarm_chat() { - let (tx, mut rx) = tokio::sync::broadcast::channel(1); - let swarm_state = SwarmState { - chat_queue: Arc::new(Mutex::new(VecDeque::new())), - chat_min_index: Arc::new(Mutex::new(0)), - rx: Arc::new(tokio::sync::Mutex::new(rx)), - }; - let mut bot_states = vec![]; - let bot0 = State { - swarm_state: swarm_state.clone(), - chat_index: Arc::new(Mutex::new(0)), - tx: tx.clone(), - }; - let bot1 = State { - swarm_state: swarm_state.clone(), - chat_index: Arc::new(Mutex::new(0)), - tx: tx.clone(), - }; - bot_states.push(bot0.clone()); - bot_states.push(bot1.clone()); + let mut app = make_test_app(); + + let bot0 = app.world.spawn_empty().id(); + let bot1 = app.world.spawn_empty().id(); + + app.world.send_event(ChatReceivedEvent { + entity: bot0, + packet: ChatPacket::new("a"), + }); + app.update(); - bot0.handle_chat(ChatPacket::new("a")); // the swarm should get the event immediately after the bot gets it + assert_eq!(drain_events(&mut app.world), vec![ChatPacket::new("a")]); assert_eq!( - swarm_state.rx.lock().await.try_recv(), - Ok(ChatPacket::new("a")) + app.world.get::<ClientChatState>(bot0).unwrap().chat_index, + 1 ); - assert_eq!(*bot0.chat_index.lock(), 1); // and a second bot sending the event shouldn't do anything - bot1.handle_chat(ChatPacket::new("a")); - assert!(swarm_state.rx.lock().await.try_recv().is_err()); - assert_eq!(*bot1.chat_index.lock(), 1); - - // but if the first one gets it again, it should sent it again - bot0.handle_chat(ChatPacket::new("a")); + app.world.send_event(ChatReceivedEvent { + entity: bot1, + packet: ChatPacket::new("a"), + }); + app.update(); + assert_eq!(drain_events(&mut app.world), vec![]); assert_eq!( - swarm_state.rx.lock().await.try_recv(), - Ok(ChatPacket::new("a")) + app.world.get::<ClientChatState>(bot1).unwrap().chat_index, + 1 ); + // but if the first one gets it again, it should sent it again + app.world.send_event(ChatReceivedEvent { + entity: bot0, + packet: ChatPacket::new("a"), + }); + app.update(); + assert_eq!(drain_events(&mut app.world), vec![ChatPacket::new("a")]); + // alright and now the second bot got a different chat message and it should be // sent - bot1.handle_chat(ChatPacket::new("b")); - assert_eq!( - swarm_state.rx.lock().await.try_recv(), - Ok(ChatPacket::new("b")) - ); + app.world.send_event(ChatReceivedEvent { + entity: bot1, + packet: ChatPacket::new("b"), + }); + app.update(); + assert_eq!(drain_events(&mut app.world), vec![ChatPacket::new("b")]); } #[tokio::test] async fn test_new_bot() { - let (tx, mut rx) = tokio::sync::broadcast::channel(1); - let swarm_state = SwarmState { - chat_queue: Arc::new(Mutex::new(VecDeque::new())), - chat_min_index: Arc::new(Mutex::new(0)), - rx: Arc::new(tokio::sync::Mutex::new(rx)), - }; - let mut bot_states = vec![]; - let bot0 = State { - swarm_state: swarm_state.clone(), - chat_index: Arc::new(Mutex::new(0)), - tx: tx.clone(), - }; - bot_states.push(bot0.clone()); + let mut app = make_test_app(); + + let bot0 = app.world.spawn_empty().id(); // bot0 gets a chat message - bot0.handle_chat(ChatPacket::new("a")); - assert_eq!( - swarm_state.rx.lock().await.try_recv(), - Ok(ChatPacket::new("a")) - ); - // now a second bot joined and got a different chat message - let bot1 = State { - swarm_state: swarm_state.clone(), - chat_index: Arc::new(Mutex::new(0)), - tx: tx.clone(), - }; - bot_states.push(bot1.clone()); - bot1.handle_chat(ChatPacket::new("b")); - assert_eq!( - swarm_state.rx.lock().await.try_recv(), - Ok(ChatPacket::new("b")) - ); + app.world.send_event(ChatReceivedEvent { + entity: bot0, + packet: ChatPacket::new("a"), + }); + app.update(); + assert_eq!(drain_events(&mut app.world), vec![ChatPacket::new("a")]); + let bot1 = app.world.spawn_empty().id(); + app.world.send_event(ChatReceivedEvent { + entity: bot1, + packet: ChatPacket::new("b"), + }); + app.update(); + assert_eq!(drain_events(&mut app.world), vec![ChatPacket::new("b")]); } } diff --git a/azalea/src/swarm/events.rs b/azalea/src/swarm/events.rs new file mode 100644 index 00000000..81d8c731 --- /dev/null +++ b/azalea/src/swarm/events.rs @@ -0,0 +1,45 @@ +use azalea_client::LocalPlayer; +use azalea_ecs::{ + app::{App, Plugin}, + event::EventWriter, + query::With, + system::{Query, ResMut, Resource}, +}; +use azalea_world::entity::MinecraftEntityId; +use derive_more::{Deref, DerefMut}; + +pub struct SwarmPlugin; +impl Plugin for SwarmPlugin { + fn build(&self, app: &mut App) { + app.add_event::<SwarmReadyEvent>() + .add_system(check_ready) + .init_resource::<IsSwarmReady>(); + } +} + +/// All the bots from the swarm are now in the world. +pub struct SwarmReadyEvent; + +#[derive(Default, Resource, Deref, DerefMut)] +struct IsSwarmReady(bool); + +fn check_ready( + query: Query<Option<&MinecraftEntityId>, With<LocalPlayer>>, + mut is_swarm_ready: ResMut<IsSwarmReady>, + mut ready_events: EventWriter<SwarmReadyEvent>, +) { + // if we already know the swarm is ready, do nothing + if **is_swarm_ready { + return; + } + // if all the players are in the world, we're ready + for entity_id in query.iter() { + if entity_id.is_none() { + return; + } + } + + // all the players are in the world, so we're ready + **is_swarm_ready = true; + ready_events.send(SwarmReadyEvent); +} diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs index 6fc3e40c..9c16bc01 100644 --- a/azalea/src/swarm/mod.rs +++ b/azalea/src/swarm/mod.rs @@ -1,41 +1,29 @@ -/// Swarms are a way to conveniently control many bots. -mod chat; -mod plugins; +//! Swarms are a way to conveniently control many bots. -pub use self::plugins::*; -use crate::{bot, HandleFn}; -use azalea_client::{Account, ChatPacket, Client, Event, JoinError, Plugins}; +mod chat; +mod events; + +use crate::{bot::DefaultBotPlugins, HandleFn}; +use azalea_client::{init_ecs_app, start_ecs, Account, ChatPacket, Client, Event, JoinError}; +use azalea_ecs::{ + app::{App, Plugin, PluginGroup, PluginGroupBuilder}, + component::Component, + ecs::Ecs, + entity::Entity, + system::Resource, +}; use azalea_protocol::{ - connect::{Connection, ConnectionError}, + connect::ConnectionError, resolver::{self, ResolverError}, ServerAddress, }; -use azalea_world::WeakWorldContainer; +use azalea_world::WorldContainer; use futures::future::join_all; use log::error; use parking_lot::{Mutex, RwLock}; -use std::{future::Future, net::SocketAddr, sync::Arc, time::Duration}; +use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time::Duration}; use thiserror::Error; -use tokio::sync::mpsc::{self, UnboundedSender}; - -/// A helper macro that generates a [`SwarmPlugins`] struct from a list of -/// objects that implement [`SwarmPlugin`]. -/// -/// ```rust,no_run -/// swarm_plugins![azalea_pathfinder::Plugin]; -/// ``` -#[macro_export] -macro_rules! swarm_plugins { - ($($plugin:expr),*) => { - { - let mut plugins = azalea::SwarmPlugins::new(); - $( - plugins.add($plugin); - )* - plugins - } - }; -} +use tokio::sync::mpsc; /// A swarm is a way to conveniently control many bots at once, while also /// being able to control bots at an individual level when desired. @@ -46,82 +34,295 @@ macro_rules! swarm_plugins { /// It's used to make the [`Swarm::add`] function work. /// /// [`azalea::start_swarm`]: fn.start_swarm.html -#[derive(Clone)] -pub struct Swarm<S> { - bot_datas: Arc<Mutex<Vec<(Client, S)>>>, +#[derive(Clone, Resource)] +pub struct Swarm { + pub ecs_lock: Arc<Mutex<Ecs>>, + bots: Arc<Mutex<HashMap<Entity, Client>>>, + + // bot_datas: Arc<Mutex<Vec<(Client, S)>>>, resolved_address: SocketAddr, address: ServerAddress, - pub worlds: Arc<RwLock<WeakWorldContainer>>, - /// Plugins that are set for new bots - plugins: Plugins, + pub world_container: Arc<RwLock<WorldContainer>>, - bots_tx: UnboundedSender<(Option<Event>, (Client, S))>, - swarm_tx: UnboundedSender<SwarmEvent>, -} + bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>, + swarm_tx: mpsc::UnboundedSender<SwarmEvent>, -/// An event about something that doesn't have to do with a single bot. -#[derive(Clone, Debug)] -pub enum SwarmEvent { - /// All the bots in the swarm have successfully joined the server. - Login, - /// The swarm was created. This is only fired once, and it's guaranteed to - /// be the first event to fire. - Init, - /// A bot got disconnected from the server. - /// - /// You can implement an auto-reconnect by calling [`Swarm::add`] - /// with the account from this event. - Disconnect(Account), - /// At least one bot received a chat message. - Chat(ChatPacket), + run_schedule_sender: mpsc::Sender<()>, } -pub type SwarmHandleFn<Fut, S, SS> = fn(Swarm<S>, SwarmEvent, SS) -> Fut; - -/// The options that are passed to [`azalea::start_swarm`]. -/// -/// [`azalea::start_swarm`]: crate::start_swarm() -pub struct SwarmOptions<S, SS, A, Fut, SwarmFut> +/// Create a new [`Swarm`]. +pub struct SwarmBuilder<S, SS, Fut, SwarmFut> where - A: TryInto<ServerAddress>, + S: Default + Send + Sync + Clone + 'static, + SS: Default + Send + Sync + Clone + 'static, Fut: Future<Output = Result<(), anyhow::Error>>, SwarmFut: Future<Output = Result<(), anyhow::Error>>, { - /// The address of the server that we're connecting to. This can be a - /// `&str`, [`ServerAddress`], or anything that implements - /// `TryInto<ServerAddress>`. - /// - /// [`ServerAddress`]: azalea_protocol::ServerAddress - pub address: A, + app: App, /// The accounts that are going to join the server. - pub accounts: Vec<Account>, - /// The plugins that are going to be used for all the bots. - /// - /// You can usually leave this as `plugins![]`. - pub plugins: Plugins, - /// The plugins that are going to be used for the swarm. - /// - /// You can usually leave this as `swarm_plugins![]`. - pub swarm_plugins: SwarmPlugins<S>, + accounts: Vec<Account>, /// The individual bot states. This must be the same length as `accounts`, /// since each bot gets one state. - pub states: Vec<S>, + states: Vec<S>, /// The state for the overall swarm. - pub swarm_state: SS, + swarm_state: SS, /// The function that's called every time a bot receives an [`Event`]. - pub handle: HandleFn<Fut, S>, + handler: Option<HandleFn<Fut, S>>, /// The function that's called every time the swarm receives a /// [`SwarmEvent`]. - pub swarm_handle: SwarmHandleFn<SwarmFut, S, SS>, + swarm_handler: Option<SwarmHandleFn<SwarmFut, SS>>, /// How long we should wait between each bot joining the server. Set to /// None to have every bot connect at the same time. None is different than /// a duration of 0, since if a duration is present the bots will wait for /// the previous one to be ready. - pub join_delay: Option<std::time::Duration>, + join_delay: Option<std::time::Duration>, +} +impl<S, SS, Fut, SwarmFut> SwarmBuilder<S, SS, Fut, SwarmFut> +where + Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, + SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, + S: Default + Send + Sync + Clone + Component + 'static, + SS: Default + Send + Sync + Clone + Component + 'static, +{ + /// Start creating the swarm. + #[must_use] + pub fn new() -> Self { + Self { + // we create the app here so plugins can add onto it. + // the schedules won't run until [`Self::start`] is called. + app: init_ecs_app(), + + accounts: Vec::new(), + states: Vec::new(), + swarm_state: SS::default(), + handler: None, + swarm_handler: None, + join_delay: None, + } + .add_plugins(DefaultSwarmPlugins) + .add_plugins(DefaultBotPlugins) + } + + /// Add a vec of [`Account`]s to the swarm. + /// + /// Use [`Self::add_account`] to only add one account. If you want the + /// clients to have different default states, add them one at a time with + /// [`Self::add_account_with_state`]. + #[must_use] + pub fn add_accounts(mut self, accounts: Vec<Account>) -> Self { + for account in accounts { + self = self.add_account(account); + } + self + } + /// Add a single new [`Account`] to the swarm. Use [`Self::add_accounts`] to + /// add multiple accounts at a time. + /// + /// This will make the state for this client be the default, use + /// [`Self::add_account_with_state`] to avoid that. + #[must_use] + pub fn add_account(self, account: Account) -> Self { + self.add_account_with_state(account, S::default()) + } + /// Add an account with a custom initial state. Use just + /// [`Self::add_account`] to use the Default implementation for the state. + #[must_use] + pub fn add_account_with_state(mut self, account: Account, state: S) -> Self { + self.accounts.push(account); + self.states.push(state); + self + } + + /// Set the function that's called every time a bot receives an [`Event`]. + /// This is the way to handle normal per-bot events. + /// + /// You can only have one client handler, calling this again will replace + /// the old client handler function (you can have a client handler and swarm + /// handler separately though). + #[must_use] + pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self { + self.handler = Some(handler); + self + } + /// Set the function that's called every time the swarm receives a + /// [`SwarmEvent`]. This is the way to handle global swarm events. + /// + /// You can only have one swarm handler, calling this again will replace + /// the old swarm handler function (you can have a client handler and swarm + /// handler separately though). + #[must_use] + pub fn set_swarm_handler(mut self, handler: SwarmHandleFn<SwarmFut, SS>) -> Self { + self.swarm_handler = Some(handler); + self + } + + /// Add a plugin to the swarm. + #[must_use] + pub fn add_plugin<T: Plugin>(mut self, plugin: T) -> Self { + self.app.add_plugin(plugin); + self + } + /// Add a group of plugins to the swarm. + #[must_use] + pub fn add_plugins<T: PluginGroup>(mut self, plugin_group: T) -> Self { + self.app.add_plugins(plugin_group); + self + } + + /// Set how long we should wait between each bot joining the server. + /// + /// By default, every bot will connect at the same time. If you set this + /// field, however, the bots will wait for the previous one to have + /// connected and *then* they'll wait the given duration. + #[must_use] + pub fn join_delay(mut self, delay: std::time::Duration) -> Self { + self.join_delay = Some(delay); + self + } + + /// Build this `SwarmBuilder` into an actual [`Swarm`] and join the given + /// server. + /// + /// The `address` argument can be a `&str`, [`ServerAddress`], or anything + /// that implements `TryInto<ServerAddress>`. + /// + /// [`ServerAddress`]: azalea_protocol::ServerAddress + pub async fn start(self, address: impl TryInto<ServerAddress>) -> Result<(), SwarmStartError> { + assert_eq!( + self.accounts.len(), + self.states.len(), + "There must be exactly one state per bot." + ); + + // convert the TryInto<ServerAddress> into a ServerAddress + let address: ServerAddress = match address.try_into() { + Ok(address) => address, + Err(_) => return Err(SwarmStartError::InvalidAddress), + }; + + // resolve the address + let resolved_address = resolver::resolve_address(&address).await?; + + let world_container = Arc::new(RwLock::new(WorldContainer::default())); + + // we can't modify the swarm plugins after this + let (bots_tx, mut bots_rx) = mpsc::unbounded_channel(); + let (swarm_tx, mut swarm_rx) = mpsc::unbounded_channel(); + + let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1); + let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone()); + + let swarm = Swarm { + ecs_lock: ecs_lock.clone(), + bots: Arc::new(Mutex::new(HashMap::new())), + + resolved_address, + address, + world_container, + + bots_tx, + + swarm_tx: swarm_tx.clone(), + + run_schedule_sender, + }; + ecs_lock.lock().insert_resource(swarm.clone()); + + // SwarmBuilder (self) isn't Send so we have to take all the things we need out + // of it + let mut swarm_clone = swarm.clone(); + let join_delay = self.join_delay; + let accounts = self.accounts.clone(); + let states = self.states.clone(); + + let join_task = tokio::spawn(async move { + if let Some(join_delay) = join_delay { + // if there's a join delay, then join one by one + for (account, state) in accounts.iter().zip(states) { + swarm_clone + .add_with_exponential_backoff(account, state.clone()) + .await; + tokio::time::sleep(join_delay).await; + } + } else { + // otherwise, join all at once + let swarm_borrow = &swarm_clone; + join_all(accounts.iter().zip(states).map( + async move |(account, state)| -> Result<(), JoinError> { + swarm_borrow + .clone() + .add_with_exponential_backoff(account, state.clone()) + .await; + Ok(()) + }, + )) + .await; + } + }); + + let swarm_state = self.swarm_state; + + // Watch swarm_rx and send those events to the swarm_handle. + let swarm_clone = swarm.clone(); + tokio::spawn(async move { + while let Some(event) = swarm_rx.recv().await { + if let Some(swarm_handler) = self.swarm_handler { + tokio::spawn((swarm_handler)( + swarm_clone.clone(), + event, + swarm_state.clone(), + )); + } + } + }); + + // bot events + while let Some((Some(event), bot)) = bots_rx.recv().await { + if let Some(handler) = self.handler { + let state = bot.component::<S>(); + tokio::spawn((handler)(bot, event, state)); + } + } + + join_task.abort(); + + Ok(()) + } +} + +impl<S, SS, Fut, SwarmFut> Default for SwarmBuilder<S, SS, Fut, SwarmFut> +where + Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, + SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, + S: Default + Send + Sync + Clone + Component + 'static, + SS: Default + Send + Sync + Clone + Component + 'static, +{ + fn default() -> Self { + Self::new() + } +} + +/// An event about something that doesn't have to do with a single bot. +#[derive(Clone, Debug)] +pub enum SwarmEvent { + /// All the bots in the swarm have successfully joined the server. + Login, + /// The swarm was created. This is only fired once, and it's guaranteed to + /// be the first event to fire. + Init, + /// A bot got disconnected from the server. + /// + /// You can implement an auto-reconnect by calling [`Swarm::add`] + /// with the account from this event. + Disconnect(Account), + /// At least one bot received a chat message. + Chat(ChatPacket), } +pub type SwarmHandleFn<Fut, SS> = fn(Swarm, SwarmEvent, SS) -> Fut; + #[derive(Error, Debug)] pub enum SwarmStartError { #[error("Invalid address")] @@ -154,7 +355,7 @@ pub enum SwarmStartError { /// let mut states = Vec::new(); /// /// for i in 0..10 { -/// accounts.push(Account::offline(&format!("bot{}", i))); +/// accounts.push(Account::offline(&format!("bot{i}"))); /// states.push(State::default()); /// } /// @@ -204,193 +405,69 @@ pub enum SwarmStartError { /// } /// Ok(()) /// } -pub async fn start_swarm< - S: Send + Sync + Clone + 'static, - SS: Send + Sync + Clone + 'static, - A: Send + TryInto<ServerAddress>, - Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, - SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, ->( - options: SwarmOptions<S, SS, A, Fut, SwarmFut>, -) -> Result<(), SwarmStartError> { - assert_eq!( - options.accounts.len(), - options.states.len(), - "There must be exactly one state per bot." - ); - - // convert the TryInto<ServerAddress> into a ServerAddress - let address: ServerAddress = match options.address.try_into() { - Ok(address) => address, - Err(_) => return Err(SwarmStartError::InvalidAddress), - }; - - // resolve the address - let resolved_address = resolver::resolve_address(&address).await?; - - let world_container = Arc::new(RwLock::new(WeakWorldContainer::default())); - - let mut plugins = options.plugins; - let swarm_plugins = options.swarm_plugins; - - // DEFAULT CLIENT PLUGINS - plugins.add(bot::Plugin); - plugins.add(crate::pathfinder::Plugin); - // DEFAULT SWARM PLUGINS - - // we can't modify the swarm plugins after this - let (bots_tx, mut bots_rx) = mpsc::unbounded_channel(); - let (swarm_tx, mut swarm_rx) = mpsc::unbounded_channel(); - - let mut swarm = Swarm { - bot_datas: Arc::new(Mutex::new(Vec::new())), - - resolved_address, - address, - worlds: world_container, - plugins, - - bots_tx, - - swarm_tx: swarm_tx.clone(), - }; - - { - // the chat plugin is hacky and needs the swarm to be passed like this - let (chat_swarm_state, chat_tx) = chat::SwarmState::new(swarm.clone()); - swarm.plugins.add(chat::Plugin { - swarm_state: chat_swarm_state, - tx: chat_tx, - }); - } - - let swarm_plugins = swarm_plugins.build(); - - let mut swarm_clone = swarm.clone(); - let join_task = tokio::spawn(async move { - if let Some(join_delay) = options.join_delay { - // if there's a join delay, then join one by one - for (account, state) in options.accounts.iter().zip(options.states) { - swarm_clone - .add_with_exponential_backoff(account, state.clone()) - .await; - tokio::time::sleep(join_delay).await; - } - } else { - let swarm_borrow = &swarm_clone; - join_all(options.accounts.iter().zip(options.states).map( - async move |(account, state)| -> Result<(), JoinError> { - swarm_borrow - .clone() - .add_with_exponential_backoff(account, state.clone()) - .await; - Ok(()) - }, - )) - .await; - } - }); - - let swarm_state = options.swarm_state; - let mut internal_state = InternalSwarmState::default(); - - // Watch swarm_rx and send those events to the plugins and swarm_handle. - let swarm_clone = swarm.clone(); - let swarm_plugins_clone = swarm_plugins.clone(); - tokio::spawn(async move { - while let Some(event) = swarm_rx.recv().await { - for plugin in swarm_plugins_clone.clone().into_iter() { - tokio::spawn(plugin.handle(event.clone(), swarm_clone.clone())); - } - tokio::spawn((options.swarm_handle)( - swarm_clone.clone(), - event, - swarm_state.clone(), - )); - } - }); - - // bot events - while let Some((Some(event), (bot, state))) = bots_rx.recv().await { - // bot event handling - let cloned_plugins = (*bot.plugins).clone(); - for plugin in cloned_plugins.into_iter() { - tokio::spawn(plugin.handle(event.clone(), bot.clone())); - } - - // swarm event handling - // remove this #[allow] when more checks are added - #[allow(clippy::single_match)] - match &event { - Event::Login => { - internal_state.bots_joined += 1; - if internal_state.bots_joined == swarm.bot_datas.lock().len() { - swarm_tx.send(SwarmEvent::Login).unwrap(); - } - } - _ => {} - } - - tokio::spawn((options.handle)(bot, event, state)); - } - - join_task.abort(); - - Ok(()) -} - -impl<S> Swarm<S> -where - S: Send + Sync + Clone + 'static, -{ +// pub async fn start_swarm< +// S: Send + Sync + Clone + 'static, +// SS: Send + Sync + Clone + 'static, +// A: Send + TryInto<ServerAddress>, +// Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, +// SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static, +// >( +// options: SwarmOptions<S, SS, A, Fut, SwarmFut>, +// ) -> Result<(), SwarmStartError> { +// } + +impl Swarm { /// Add a new account to the swarm. You can remove it later by calling /// [`Client::disconnect`]. - pub async fn add(&mut self, account: &Account, state: S) -> Result<Client, JoinError> { - let conn = Connection::new(&self.resolved_address).await?; - let (conn, game_profile) = Client::handshake(conn, account, &self.address.clone()).await?; - + /// + /// # Errors + /// + /// Returns an `Err` if the bot could not do a handshake successfully. + pub async fn add<S: Component + Clone>( + &mut self, + account: &Account, + state: S, + ) -> Result<Client, JoinError> { // tx is moved to the bot so it can send us events // rx is used to receive events from the bot - let (tx, mut rx) = mpsc::channel(1); - let mut bot = Client::new(game_profile, conn, Some(self.worlds.clone())); - tx.send(Event::Init).await.expect("Failed to send event"); - bot.start_tasks(tx); + // An event that causes the schedule to run. This is only used internally. + // let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel(); + // let ecs_lock = start_ecs(run_schedule_receiver, run_schedule_sender.clone()); + let (bot, mut rx) = Client::start_client( + self.ecs_lock.clone(), + account, + &self.address, + &self.resolved_address, + self.run_schedule_sender.clone(), + ) + .await?; + // add the state to the client + { + let mut ecs = self.ecs_lock.lock(); + ecs.entity_mut(bot.entity).insert(state); + } - bot.plugins = Arc::new(self.plugins.clone().build()); + self.bots.lock().insert(bot.entity, bot.clone()); + let cloned_bots = self.bots.clone(); let cloned_bots_tx = self.bots_tx.clone(); let cloned_bot = bot.clone(); - let cloned_state = state.clone(); let owned_account = account.clone(); - let bot_datas = self.bot_datas.clone(); let swarm_tx = self.swarm_tx.clone(); - // send the init event immediately so it's the first thing we get - swarm_tx.send(SwarmEvent::Init).unwrap(); tokio::spawn(async move { while let Some(event) = rx.recv().await { // we can't handle events here (since we can't copy the handler), // they're handled above in start_swarm - if let Err(e) = - cloned_bots_tx.send((Some(event), (cloned_bot.clone(), cloned_state.clone()))) - { + if let Err(e) = cloned_bots_tx.send((Some(event), cloned_bot.clone())) { error!("Error sending event to swarm: {e}"); } } - // the bot disconnected, so we remove it from the swarm - let mut bot_datas = bot_datas.lock(); - let index = bot_datas - .iter() - .position(|(b, _)| b.profile.uuid == cloned_bot.profile.uuid) - .expect("bot disconnected but not found in swarm"); - bot_datas.remove(index); - + cloned_bots.lock().remove(&bot.entity); swarm_tx .send(SwarmEvent::Disconnect(owned_account)) .unwrap(); }); - self.bot_datas.lock().push((bot.clone(), state.clone())); - Ok(bot) } @@ -399,7 +476,11 @@ where /// /// Exponential backoff means if it fails joining it will initially wait 10 /// seconds, then 20, then 40, up to 2 minutes. - pub async fn add_with_exponential_backoff(&mut self, account: &Account, state: S) -> Client { + pub async fn add_with_exponential_backoff<S: Component + Clone>( + &mut self, + account: &Account, + state: S, + ) -> Client { let mut disconnects = 0; loop { match self.add(account, state.clone()).await { @@ -409,7 +490,7 @@ where let delay = (Duration::from_secs(5) * 2u32.pow(disconnects)) .min(Duration::from_secs(120)); let username = account.username.clone(); - error!("Error joining {username}: {e}. Waiting {delay:?} and trying again."); + error!("Error joining as {username}: {e}. Waiting {delay:?} and trying again."); tokio::time::sleep(delay).await; } } @@ -417,33 +498,42 @@ where } } -impl<S> IntoIterator for Swarm<S> -where - S: Send + Sync + Clone + 'static, -{ - type Item = (Client, S); +impl IntoIterator for Swarm { + type Item = Client; type IntoIter = std::vec::IntoIter<Self::Item>; - /// Iterate over the bots and their states in this swarm. + /// Iterate over the bots in this swarm. /// /// ```rust,no_run - /// for (bot, state) in swarm { + /// for bot in swarm { + /// let state = bot.component::<State>(); /// // ... /// } /// ``` fn into_iter(self) -> Self::IntoIter { - self.bot_datas.lock().clone().into_iter() + self.bots + .lock() + .clone() + .into_values() + .collect::<Vec<_>>() + .into_iter() } } -#[derive(Default)] -struct InternalSwarmState { - /// The number of bots connected to the server - pub bots_joined: usize, -} - impl From<ConnectionError> for SwarmStartError { fn from(e: ConnectionError) -> Self { SwarmStartError::from(JoinError::from(e)) } } + +/// This plugin group will add all the default plugins necessary for swarms to +/// work. +pub struct DefaultSwarmPlugins; + +impl PluginGroup for DefaultSwarmPlugins { + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::start::<Self>() + .add(chat::SwarmChatPlugin) + .add(events::SwarmPlugin) + } +} diff --git a/azalea/src/swarm/plugins.rs b/azalea/src/swarm/plugins.rs deleted file mode 100644 index f92d40e6..00000000 --- a/azalea/src/swarm/plugins.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{Swarm, SwarmEvent}; -use async_trait::async_trait; -use nohash_hasher::NoHashHasher; -use std::{ - any::{Any, TypeId}, - collections::HashMap, - hash::BuildHasherDefault, -}; - -type U64Hasher = BuildHasherDefault<NoHashHasher<u64>>; - -// kind of based on https://docs.rs/http/latest/src/http/extensions.rs.html -/// A map of plugin ids to [`SwarmPlugin`] trait objects. The client stores -/// this so we can keep the state for our [`Swarm`] plugins. -/// -/// If you're using azalea, you should generate this from the `swarm_plugins!` -/// macro. -#[derive(Clone, Default)] -pub struct SwarmPlugins<S> { - map: Option<HashMap<TypeId, Box<dyn SwarmPlugin<S>>, U64Hasher>>, -} - -#[derive(Clone)] -pub struct SwarmPluginStates<S> { - map: Option<HashMap<TypeId, Box<dyn SwarmPluginState<S>>, U64Hasher>>, -} - -impl<S> SwarmPluginStates<S> { - pub fn get<T: SwarmPluginState<S>>(&self) -> Option<&T> { - self.map - .as_ref() - .and_then(|map| map.get(&TypeId::of::<T>())) - .and_then(|boxed| (boxed.as_ref() as &dyn Any).downcast_ref::<T>()) - } -} - -impl<S> SwarmPlugins<S> -where - S: 'static, -{ - /// Create a new empty set of plugins. - pub fn new() -> Self { - Self { map: None } - } - - /// Add a new plugin to this set. - pub fn add<T: SwarmPlugin<S>>(&mut self, plugin: T) { - if self.map.is_none() { - self.map = Some(HashMap::with_hasher(BuildHasherDefault::default())); - } - self.map - .as_mut() - .unwrap() - .insert(TypeId::of::<T>(), Box::new(plugin)); - } - - /// Build our plugin states from this set of plugins. Note that if you're - /// using `azalea` you'll probably never need to use this as it's called - /// for you. - pub fn build(self) -> SwarmPluginStates<S> { - if self.map.is_none() { - return SwarmPluginStates { map: None }; - } - let mut map = HashMap::with_hasher(BuildHasherDefault::default()); - for (id, plugin) in self.map.unwrap().into_iter() { - map.insert(id, plugin.build()); - } - SwarmPluginStates { map: Some(map) } - } -} - -impl<S> IntoIterator for SwarmPluginStates<S> { - type Item = Box<dyn SwarmPluginState<S>>; - type IntoIter = std::vec::IntoIter<Self::Item>; - - /// Iterate over the plugin states. - fn into_iter(self) -> Self::IntoIter { - self.map - .map(|map| map.into_values().collect::<Vec<_>>()) - .unwrap_or_default() - .into_iter() - } -} - -/// A `SwarmPluginState` keeps the current state of a plugin for a client. All -/// the fields must be atomic. Unique `SwarmPluginState`s are built from -/// [`SwarmPlugin`]s. -#[async_trait] -pub trait SwarmPluginState<S>: Send + Sync + SwarmPluginStateClone<S> + Any + 'static { - async fn handle(self: Box<Self>, event: SwarmEvent, swarm: Swarm<S>); -} - -/// Swarm plugins can keep their own personal state ([`SwarmPluginState`]), -/// listen to [`SwarmEvent`]s, and add new functions to [`Swarm`]. -pub trait SwarmPlugin<S>: Send + Sync + SwarmPluginClone<S> + Any + 'static { - fn build(&self) -> Box<dyn SwarmPluginState<S>>; -} - -/// An internal trait that allows SwarmPluginState to be cloned. -#[doc(hidden)] -pub trait SwarmPluginStateClone<S> { - fn clone_box(&self) -> Box<dyn SwarmPluginState<S>>; -} -impl<T, S> SwarmPluginStateClone<S> for T -where - T: 'static + SwarmPluginState<S> + Clone, -{ - fn clone_box(&self) -> Box<dyn SwarmPluginState<S>> { - Box::new(self.clone()) - } -} -impl<S> Clone for Box<dyn SwarmPluginState<S>> { - fn clone(&self) -> Self { - self.clone_box() - } -} - -/// An internal trait that allows SwarmPlugin to be cloned. -#[doc(hidden)] -pub trait SwarmPluginClone<S> { - fn clone_box(&self) -> Box<dyn SwarmPlugin<S>>; -} -impl<T, S> SwarmPluginClone<S> for T -where - T: 'static + SwarmPlugin<S> + Clone, -{ - fn clone_box(&self) -> Box<dyn SwarmPlugin<S>> { - Box::new(self.clone()) - } -} -impl<S> Clone for Box<dyn SwarmPlugin<S>> { - fn clone(&self) -> Self { - self.clone_box() - } -} diff --git a/bot/src/main.rs b/bot/src/main.rs index 1bd407e8..f748de0b 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,14 +1,19 @@ +#![feature(type_alias_impl_trait)] + +use azalea::ecs::query::With; +use azalea::entity::metadata::Player; +use azalea::entity::Position; use azalea::pathfinder::BlockPosGoal; // use azalea::ClientInformation; -use azalea::{prelude::*, BlockPos, Swarm, SwarmEvent, WalkDirection}; +use azalea::{prelude::*, BlockPos, GameProfileComponent, Swarm, SwarmEvent, WalkDirection}; use azalea::{Account, Client, Event}; use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket; use std::time::Duration; -#[derive(Default, Clone)] +#[derive(Default, Clone, Component)] struct State {} -#[derive(Default, Clone)] +#[derive(Default, Clone, Component)] struct SwarmState {} #[tokio::main] @@ -48,23 +53,17 @@ async fn main() -> anyhow::Result<()> { } loop { - let e = azalea::start_swarm(azalea::SwarmOptions { - accounts: accounts.clone(), - address: "localhost", - - states: states.clone(), - swarm_state: SwarmState::default(), - - plugins: plugins![], - swarm_plugins: swarm_plugins![], - - handle, - swarm_handle, - - join_delay: Some(Duration::from_millis(1000)), - // join_delay: None, - }) - .await; + let e = azalea::SwarmBuilder::new() + .add_accounts(accounts.clone()) + .set_handler(handle) + .set_swarm_handler(swarm_handle) + .join_delay(Duration::from_millis(1000)) + .start("localhost") + .await; + // let e = azalea::ClientBuilder::new() + // .set_handler(handle) + // .start(Account::offline("bot"), "localhost") + // .await; println!("{e:?}"); } } @@ -72,6 +71,7 @@ async fn main() -> anyhow::Result<()> { async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> { match event { Event::Init => { + println!("bot init"); // bot.set_client_information(ClientInformation { // view_distance: 2, // ..Default::default() @@ -79,43 +79,76 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< // .await?; } Event::Login => { - bot.chat("Hello world").await?; + bot.chat("Hello world"); } Event::Chat(m) => { + println!("client chat message: {}", m.content()); if m.content() == bot.profile.name { - bot.chat("Bye").await?; + bot.chat("Bye"); tokio::time::sleep(Duration::from_millis(50)).await; - bot.disconnect().await?; + bot.disconnect(); } - let entity = bot - .world() - .entity_by_uuid(&uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd")); + 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::<With<Player>>(|name: &Name| name == sender); + let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>( + |profile: &&GameProfileComponent| { + println!("entity {profile:?}"); + profile.name == sender + }, + ); + println!("sender entity: {entity:?}"); if let Some(entity) = entity { - if m.content() == "goto" { - let target_pos_vec3 = entity.pos(); - let target_pos: BlockPos = target_pos_vec3.into(); - bot.goto(BlockPosGoal::from(target_pos)); - } else if m.content() == "look" { - let target_pos_vec3 = entity.pos(); - let target_pos: BlockPos = target_pos_vec3.into(); - println!("target_pos: {target_pos:?}"); - bot.look_at(&target_pos.center()); - } else if m.content() == "jump" { - bot.set_jumping(true); - } else if m.content() == "walk" { - bot.walk(WalkDirection::Forward); - } else if m.content() == "stop" { - bot.set_jumping(false); - bot.walk(WalkDirection::None); - } else if m.content() == "lag" { - std::thread::sleep(Duration::from_millis(1000)); + match m.content().as_str() { + "whereami" => { + let pos = bot.entity_component::<Position>(entity); + bot.chat(&format!("You're at {pos:?}",)); + } + "whereareyou" => { + let pos = bot.component::<Position>(); + bot.chat(&format!("I'm at {pos:?}",)); + } + "goto" => { + let entity_pos = bot.entity_component::<Position>(entity); + let target_pos: BlockPos = entity_pos.into(); + println!("going to {target_pos:?}"); + bot.goto(BlockPosGoal::from(target_pos)); + } + "look" => { + let entity_pos = bot.entity_component::<Position>(entity); + let target_pos: BlockPos = entity_pos.into(); + println!("target_pos: {target_pos:?}"); + bot.look_at(target_pos.center()); + } + "jump" => { + bot.set_jumping(true); + } + "walk" => { + bot.walk(WalkDirection::Forward); + } + "stop" => { + bot.set_jumping(false); + bot.walk(WalkDirection::None); + } + "lag" => { + std::thread::sleep(Duration::from_millis(1000)); + } + _ => {} } } } Event::Death(_) => { bot.write_packet(ServerboundClientCommandPacket { action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn, - }.get()).await?; + }.get()); } _ => {} } @@ -124,7 +157,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< } async fn swarm_handle( - mut swarm: Swarm<State>, + mut swarm: Swarm, event: SwarmEvent, _state: SwarmState, ) -> anyhow::Result<()> { @@ -137,10 +170,10 @@ async fn swarm_handle( SwarmEvent::Chat(m) => { println!("swarm chat message: {}", m.message().to_ansi()); if m.message().to_string() == "<py5> world" { - for (name, world) in &swarm.worlds.read().worlds { + for (name, world) in &swarm.world_container.read().worlds { println!("world name: {name}"); if let Some(w) = world.upgrade() { - for chunk_pos in w.chunk_storage.read().chunks.values() { + for chunk_pos in w.read().chunks.chunks.values() { println!("chunk: {chunk_pos:?}"); } } else { @@ -149,8 +182,8 @@ async fn swarm_handle( } } if m.message().to_string() == "<py5> hi" { - for (bot, _) in swarm { - bot.chat("hello").await?; + for bot in swarm { + bot.chat("hello"); } } } diff --git a/codegen/lib/code/entity.py b/codegen/lib/code/entity.py index 6616c8d7..844793f6 100644 --- a/codegen/lib/code/entity.py +++ b/codegen/lib/code/entity.py @@ -15,8 +15,8 @@ def generate_entity_metadata(burger_entity_data: dict, mappings: Mappings): {'name': 'Long', 'type': 'i64'}, {'name': 'Float', 'type': 'f32'}, {'name': 'String', 'type': 'String'}, - {'name': 'Component', 'type': 'Component'}, - {'name': 'OptionalComponent', 'type': 'Option<Component>'}, + {'name': 'FormattedText', 'type': 'FormattedText'}, + {'name': 'OptionalFormattedText', 'type': 'Option<FormattedText>'}, {'name': 'ItemStack', 'type': 'Slot'}, {'name': 'Boolean', 'type': 'bool'}, {'name': 'Rotations', 'type': 'Rotations'}, @@ -37,313 +37,426 @@ def generate_entity_metadata(burger_entity_data: dict, mappings: Mappings): ] code = [] - code.append('// This file is generated from codegen/lib/code/entity.py.') - code.append("// Don't change it manually!") - code.append('') - code.append('#![allow(clippy::clone_on_copy, clippy::derivable_impls)]') - code.append( - 'use super::{EntityDataValue, Rotations, VillagerData, OptionalUnsignedInt, Pose};') - code.append('use azalea_block::BlockState;') - code.append('use azalea_chat::Component;') - code.append('use azalea_core::{BlockPos, Direction, Particle, Slot};') - code.append('use std::{collections::VecDeque, ops::{Deref, DerefMut}};') - code.append('use uuid::Uuid;') - code.append('') - - entity_structs = [] - - parent_field_name = None - for entity_id in burger_entity_data: - entity_parents = get_entity_parents(entity_id, burger_entity_data) - entity_metadata = get_entity_metadata(entity_id, burger_entity_data) - entity_metadata_names = get_entity_metadata_names( - entity_id, burger_entity_data, mappings) - - struct_name: str = upper_first_letter( - to_camel_case(entity_parents[0].replace('~', ''))) - parent_struct_name: Optional[str] = upper_first_letter(to_camel_case( - entity_parents[1].replace('~', ''))) if (len(entity_parents) >= 2) else None - if parent_struct_name: - parent_field_name = to_snake_case(parent_struct_name) - if not entity_parents[0].startswith('~'): - entity_structs.append(struct_name) + code.append('''#![allow(clippy::single_match)] + +// This file is generated from codegen/lib/code/entity.py. +// Don't change it manually! + +use super::{EntityDataItem, EntityDataValue, OptionalUnsignedInt, Pose, Rotations, VillagerData}; +use azalea_block::BlockState; +use azalea_chat::FormattedText; +use azalea_core::{BlockPos, Direction, Particle, Slot}; +use azalea_ecs::{bundle::Bundle, component::Component}; +use derive_more::{Deref, DerefMut}; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Error, Debug)] +pub enum UpdateMetadataError { + #[error("Wrong type ({0:?})")] + WrongType(EntityDataValue), +} +impl From<EntityDataValue> for UpdateMetadataError { + fn from(value: EntityDataValue) -> Self { + Self::WrongType(value) + } +} +''') + + # types that are only ever used in one entity + single_use_imported_types = {'particle', 'pose'} + + added_metadata_fields = set() + + # a dict of { entity_id: { field_name: new_name } } + field_name_map = {} + + # build the duplicate_field_names set + previous_field_names = set() + duplicate_field_names = set() + for entity_id in burger_entity_data.keys(): + field_name_map[entity_id] = {} + for field_name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_data, mappings).values(): + if isinstance(field_name_or_bitfield, str): + if field_name_or_bitfield in previous_field_names: + duplicate_field_names.add(field_name_or_bitfield) + else: + previous_field_names.add(field_name_or_bitfield) + else: + for mask, name in field_name_or_bitfield.items(): + if name in previous_field_names: + duplicate_field_names.add(name) + else: + previous_field_names.add(name) + + # oh and also just add the entity id to the duplicate field names to + # make sure entity names don't clash with field names + duplicate_field_names.add(entity_id) + + # make sure these types are only ever made once + for name in single_use_imported_types: + if name in duplicate_field_names: + raise Exception(f'{name} should only exist once') + + # and now figure out what to rename them to + for entity_id in burger_entity_data.keys(): + for index, field_name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_data, mappings).items(): + if isinstance(field_name_or_bitfield, str): + new_field_name = field_name_or_bitfield + if new_field_name == 'type': + new_field_name = 'kind' + if field_name_or_bitfield in duplicate_field_names: + field_name_map[entity_id][ + field_name_or_bitfield] = f'{entity_id.strip("~")}_{new_field_name}' + else: + for mask, name in field_name_or_bitfield.items(): + new_field_name = name + if new_field_name == 'type': + new_field_name = 'kind' + if name in duplicate_field_names: + field_name_map[entity_id][name] = f'{entity_id.strip("~")}_{new_field_name}' + + def new_entity(entity_id: str): + # note: fields are components + + # if it doesn't start with ~ then also make a marker struct and Query struct for it + all_field_names_or_bitfields = [] + entity_ids_for_all_field_names_or_bitfields = [] + entity_metadatas = [] + + def maybe_rename_field(name: str, index: int) -> str: + if name in field_name_map[entity_ids_for_all_field_names_or_bitfields[index]]: + return field_name_map[entity_ids_for_all_field_names_or_bitfields[index]][name] + return name + + parents = get_entity_parents(entity_id, burger_entity_data) + for parent_id in list(reversed(parents)): + for index, name_or_bitfield in get_entity_metadata_names(parent_id, burger_entity_data, mappings).items(): + assert index == len(all_field_names_or_bitfields) + all_field_names_or_bitfields.append(name_or_bitfield) + entity_ids_for_all_field_names_or_bitfields.append(parent_id) + entity_metadatas.extend(get_entity_metadata( + parent_id, burger_entity_data)) + parent_id = parents[1] if len(parents) > 1 else None + + # now add all the fields/component structs + for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): + # make sure we only ever make these structs once + hashable_name_or_bitfield = str( + name_or_bitfield) + entity_ids_for_all_field_names_or_bitfields[index] + if hashable_name_or_bitfield in added_metadata_fields: + continue + added_metadata_fields.add(hashable_name_or_bitfield) - reader_code = [] - writer_code = [] - set_index_code = [] - field_names = [] + if isinstance(name_or_bitfield, str): + # we just use the imported type instead of making our own + if name_or_bitfield in single_use_imported_types: + continue - code.append(f'#[derive(Debug, Clone)]') - code.append(f'pub struct {struct_name} {{') + name_or_bitfield = maybe_rename_field(name_or_bitfield, index) - if parent_struct_name: - assert parent_field_name - code.append(f'pub {parent_field_name}: {parent_struct_name},') - reader_code.append( - f'let {parent_field_name} = {parent_struct_name}::read(metadata)?;') - writer_code.append( - f'metadata.extend(self.{parent_field_name}.write());') - for index, name_or_bitfield in entity_metadata_names.items(): - if isinstance(name_or_bitfield, str): - # normal field (can be any type) - name = name_or_bitfield - if name == 'type': - name = 'kind' - field_names.append(name) - type_id = next(filter(lambda i: i['index'] == index, entity_metadata))[ + struct_name = upper_first_letter( + to_camel_case(name_or_bitfield)) + type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))[ 'type_id'] metadata_type_data = metadata_types[type_id] rust_type = metadata_type_data['type'] - type_name = metadata_type_data['name'] - code.append(f'pub {name}: {rust_type},') - type_name_field = to_snake_case(type_name) - reader_code.append( - f'let {name} = metadata.pop_front()?.into_{type_name_field}().ok()?;') - writer_code.append( - f'metadata.push(EntityDataValue::{type_name}(self.{name}.clone()));') - - # 1 => self.dancing = value.into_boolean().ok()?, - set_index_code.append( - f'{index} => self.{name} = value.into_{type_name_field}().ok()?,' - ) + code.append(f'#[derive(Component, Deref, DerefMut)]') + code.append(f'pub struct {struct_name}(pub {rust_type});') else: - # bitfield (sent as a byte, each bit in the byte is used as a boolean) - reader_code.append( - 'let bitfield = metadata.pop_front()?.into_byte().ok()?;') - writer_code.append('let mut bitfield = 0u8;') - set_index_code.append(f'{index} => {{') - set_index_code.append( - f'let bitfield = value.into_byte().ok()?;') + # if it's a bitfield just make a struct for each bit for mask, name in name_or_bitfield.items(): - if name == 'type': - name = 'kind' - - field_names.append(name) - code.append(f'pub {name}: bool,') - reader_code.append(f'let {name} = bitfield & {mask} != 0;') - writer_code.append( - f'if self.{name} {{ bitfield &= {mask}; }}') - set_index_code.append( - f'self.{name} = bitfield & {mask} != 0;') - writer_code.append( - 'metadata.push(EntityDataValue::Byte(bitfield));') - set_index_code.append('},') + name = maybe_rename_field(name, index) + struct_name = upper_first_letter(to_camel_case(name)) + code.append(f'#[derive(Component, Deref, DerefMut)]') + code.append(f'pub struct {struct_name}(pub bool);') - code.append('}') - code.append('') + # add the entity struct and Bundle struct + struct_name: str = upper_first_letter( + to_camel_case(entity_id.lstrip('~'))) + code.append(f'#[derive(Component)]') + code.append(f'pub struct {struct_name};') - code.append(f'impl {struct_name} {{') + parent_struct_name = upper_first_letter( + to_camel_case(parent_id.lstrip("~"))) if parent_id else None + # impl Allay { + # pub fn apply_metadata( + # entity: &mut azalea_ecs::system::EntityCommands, + # d: EntityDataItem, + # ) -> Result<(), UpdateMetadataError> { + # match d.index { + # 0..=15 => AbstractCreatureBundle::apply_metadata(entity, d)?, + # 16 => entity.insert(Dancing(d.value.into_boolean()?)), + # 17 => entity.insert(CanDuplicate(d.value.into_boolean()?)), + # } + # Ok(()) + # } + # } + code.append(f'impl {struct_name} {{') code.append( - 'pub fn read(metadata: &mut VecDeque<EntityDataValue>) -> Option<Self> {') - code.extend(reader_code) - - self_args = [] - if parent_struct_name: - self_args.append( - f'{parent_field_name}') - self_args.extend(field_names) - code.append(f'Some(Self {{ {",".join(self_args)} }})') - code.append('}') - code.append('') + f' pub fn apply_metadata(entity: &mut azalea_ecs::system::EntityCommands, d: EntityDataItem) -> Result<(), UpdateMetadataError> {{') + code.append(f' match d.index {{') + + parent_last_index = -1 + for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): + is_from_parent = entity_ids_for_all_field_names_or_bitfields[index] != entity_id + if is_from_parent: + parent_last_index = index + if parent_last_index != -1: + code.append( + f' 0..={parent_last_index} => {parent_struct_name}::apply_metadata(entity, d)?,') - code.append('pub fn write(&self) -> Vec<EntityDataValue> {') - code.append('let mut metadata = Vec::new();') - code.extend(writer_code) - code.append('metadata') - code.append('}') + for index, name_or_bitfield in enumerate(all_field_names_or_bitfields): + if index <= parent_last_index: + continue + if isinstance(name_or_bitfield, str): + name_or_bitfield = maybe_rename_field( + name_or_bitfield, index) - code.append('}') - code.append('') + field_struct_name = upper_first_letter( + to_camel_case(name_or_bitfield)) + if name_or_bitfield in single_use_imported_types: + field_struct_name = '' - # default - code.append(f'impl Default for {struct_name} {{') - code.append('fn default() -> Self {') - default_fields_code = [] - if parent_struct_name: - assert parent_field_name - default_fields_code.append( - f'{parent_field_name}: Default::default()') - for index, name_or_bitfield in entity_metadata_names.items(): - default = next(filter(lambda i: i['index'] == index, entity_metadata)).get( - 'default', 'Default::default()') - if isinstance(name_or_bitfield, str): - type_id = next(filter(lambda i: i['index'] == index, entity_metadata))[ + type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))[ 'type_id'] metadata_type_data = metadata_types[type_id] + rust_type = metadata_type_data['type'] type_name = metadata_type_data['name'] - # TODO: burger doesn't get the default if it's a complex type - # like `Rotations`, so entities like armor stands will have the - # wrong default metadatas. This should be added to Burger. - if default is None: - # some types don't have Default implemented - if type_name == 'CompoundTag': - default = 'azalea_nbt::Tag::Compound(Default::default())' - elif type_name == 'CatVariant': - default = 'azalea_registry::CatVariant::Tabby' - elif type_name == 'PaintingVariant': - default = 'azalea_registry::PaintingVariant::Kebab' - elif type_name == 'FrogVariant': - default = 'azalea_registry::FrogVariant::Temperate' - elif type_name == 'VillagerData': - default = 'VillagerData { kind: azalea_registry::VillagerType::Plains, profession: azalea_registry::VillagerProfession::None, level: 0 }' - else: - default = 'Default::default()' - else: - if type_name == 'Boolean': - default = 'true' if default else 'false' - elif type_name == 'String': - string_escaped = default.replace('"', '\\"') - default = f'"{string_escaped}".to_string()' - elif type_name == 'BlockPos': - default = f'BlockPos::new{default}' - elif type_name == 'OptionalBlockPos': # Option<BlockPos> - default = f'Some(BlockPos::new{default})' if default != 'Empty' else 'None' - elif type_name == 'OptionalUuid': - default = f'Some(uuid::uuid!({default}))' if default != 'Empty' else 'None' - elif type_name == 'OptionalUnsignedInt': - default = f'OptionalUnsignedInt(Some({default}))' if default != 'Empty' else 'OptionalUnsignedInt(None)' - elif type_name == 'ItemStack': - default = f'Slot::Present({default})' if default != 'Empty' else 'Slot::Empty' - elif type_name == 'BlockState': - default = f'{default}' if default != 'Empty' else 'BlockState::Air' - elif type_name == 'OptionalComponent': - default = f'Some({default})' if default != 'Empty' else 'None' - elif type_name == 'CompoundTag': - default = f'azalea_nbt::Tag::Compound({default})' if default != 'Empty' else 'azalea_nbt::Tag::Compound(Default::default())' - - print(default, name_or_bitfield, type_name) - name = name_or_bitfield - if name == 'type': - name = 'kind' - default_fields_code.append(f'{name}: {default}') + type_name_field = to_snake_case(type_name) + read_field_code = f'{field_struct_name}(d.value.into_{type_name_field}()?)' if field_struct_name else f'd.value.into_{type_name_field}()?' + code.append( + f' {index} => {{ entity.insert({read_field_code}); }},') else: - # if it's a bitfield, we'll have to extract the default for - # each bool from each bit in the default + code.append(f' {index} => {{') + code.append( + f'let bitfield = d.value.into_byte()?;') for mask, name in name_or_bitfield.items(): - if name == 'type': - name = 'kind' - mask = int(mask, 0) - field_names.append(name) - bit_default = 'true' if (default & mask != 0) else 'false' - default_fields_code.append(f'{name}: {bit_default}') - - # Self { abstract_creature: Default::default(), dancing: Default::default(), can_duplicate: Default::default() } - code.append(f'Self {{ {", ".join(default_fields_code)} }}') - code.append('}') + name = maybe_rename_field(name, index) + field_struct_name = upper_first_letter(to_camel_case(name)) + + code.append( + f'entity.insert({field_struct_name}(bitfield & {mask} != 0));') + code.append(' },') + code.append(' _ => {}') + code.append(' }') + code.append(' Ok(())') + code.append(' }') code.append('}') code.append('') - # impl Allay { - # pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> { - # match index { - # 0..=0 => self.abstract_creature.set_index(index, value), - # 1 => self.dancing = value.into_boolean().ok()?, - # 2 => self.can_duplicate = value.into_boolean().ok()?, - # _ => {} - # } - # Some(()) - # } + # #[derive(Bundle)] + # struct AllayBundle { + # health: Health, + # ... + # dancing: Dancing, + # can_duplicate: CanDuplicate, # } - code.append(f'impl {struct_name} {{') + bundle_struct_name = f'{struct_name}MetadataBundle' + code.append(f'') + code.append(f'#[derive(Bundle)]') + code.append(f'pub struct {bundle_struct_name} {{') code.append( - 'pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> {') - if len(entity_metadata_names) > 0: - code.append('match index {') - # get the smallest index for this entity - smallest_index = min(entity_metadata_names.keys()) - if parent_struct_name: - code.append( - f'0..={smallest_index-1} => self.{parent_field_name}.set_index(index, value)?,') - code.extend(set_index_code) - code.append('_ => {}') - code.append('}') - code.append('Some(())') - elif parent_struct_name: - code.append(f'self.{parent_field_name}.set_index(index, value)') - else: - code.append('Some(())') - code.append('}') - code.append('}') - - # deref + f' _marker: {struct_name},') if parent_struct_name: - code.append(f'impl Deref for {struct_name} {{') - code.append(f'type Target = {parent_struct_name};') code.append( - f'fn deref(&self) -> &Self::Target {{ &self.{parent_field_name} }}') - code.append('}') - code.append(f'impl DerefMut for {struct_name} {{') - code.append( - f'fn deref_mut(&mut self) -> &mut Self::Target {{ &mut self.{parent_field_name} }}') - code.append('}') - code.append('') - - # make the EntityMetadata enum from entity_structs - code.append(f'#[derive(Debug, Clone)]') - code.append('pub enum EntityMetadata {') - for struct_name in entity_structs: - code.append(f'{struct_name}({struct_name}),') - code.append('}') - code.append('') + f' parent: {parent_struct_name}MetadataBundle,') + for index, name_or_bitfield in get_entity_metadata_names(entity_id, burger_entity_data, mappings).items(): + if isinstance(name_or_bitfield, str): + name_or_bitfield = maybe_rename_field( + name_or_bitfield, index) + struct_name = upper_first_letter( + to_camel_case(name_or_bitfield)) + code.append( + f' {name_or_bitfield}: {struct_name},') + else: + for mask, name in name_or_bitfield.items(): + name = maybe_rename_field(name, index) + + struct_name = upper_first_letter(to_camel_case(name)) + code.append(f' {name}: {struct_name},') + code.append('}') - # impl From<azalea_registry::EntityType> for EntityMetadata { - code.append('impl From<azalea_registry::EntityType> for EntityMetadata {') - code.append('fn from(value: azalea_registry::EntityType) -> Self {') - code.append('match value {') - # azalea_registry::EntityType::Allay => EntityMetadata::Allay(Allay::default()), - for struct_name in entity_structs: + # impl Default for AllayBundle { + # fn default() -> Self { + # Self { + # _marker: Allay, + # parent: AbstractCreatureBundle { + # on_fire: OnFire(false), + # shift_key_down: ShiftKeyDown(false), + # }, + # sprinting: Sprinting(false), + # swimming: Swimming(false) + # } + # } + # } + code.append(f'impl Default for {bundle_struct_name} {{') code.append( - f'azalea_registry::EntityType::{struct_name} => EntityMetadata::{struct_name}({struct_name}::default()),') - code.append('}') - code.append('}') - code.append('}') - code.append('') + ' fn default() -> Self {') - # impl EntityMetadata - # pub fn set_index(&mut self, index: u8, value: EntityDataValue) - code.append('impl EntityMetadata {') + def generate_fields(this_entity_id: str): + # on_fire: OnFire(false), + # shift_key_down: ShiftKeyDown(false), + + # _marker + this_entity_struct_name = upper_first_letter( + to_camel_case(this_entity_id.lstrip('~'))) + code.append( + f' _marker: {this_entity_struct_name},') + + # if it has a parent, put it (do recursion) + # parent: AbstractCreatureBundle { ... }, + this_entity_parent_ids = get_entity_parents( + this_entity_id, burger_entity_data) + this_entity_parent_id = this_entity_parent_ids[1] if len( + this_entity_parent_ids) > 1 else None + if this_entity_parent_id: + bundle_struct_name = upper_first_letter( + to_camel_case(this_entity_parent_id.lstrip('~'))) + 'MetadataBundle' + code.append( + f' parent: {bundle_struct_name} {{') + generate_fields(this_entity_parent_id) + code.append( + ' },') + + for index, name_or_bitfield in get_entity_metadata_names(this_entity_id, burger_entity_data, mappings).items(): + default = next(filter(lambda i: i['index'] == index, entity_metadatas)).get( + 'default', 'Default::default()') + if isinstance(name_or_bitfield, str): + type_id = next(filter(lambda i: i['index'] == index, entity_metadatas))[ + 'type_id'] + metadata_type_data = metadata_types[type_id] + type_name = metadata_type_data['name'] + + name = maybe_rename_field(name_or_bitfield, index) + + # TODO: burger doesn't get the default if it's a complex type + # like `Rotations`, so entities like armor stands will have the + # wrong default metadatas. This should be added to Burger. + if default is None: + # some types don't have Default implemented + if type_name == 'CompoundTag': + default = 'azalea_nbt::Tag::Compound(Default::default())' + elif type_name == 'CatVariant': + default = 'azalea_registry::CatVariant::Tabby' + elif type_name == 'PaintingVariant': + default = 'azalea_registry::PaintingVariant::Kebab' + elif type_name == 'FrogVariant': + default = 'azalea_registry::FrogVariant::Temperate' + elif type_name == 'VillagerData': + default = 'VillagerData { kind: azalea_registry::VillagerKind::Plains, profession: azalea_registry::VillagerProfession::None, level: 0 }' + else: + default = f'{type_name}::default()' if name in single_use_imported_types else 'Default::default()' + else: + if type_name == 'Boolean': + default = 'true' if default else 'false' + elif type_name == 'String': + string_escaped = default.replace('"', '\\"') + default = f'"{string_escaped}".to_string()' + elif type_name == 'BlockPos': + default = f'BlockPos::new{default}' + elif type_name == 'OptionalBlockPos': # Option<BlockPos> + default = f'Some(BlockPos::new{default})' if default != 'Empty' else 'None' + elif type_name == 'OptionalUuid': + default = f'Some(uuid::uuid!({default}))' if default != 'Empty' else 'None' + elif type_name == 'OptionalUnsignedInt': + default = f'OptionalUnsignedInt(Some({default}))' if default != 'Empty' else 'OptionalUnsignedInt(None)' + elif type_name == 'ItemStack': + default = f'Slot::Present({default})' if default != 'Empty' else 'Slot::Empty' + elif type_name == 'BlockState': + default = f'{default}' if default != 'Empty' else 'BlockState::Air' + elif type_name == 'OptionalFormattedText': + default = f'Some({default})' if default != 'Empty' else 'None' + elif type_name == 'CompoundTag': + default = f'azalea_nbt::Tag::Compound({default})' if default != 'Empty' else 'azalea_nbt::Tag::Compound(Default::default())' + if name in single_use_imported_types: + code.append(f' {name}: {default},') + else: + code.append( + f' {name}: {upper_first_letter(to_camel_case(name))}({default}),') + else: + # if it's a bitfield, we'll have to extract the default for + # each bool from each bit in the default + for mask, name in name_or_bitfield.items(): + name = maybe_rename_field(name, index) + mask = int(mask, 0) + bit_default = 'true' if ( + default & mask != 0) else 'false' + code.append( + f' {name}: {upper_first_letter(to_camel_case(name))}({bit_default}),') + code.append(' Self {') + generate_fields(entity_id) + code.append(' }') + code.append(' }') + code.append('}') + code.append('') + + # parent_field_name = None + for entity_id in burger_entity_data: + new_entity(entity_id) + + # and now make the main apply_metadata + # pub fn apply_metadata( + # entity: &mut azalea_ecs::system::EntityCommands, + # items: Vec<EntityDataItem>, + # ) -> Result<(), UpdateMetadataError> { + # if entity.contains::<Allay>() { + # for d in items { + # Allay::apply_metadata(entity, d)?; + # } + # return Ok(()); + # } + # + # Ok(()) + # } code.append( - 'pub fn set_index(&mut self, index: u8, value: EntityDataValue) -> Option<()> {') - code.append('match self {') - # EntityMetadata::Allay(allay) => allay.set_index(index, value), - for struct_name in entity_structs: + f'''pub fn apply_metadata( + entity: &mut azalea_ecs::system::EntityCommands, + entity_kind: azalea_registry::EntityKind, + items: Vec<EntityDataItem>, +) -> Result<(), UpdateMetadataError> {{ + match entity_kind {{''') + for entity_id in burger_entity_data: + if entity_id.startswith('~'): + # not actually an entity + continue + struct_name: str = upper_first_letter(to_camel_case(entity_id)) code.append( - f'EntityMetadata::{struct_name}(entity) => entity.set_index(index, value),') - code.append('}') - code.append('}') + f' azalea_registry::EntityKind::{struct_name} => {{') + code.append(' for d in items {') + code.append( + f' {struct_name}::apply_metadata(entity, d)?;') + code.append(' }') + code.append(' },') + code.append(' }') + code.append(' Ok(())') code.append('}') code.append('') - # impl Deref for EntityMetadata { - # type Target = AbstractEntity; - # fn deref(&self) -> &Self::Target { - # match self { - # EntityMetadata::Allay(entity) => entity, - # _ => {} + # pub fn apply_default_metadata(entity: &mut azalea_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) { + # match kind { + # azalea_registry::EntityKind::AreaEffectCloud => { + # entity.insert(AreaEffectCloudMetadataBundle::default()); # } # } # } - code.append('impl Deref for EntityMetadata {') - code.append('type Target = AbstractEntity;') - code.append('fn deref(&self) -> &Self::Target {') - code.append('match self {') - for struct_name in entity_structs: + code.append( + 'pub fn apply_default_metadata(entity: &mut azalea_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {') + code.append(' match kind {') + for entity_id in burger_entity_data: + if entity_id.startswith('~'): + # not actually an entity + continue + struct_name: str = upper_first_letter(to_camel_case(entity_id)) code.append( - f'EntityMetadata::{struct_name}(entity) => entity,') - code.append('}') - code.append('}') - code.append('}') - code.append('impl DerefMut for EntityMetadata {') - code.append('fn deref_mut(&mut self) -> &mut Self::Target {') - code.append('match self {') - for struct_name in entity_structs: + f' azalea_registry::EntityKind::{struct_name} => {{') code.append( - f'EntityMetadata::{struct_name}(entity) => entity,') - code.append('}') - code.append('}') + f' entity.insert({struct_name}MetadataBundle::default());') + code.append(' },') + code.append(' }') code.append('}') code.append('') @@ -378,6 +491,8 @@ def get_entity_metadata(entity_id: str, burger_entity_data: dict): }) return entity_useful_metadata +# returns a dict of {index: (name or bitfield)} + def get_entity_metadata_names(entity_id: str, burger_entity_data: dict, mappings: Mappings): entity_metadata = burger_entity_data[entity_id]['metadata'] diff --git a/codegen/lib/code/registry.py b/codegen/lib/code/registry.py index 86f5f02d..1e9d9f43 100755 --- a/codegen/lib/code/registry.py +++ b/codegen/lib/code/registry.py @@ -56,7 +56,14 @@ impl<T: Registry> McBufWritable for OptionalRegistry<T> { # Air => "minecraft:air", # Stone => "minecraft:stone" # }); + + if registry_name.endswith('_type'): + # change _type to _kind because that's Rustier (and because _type + # is a reserved keyword) + registry_name = registry_name[:-5] + '_kind' + registry_struct_name = to_camel_case(registry_name.split(':')[1]) + code.append(f'registry!({registry_struct_name}, {{') registry_entries = sorted( registry['entries'].items(), key=lambda x: x[1]['protocol_id']) diff --git a/codegen/lib/code/utils.py b/codegen/lib/code/utils.py index 5550cdb2..fe4aca7f 100755 --- a/codegen/lib/code/utils.py +++ b/codegen/lib/code/utils.py @@ -44,8 +44,8 @@ def burger_type_to_rust_type(burger_type, field_name: Optional[str] = None, inst field_type_rs = 'String' elif burger_type == 'chatcomponent': - field_type_rs = 'Component' - uses.add('azalea_chat::Component') + field_type_rs = 'FormattedText' + uses.add('azalea_chat::FormattedText') elif burger_type == 'identifier': field_type_rs = 'ResourceLocation' uses.add('azalea_core::ResourceLocation') |
