diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2023-02-04 19:32:27 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-04 19:32:27 -0600 |
| commit | a5672815ccef520b433363ac622dbb6d6af60c91 (patch) | |
| tree | f9bb1b41876d81423ac3f188f4d368e6d362eed1 | |
| parent | 7c7446ab1e467c29f86e9bfba260741fc469389a (diff) | |
| download | azalea-drasl-a5672815ccef520b433363ac622dbb6d6af60c91.tar.xz | |
Use an ECS (#52)
* add EntityData::kind
* start making metadata use hecs
* make entity codegen generate ecs stuff
* fix registry codegen
* get rid of worldhaver
it's not even used
* add bevy_ecs to deps
* rename Component to FormattedText
also start making the metadata use bevy_ecs but bevy_ecs doesn't let you query on Bundles so it's annoying
* generate metadata.rs correctly for bevy_ecs
* start switching more entity stuff to use ecs
* more ecs stuff for entity storage
* ok well it compiles but
it definitely doesn't work
* random fixes
* change a bunch of entity things to use the components
* some ecs stuff in az-client
* packet handler uses the ecs now
and other fun changes
i still need to make ticking use the ecs but that's tricker, i'm considering using bevy_ecs systems for those
bevy_ecs systems can't be async but the only async things in ticking is just sending packets which can just be done as a tokio task so that's not a big deal
* start converting some functions in az-client into systems
committing because i'm about to try something that might go horribly wrong
* start splitting client
i'm probably gonna change it so azalea entity ids are separate from minecraft entity ids next (so stuff like player ids can be consistent and we don't have to wait for the login packet)
* separate minecraft entity ids from azalea entity ids + more ecs stuff
i guess i'm using bevy_app now too huh
it's necessary for plugins and it lets us control the tick rate anyways so it's fine i think
i'm still not 100% sure how packet handling that interacts with the world will work, but i think if i can sneak the ecs world into there it'll be fine. Can't put packet handling in the schedule because that'd make it tick-bound, which it's not (technically it'd still work but it'd be wrong and anticheats might realize).
* packet handling
now it runs the schedule only when we get a tick or packet :smile:
also i systemified some more functions and did other random fixes so az-world and az-physics compile
making azalea-client use the ecs is almost done! all the hard parts are done now i hope, i just have to finish writing all the code so it actually works
* start figuring out how functions in Client will work
generally just lifetimes being annoying but i think i can get it all to work
* make writing packets work synchronously*
* huh az-client compiles
* start fixing stuff
* start fixing some packets
* make packet handler work
i still haven't actually tested any of this yet lol but in theory it should all work
i'll probably either actually test az-client and fix all the remaining issues or update the azalea crate next
ok also one thing that i'm not particularly happy with is how the packet handlers are doing ugly queries like
```rs
let local_player = ecs
.query::<&LocalPlayer>()
.get_mut(ecs, player_entity)
.unwrap();
```
i think the right way to solve it would be by putting every packet handler in its own system but i haven't come up with a way to make that not be really annoying yet
* fix warnings
* ok what if i just have a bunch of queries and a single packet handler system
* simple example for azalea-client
* :bug:
* maybe fix deadlock idk
can't test it rn lmao
* make physicsstate its own component
* use the default plugins
* azalea compiles lol
* use systemstate for packet handler
* fix entities
basically moved some stuff from being in the world to just being components
* physics (ticking) works
* try to add a .entity_by function
still doesn't work because i want to make the predicate magic
* try to make entity_by work
well it does work but i couldn't figure out how to make it look not terrible. Will hopefully change in the future
* everything compiles
* start converting swarm to use builder
* continue switching swarm to builder and fix stuff
* make swarm use builder
still have to fix some stuff and make client use builder
* fix death event
* client builder
* fix some warnings
* document plugins a bit
* start trying to fix tests
* azalea-ecs
* azalea-ecs stuff compiles
* az-physics tests pass :tada:
* fix all the tests
* clippy on azalea-ecs-macros
* remove now-unnecessary trait_upcasting feature
* fix some clippy::pedantic warnings lol
* why did cargo fmt not remove the trailing spaces
* FIX ALL THE THINGS
* when i said 'all' i meant non-swarm bugs
* start adding task pool
* fix entity deduplication
* fix pathfinder not stopping
* fix some more random bugs
* fix panic that sometimes happens in swarms
* make pathfinder run in task
* fix some tests
* fix doctests and clippy
* deadlock
* fix systems running in wrong order
* fix non-swarm bots
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') |
