mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Inventory (#48)
* start adding azalea-inventory * design more of how inventories are defined * start working on az-inv-macros * inventory macro works * start adding inventory codegen * update some deps * add inventory codegen * manually write inventory menus * put the inventories in Client * start on containersetcontent * inventory menu should hopefully work * checks in containersetcontent * format a comment * move some variant matches * inventory.rs * inventory stuff * more inventory stuff * inventory/container tracking works * start adding interact function * sequence number * start adding HitResultComponent * implement traverse_blocks * start adding clip * add clip function * update_hit_result_component * start trying to fix * fix * make some stuff simpler * clippy * lever * chest * container handle * fix ambiguity * fix some doc tests * move some container stuff from az-client to azalea * clicking container * start implementing simulate_click * keep working on simulate click * implement more of simulate_click this is really boring * inventory fixes * start implementing shift clicking * fix panic in azalea-chat i hope * shift clicking implemented * more inventory stuff * fix items not showing in containers sometimes * fix test * fix all warnings * remove a println --------- Co-authored-by: mat <git@matdoes.dev>
This commit is contained in:
parent
1fb4418f2c
commit
634cb8d72c
78 changed files with 5635 additions and 404 deletions
214
Cargo.lock
generated
214
Cargo.lock
generated
|
@ -41,9 +41,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.20"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -62,9 +62,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.70"
|
version = "1.0.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
|
@ -169,6 +169,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-client",
|
"azalea-client",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
|
@ -277,6 +278,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-crypto",
|
"azalea-crypto",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
|
@ -303,8 +305,12 @@ name = "azalea-core"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
|
"azalea-chat",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
|
"azalea-registry",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -324,6 +330,25 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "azalea-inventory"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"azalea-buf",
|
||||||
|
"azalea-inventory-macros",
|
||||||
|
"azalea-nbt",
|
||||||
|
"azalea-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "azalea-inventory-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "azalea-language"
|
name = "azalea-language"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -357,6 +382,7 @@ version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azalea-block",
|
"azalea-block",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
"azalea-world",
|
"azalea-world",
|
||||||
"bevy_app",
|
"bevy_app",
|
||||||
|
@ -381,6 +407,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-crypto",
|
"azalea-crypto",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"azalea-protocol-macros",
|
"azalea-protocol-macros",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
|
@ -438,7 +465,9 @@ dependencies = [
|
||||||
"azalea-block",
|
"azalea-block",
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
|
"azalea-client",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-inventory",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
"bevy_app",
|
"bevy_app",
|
||||||
|
@ -463,7 +492,7 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.6.2",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
@ -698,9 +727,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.12.0"
|
version = "3.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
|
@ -811,9 +840,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.23"
|
version = "3.2.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
|
@ -871,9 +900,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
|
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -1068,9 +1097,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastnbt"
|
name = "fastnbt"
|
||||||
version = "2.4.3"
|
version = "2.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1aab2b0109236f6c89cc81b9e2ef4aced6d585aabe96ac860ee5e9a102eb198"
|
checksum = "3369bd70629bccfda7e344883c9ae3ab7f3b10a357bcf8b0f69caa7256bcf188"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cesu8",
|
"cesu8",
|
||||||
|
@ -1095,12 +1124,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.25"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1512,9 +1541,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.141"
|
version = "0.2.142"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
|
@ -1601,6 +1630,15 @@ dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
@ -1610,7 +1648,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1787,7 +1825,7 @@ dependencies = [
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread-id",
|
"thread-id",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1943,13 +1981,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.7.3"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1958,7 +1996,7 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex-syntax",
|
"regex-syntax 0.6.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1968,10 +2006,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "regex-syntax"
|
||||||
version = "0.11.16"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -2355,9 +2399,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.27.0"
|
version = "1.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -2369,14 +2413,14 @@ dependencies = [
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2396,9 +2440,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.7"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -2445,13 +2489,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.23"
|
version = "0.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2477,9 +2521,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.16"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matchers",
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
@ -2599,9 +2643,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.3.1"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb"
|
checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2794,7 +2838,16 @@ version = "0.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2803,13 +2856,28 @@ version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm 0.42.2",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.42.2",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.42.2",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.42.2",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.42.2",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm 0.42.2",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.48.0",
|
||||||
|
"windows_aarch64_msvc 0.48.0",
|
||||||
|
"windows_i686_gnu 0.48.0",
|
||||||
|
"windows_i686_msvc 0.48.0",
|
||||||
|
"windows_x86_64_gnu 0.48.0",
|
||||||
|
"windows_x86_64_gnullvm 0.48.0",
|
||||||
|
"windows_x86_64_msvc 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2818,36 +2886,72 @@ version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
|
@ -2855,10 +2959,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.4.1"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5617da7e1f97bf363947d767b91aaf3c2bbc19db7fda9c65af1278713d58e0a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,6 +15,7 @@ members = [
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
|
"azalea-inventory",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -25,6 +25,7 @@ bevy_ecs = "0.10.0"
|
||||||
bevy_log = "0.10.0"
|
bevy_log = "0.10.0"
|
||||||
bevy_tasks = "0.10.0"
|
bevy_tasks = "0.10.0"
|
||||||
bevy_time = "0.10.0"
|
bevy_time = "0.10.0"
|
||||||
|
azalea-inventory = { path = "../azalea-inventory", version = "0.1.0" }
|
||||||
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
|
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
|
||||||
futures = "0.3.25"
|
futures = "0.3.25"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
|
|
@ -2,11 +2,13 @@ use crate::{
|
||||||
chat::ChatPlugin,
|
chat::ChatPlugin,
|
||||||
disconnect::{DisconnectEvent, DisconnectPlugin},
|
disconnect::{DisconnectEvent, DisconnectPlugin},
|
||||||
events::{Event, EventPlugin, LocalPlayerEvents},
|
events::{Event, EventPlugin, LocalPlayerEvents},
|
||||||
|
interact::{CurrentSequenceNumber, InteractPlugin},
|
||||||
|
inventory::{InventoryComponent, InventoryPlugin},
|
||||||
local_player::{
|
local_player::{
|
||||||
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
|
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
|
||||||
LocalPlayer, PhysicsState, SendPacketEvent,
|
LocalPlayer, PhysicsState, SendPacketEvent,
|
||||||
},
|
},
|
||||||
movement::PlayerMovePlugin,
|
movement::{LastSentLookDirection, PlayerMovePlugin},
|
||||||
packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
|
packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
|
||||||
player::retroactively_add_game_profile_component,
|
player::retroactively_add_game_profile_component,
|
||||||
task_pool::TaskPoolPlugin,
|
task_pool::TaskPoolPlugin,
|
||||||
|
@ -15,11 +17,13 @@ use crate::{
|
||||||
|
|
||||||
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
|
use azalea_core::Vec3;
|
||||||
use azalea_physics::{PhysicsPlugin, PhysicsSet};
|
use azalea_physics::{PhysicsPlugin, PhysicsSet};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
connect::{Connection, ConnectionError},
|
connect::{Connection, ConnectionError},
|
||||||
packets::{
|
packets::{
|
||||||
game::{
|
game::{
|
||||||
|
clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket,
|
||||||
serverbound_client_information_packet::ServerboundClientInformationPacket,
|
serverbound_client_information_packet::ServerboundClientInformationPacket,
|
||||||
ClientboundGamePacket, ServerboundGamePacket,
|
ClientboundGamePacket, ServerboundGamePacket,
|
||||||
},
|
},
|
||||||
|
@ -37,16 +41,17 @@ use azalea_protocol::{
|
||||||
resolver, ServerAddress,
|
resolver, ServerAddress,
|
||||||
};
|
};
|
||||||
use azalea_world::{
|
use azalea_world::{
|
||||||
entity::{EntityPlugin, EntityUpdateSet, Local, WorldName},
|
entity::{EntityPlugin, EntityUpdateSet, Local, Position, WorldName},
|
||||||
Instance, InstanceContainer, PartialInstance,
|
Instance, InstanceContainer, PartialInstance,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, CoreSchedule, Plugin, PluginGroup, PluginGroupBuilder};
|
use bevy_app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
schedule::IntoSystemConfig,
|
schedule::IntoSystemConfig,
|
||||||
schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
|
schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
|
||||||
|
system::{ResMut, Resource},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use bevy_log::LogPlugin;
|
use bevy_log::LogPlugin;
|
||||||
|
@ -56,7 +61,10 @@ use log::{debug, error};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc, time::Duration};
|
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc, time::Duration};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::{sync::mpsc, time};
|
use tokio::{
|
||||||
|
sync::{broadcast, mpsc},
|
||||||
|
time,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// `Client` has the things that a user interacting with the library will want.
|
/// `Client` has the things that a user interacting with the library will want.
|
||||||
|
@ -93,11 +101,50 @@ pub struct Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that contains some of the "settings" for this client that are
|
/// A component that contains some of the "settings" for this client that are
|
||||||
/// sent to the server, such as render distance.
|
/// sent to the server, such as render distance. This is only present on local
|
||||||
|
/// players.
|
||||||
pub type ClientInformation = ServerboundClientInformationPacket;
|
pub type ClientInformation = ServerboundClientInformationPacket;
|
||||||
|
|
||||||
|
/// A component that contains the abilities the player has, like flying
|
||||||
|
/// or instantly breaking blocks. This is only present on local players.
|
||||||
|
#[derive(Clone, Debug, Component, Default)]
|
||||||
|
pub struct PlayerAbilities {
|
||||||
|
pub invulnerable: bool,
|
||||||
|
pub flying: bool,
|
||||||
|
pub can_fly: bool,
|
||||||
|
/// Whether the player can instantly break blocks and can duplicate blocks
|
||||||
|
/// in their inventory.
|
||||||
|
pub instant_break: bool,
|
||||||
|
|
||||||
|
pub flying_speed: f32,
|
||||||
|
/// Used for the fov
|
||||||
|
pub walking_speed: f32,
|
||||||
|
}
|
||||||
|
impl From<ClientboundPlayerAbilitiesPacket> for PlayerAbilities {
|
||||||
|
fn from(packet: ClientboundPlayerAbilitiesPacket) -> Self {
|
||||||
|
Self {
|
||||||
|
invulnerable: packet.flags.invulnerable,
|
||||||
|
flying: packet.flags.flying,
|
||||||
|
can_fly: packet.flags.can_fly,
|
||||||
|
instant_break: packet.flags.instant_break,
|
||||||
|
flying_speed: packet.flying_speed,
|
||||||
|
walking_speed: packet.walking_speed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component that contains a map of player UUIDs to their information in the
|
/// A component that contains a map of player UUIDs to their information in the
|
||||||
/// tab list
|
/// tab list.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use azalea_client::TabList;
|
||||||
|
/// # fn example(client: &azalea_client::Client) {
|
||||||
|
/// let tab_list = client.component::<TabList>();
|
||||||
|
/// println!("Online players:");
|
||||||
|
/// for (uuid, player_info) in tab_list.iter() {
|
||||||
|
/// println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
#[derive(Component, Clone, Debug, Deref, DerefMut, Default)]
|
#[derive(Component, Clone, Debug, Deref, DerefMut, Default)]
|
||||||
pub struct TabList(HashMap<Uuid, PlayerInfo>);
|
pub struct TabList(HashMap<Uuid, PlayerInfo>);
|
||||||
|
|
||||||
|
@ -246,8 +293,12 @@ impl Client {
|
||||||
game_profile: GameProfileComponent(game_profile),
|
game_profile: GameProfileComponent(game_profile),
|
||||||
physics_state: PhysicsState::default(),
|
physics_state: PhysicsState::default(),
|
||||||
local_player_events: LocalPlayerEvents(tx),
|
local_player_events: LocalPlayerEvents(tx),
|
||||||
|
inventory: InventoryComponent::default(),
|
||||||
client_information: ClientInformation::default(),
|
client_information: ClientInformation::default(),
|
||||||
tab_list: TabList::default(),
|
tab_list: TabList::default(),
|
||||||
|
current_sequence_number: CurrentSequenceNumber::default(),
|
||||||
|
last_sent_direction: LastSentLookDirection::default(),
|
||||||
|
abilities: PlayerAbilities::default(),
|
||||||
_local: Local,
|
_local: Local,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -421,6 +472,11 @@ impl Client {
|
||||||
self.query::<&T>(&mut self.ecs.lock()).clone()
|
self.query::<&T>(&mut self.ecs.lock()).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a component from this client, or `None` if it doesn't exist.
|
||||||
|
pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
|
||||||
|
self.query::<Option<&T>>(&mut self.ecs.lock()).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a reference to our (potentially shared) world.
|
/// Get a reference to our (potentially shared) world.
|
||||||
///
|
///
|
||||||
/// This gets the [`Instance`] from our world container. If it's a normal
|
/// This gets the [`Instance`] from our world container. If it's a normal
|
||||||
|
@ -430,8 +486,8 @@ impl Client {
|
||||||
pub fn world(&self) -> Arc<RwLock<Instance>> {
|
pub fn world(&self) -> Arc<RwLock<Instance>> {
|
||||||
let world_name = self.component::<WorldName>();
|
let world_name = self.component::<WorldName>();
|
||||||
let ecs = self.ecs.lock();
|
let ecs = self.ecs.lock();
|
||||||
let world_container = ecs.resource::<InstanceContainer>();
|
let instance_container = ecs.resource::<InstanceContainer>();
|
||||||
world_container.get(&world_name).unwrap()
|
instance_container.get(&world_name).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether we have a received the login packet yet.
|
/// Returns whether we have a received the login packet yet.
|
||||||
|
@ -478,6 +534,15 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Get the position of this client.
|
||||||
|
///
|
||||||
|
/// This is a shortcut for `Vec3::from(&bot.component::<Position>())`.
|
||||||
|
pub fn position(&self) -> Vec3 {
|
||||||
|
Vec3::from(&self.component::<Position>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A bundle for the components that are present on a local player that received
|
/// A bundle for the components that are present on a local player that received
|
||||||
/// a login packet. If you want to filter for this, just use [`Local`].
|
/// a login packet. If you want to filter for this, just use [`Local`].
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
|
@ -487,8 +552,12 @@ pub struct JoinedClientBundle {
|
||||||
pub game_profile: GameProfileComponent,
|
pub game_profile: GameProfileComponent,
|
||||||
pub physics_state: PhysicsState,
|
pub physics_state: PhysicsState,
|
||||||
pub local_player_events: LocalPlayerEvents,
|
pub local_player_events: LocalPlayerEvents,
|
||||||
|
pub inventory: InventoryComponent,
|
||||||
pub client_information: ClientInformation,
|
pub client_information: ClientInformation,
|
||||||
pub tab_list: TabList,
|
pub tab_list: TabList,
|
||||||
|
pub current_sequence_number: CurrentSequenceNumber,
|
||||||
|
pub last_sent_direction: LastSentLookDirection,
|
||||||
|
pub abilities: PlayerAbilities,
|
||||||
pub _local: Local,
|
pub _local: Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,11 +567,7 @@ impl Plugin for AzaleaPlugin {
|
||||||
// Minecraft ticks happen every 50ms
|
// Minecraft ticks happen every 50ms
|
||||||
app.insert_resource(FixedTime::new(Duration::from_millis(50)));
|
app.insert_resource(FixedTime::new(Duration::from_millis(50)));
|
||||||
|
|
||||||
app.add_system(
|
app.add_system(update_in_loaded_chunk.after(PhysicsSet));
|
||||||
update_in_loaded_chunk
|
|
||||||
.after(PhysicsSet)
|
|
||||||
.after(handle_send_packet_event),
|
|
||||||
);
|
|
||||||
|
|
||||||
// fire the Death event when the player dies.
|
// fire the Death event when the player dies.
|
||||||
app.add_system(death_event);
|
app.add_system(death_event);
|
||||||
|
@ -599,6 +664,39 @@ pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::UnboundedSender<(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A resource that contains a [`broadcast::Sender`] that will be sent every
|
||||||
|
/// Minecraft tick.
|
||||||
|
///
|
||||||
|
/// This is useful for running code every schedule from async user code.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use azalea_client::TickBroadcast;
|
||||||
|
/// # async fn example(client: azalea_client::Client) {
|
||||||
|
/// let mut receiver = {
|
||||||
|
/// let ecs = client.ecs.lock();
|
||||||
|
/// let tick_broadcast = ecs.resource::<TickBroadcast>();
|
||||||
|
/// tick_broadcast.subscribe()
|
||||||
|
/// };
|
||||||
|
/// while receiver.recv().await.is_ok() {
|
||||||
|
/// // do something
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
pub struct TickBroadcast(broadcast::Sender<()>);
|
||||||
|
|
||||||
|
fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
|
||||||
|
let _ = tick_broadcast.0.send(());
|
||||||
|
}
|
||||||
|
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
||||||
|
pub struct TickBroadcastPlugin;
|
||||||
|
impl Plugin for TickBroadcastPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(TickBroadcast(broadcast::channel(1).0))
|
||||||
|
.add_system(send_tick_broadcast.in_schedule(CoreSchedule::FixedUpdate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This plugin group will add all the default plugins necessary for Azalea to
|
/// This plugin group will add all the default plugins necessary for Azalea to
|
||||||
/// work.
|
/// work.
|
||||||
pub struct DefaultPlugins;
|
pub struct DefaultPlugins;
|
||||||
|
@ -614,8 +712,11 @@ impl PluginGroup for DefaultPlugins {
|
||||||
.add(PhysicsPlugin)
|
.add(PhysicsPlugin)
|
||||||
.add(EventPlugin)
|
.add(EventPlugin)
|
||||||
.add(TaskPoolPlugin::default())
|
.add(TaskPoolPlugin::default())
|
||||||
|
.add(InventoryPlugin)
|
||||||
.add(ChatPlugin)
|
.add(ChatPlugin)
|
||||||
.add(DisconnectPlugin)
|
.add(DisconnectPlugin)
|
||||||
.add(PlayerMovePlugin)
|
.add(PlayerMovePlugin)
|
||||||
|
.add(InteractPlugin)
|
||||||
|
.add(TickBroadcastPlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
200
azalea-client/src/interact.rs
Normal file
200
azalea-client/src/interact.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
||||||
|
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||||
|
use azalea_protocol::packets::game::{
|
||||||
|
serverbound_interact_packet::InteractionHand,
|
||||||
|
serverbound_use_item_on_packet::{BlockHit, ServerboundUseItemOnPacket},
|
||||||
|
};
|
||||||
|
use azalea_world::{
|
||||||
|
entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position, WorldName},
|
||||||
|
InstanceContainer,
|
||||||
|
};
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
event::EventReader,
|
||||||
|
schedule::{IntoSystemConfig, IntoSystemConfigs},
|
||||||
|
system::{Commands, Query, Res},
|
||||||
|
};
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
local_player::{handle_send_packet_event, LocalGameMode},
|
||||||
|
Client, LocalPlayer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A plugin that allows clients to interact with blocks in the world.
|
||||||
|
pub struct InteractPlugin;
|
||||||
|
impl Plugin for InteractPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_event::<BlockInteractEvent>().add_systems(
|
||||||
|
(
|
||||||
|
update_hit_result_component.after(clamp_look_direction),
|
||||||
|
handle_block_interact_event,
|
||||||
|
)
|
||||||
|
.before(handle_send_packet_event)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Right click a block. The behavior of this depends on the target block,
|
||||||
|
/// and it'll either place the block you're holding in your hand or use the
|
||||||
|
/// block you clicked (like toggling a lever).
|
||||||
|
///
|
||||||
|
/// Note that this may trigger anticheats as it doesn't take into account
|
||||||
|
/// whether you're actually looking at the block.
|
||||||
|
pub fn block_interact(&mut self, position: BlockPos) {
|
||||||
|
self.ecs.lock().send_event(BlockInteractEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Right click a block. The behavior of this depends on the target block,
|
||||||
|
/// and it'll either place the block you're holding in your hand or use the
|
||||||
|
/// block you clicked (like toggling a lever).
|
||||||
|
pub struct BlockInteractEvent {
|
||||||
|
/// The local player entity that's opening the container.
|
||||||
|
pub entity: Entity,
|
||||||
|
/// The coordinates of the container.
|
||||||
|
pub position: BlockPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that contains the number of changes this client has made to
|
||||||
|
/// blocks.
|
||||||
|
#[derive(Component, Copy, Clone, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct CurrentSequenceNumber(u32);
|
||||||
|
|
||||||
|
/// A component that contains the block that the player is currently looking at.
|
||||||
|
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||||
|
pub struct HitResultComponent(BlockHitResult);
|
||||||
|
|
||||||
|
fn handle_block_interact_event(
|
||||||
|
mut events: EventReader<BlockInteractEvent>,
|
||||||
|
mut query: Query<(
|
||||||
|
&LocalPlayer,
|
||||||
|
&mut CurrentSequenceNumber,
|
||||||
|
&HitResultComponent,
|
||||||
|
)>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let Ok((local_player, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
|
||||||
|
warn!("Sent BlockInteractEvent for entity that isn't LocalPlayer");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: check to make sure we're within the world border
|
||||||
|
|
||||||
|
**sequence_number += 1;
|
||||||
|
|
||||||
|
// minecraft also does the interaction client-side (so it looks like clicking a
|
||||||
|
// button is instant) but we don't really need that
|
||||||
|
|
||||||
|
// the block_hit data will depend on whether we're looking at the block and
|
||||||
|
// whether we can reach it
|
||||||
|
|
||||||
|
let block_hit = if hit_result.block_pos == event.position {
|
||||||
|
// we're looking at the block :)
|
||||||
|
BlockHit {
|
||||||
|
block_pos: hit_result.block_pos,
|
||||||
|
direction: hit_result.direction,
|
||||||
|
location: hit_result.location,
|
||||||
|
inside: hit_result.inside,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we're not looking at the block, so make up some numbers
|
||||||
|
BlockHit {
|
||||||
|
block_pos: event.position,
|
||||||
|
direction: Direction::Up,
|
||||||
|
location: event.position.center(),
|
||||||
|
inside: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
local_player.write_packet(
|
||||||
|
ServerboundUseItemOnPacket {
|
||||||
|
hand: InteractionHand::MainHand,
|
||||||
|
block_hit,
|
||||||
|
sequence: sequence_number.0,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn update_hit_result_component(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<(
|
||||||
|
Entity,
|
||||||
|
Option<&mut HitResultComponent>,
|
||||||
|
&LocalGameMode,
|
||||||
|
&Position,
|
||||||
|
&EyeHeight,
|
||||||
|
&LookDirection,
|
||||||
|
&WorldName,
|
||||||
|
)>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (entity, hit_result_ref, game_mode, position, eye_height, look_direction, world_name) in
|
||||||
|
&mut query
|
||||||
|
{
|
||||||
|
let pick_range = if game_mode.current == GameMode::Creative {
|
||||||
|
6.
|
||||||
|
} else {
|
||||||
|
4.5
|
||||||
|
};
|
||||||
|
let eye_position = Vec3 {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y + **eye_height as f64,
|
||||||
|
z: position.z,
|
||||||
|
};
|
||||||
|
let hit_result = pick(
|
||||||
|
look_direction,
|
||||||
|
&eye_position,
|
||||||
|
world_name,
|
||||||
|
&instance_container,
|
||||||
|
pick_range,
|
||||||
|
);
|
||||||
|
if let Some(mut hit_result_ref) = hit_result_ref {
|
||||||
|
**hit_result_ref = hit_result;
|
||||||
|
} else {
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(HitResultComponent(hit_result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the block that a player would be looking at if their eyes were at the
|
||||||
|
/// given direction and position.
|
||||||
|
///
|
||||||
|
/// If you need to get the block the player is looking at right now, use
|
||||||
|
/// [`HitResultComponent`].
|
||||||
|
pub fn pick(
|
||||||
|
look_direction: &LookDirection,
|
||||||
|
eye_position: &Vec3,
|
||||||
|
world_name: &WorldName,
|
||||||
|
instance_container: &InstanceContainer,
|
||||||
|
pick_range: f64,
|
||||||
|
) -> BlockHitResult {
|
||||||
|
let view_vector = view_vector(look_direction);
|
||||||
|
let end_position = eye_position + &(view_vector * pick_range);
|
||||||
|
let instance_lock = instance_container
|
||||||
|
.get(world_name)
|
||||||
|
.expect("entities must always be in a valid world");
|
||||||
|
let instance = instance_lock.read();
|
||||||
|
azalea_physics::clip::clip(
|
||||||
|
&instance.chunks,
|
||||||
|
ClipContext {
|
||||||
|
from: *eye_position,
|
||||||
|
to: end_position,
|
||||||
|
block_shape_type: BlockShapeType::Outline,
|
||||||
|
fluid_pick_type: FluidPickType::None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
721
azalea-client/src/inventory.rs
Normal file
721
azalea-client/src/inventory.rs
Normal file
|
@ -0,0 +1,721 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use azalea_chat::FormattedText;
|
||||||
|
pub use azalea_inventory::*;
|
||||||
|
use azalea_inventory::{
|
||||||
|
item::MaxStackSizeExt,
|
||||||
|
operations::{
|
||||||
|
ClickOperation, CloneClick, PickupAllClick, PickupClick, QuickCraftKind, QuickCraftStatus,
|
||||||
|
QuickCraftStatusKind, QuickMoveClick, ThrowClick,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use azalea_protocol::packets::game::{
|
||||||
|
serverbound_container_click_packet::ServerboundContainerClickPacket,
|
||||||
|
serverbound_container_close_packet::ServerboundContainerClosePacket,
|
||||||
|
};
|
||||||
|
use azalea_registry::MenuKind;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
event::EventReader,
|
||||||
|
prelude::EventWriter,
|
||||||
|
schedule::{IntoSystemConfig, IntoSystemConfigs},
|
||||||
|
system::Query,
|
||||||
|
};
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
use crate::{client::PlayerAbilities, local_player::handle_send_packet_event, Client, LocalPlayer};
|
||||||
|
|
||||||
|
pub struct InventoryPlugin;
|
||||||
|
impl Plugin for InventoryPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_event::<ClientSideCloseContainerEvent>()
|
||||||
|
.add_event::<MenuOpenedEvent>()
|
||||||
|
.add_event::<CloseContainerEvent>()
|
||||||
|
.add_event::<ContainerClickEvent>()
|
||||||
|
.add_event::<SetContainerContentEvent>()
|
||||||
|
.add_systems(
|
||||||
|
(
|
||||||
|
handle_menu_opened_event,
|
||||||
|
handle_set_container_content_event,
|
||||||
|
handle_container_click_event,
|
||||||
|
handle_container_close_event.before(handle_send_packet_event),
|
||||||
|
handle_client_side_close_container_event,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Return the menu that is currently open. If no menu is open, this will
|
||||||
|
/// have the player's inventory.
|
||||||
|
pub fn menu(&self) -> Menu {
|
||||||
|
let mut ecs = self.ecs.lock();
|
||||||
|
let inventory = self.query::<&InventoryComponent>(&mut ecs);
|
||||||
|
inventory.menu().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component present on all local players that have an inventory.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct InventoryComponent {
|
||||||
|
/// A component that contains the player's inventory menu. This is
|
||||||
|
/// guaranteed to be a `Menu::Player`.
|
||||||
|
///
|
||||||
|
/// We keep it as a [`Menu`] since `Menu` has some useful functions that
|
||||||
|
/// bare [`azalea_inventory::Player`] doesn't have.
|
||||||
|
pub inventory_menu: azalea_inventory::Menu,
|
||||||
|
|
||||||
|
/// The ID of the container that's currently open. Its value is not
|
||||||
|
/// guaranteed to be anything specific, and may change every time you open a
|
||||||
|
/// container (unless it's 0, in which case it means that no container is
|
||||||
|
/// open).
|
||||||
|
pub id: u8,
|
||||||
|
/// The current container menu that the player has open. If no container is
|
||||||
|
/// open, this will be `None`.
|
||||||
|
pub container_menu: Option<azalea_inventory::Menu>,
|
||||||
|
/// The item that is currently held by the cursor. `Slot::Empty` if nothing
|
||||||
|
/// is currently being held.
|
||||||
|
pub carried: ItemSlot,
|
||||||
|
/// An identifier used by the server to track client inventory desyncs. This
|
||||||
|
/// is sent on every container click, and it's only ever updated when the
|
||||||
|
/// server sends a new container update.
|
||||||
|
pub state_id: u32,
|
||||||
|
|
||||||
|
pub quick_craft_status: QuickCraftStatusKind,
|
||||||
|
pub quick_craft_kind: QuickCraftKind,
|
||||||
|
/// A set of the indexes of the slots that have been right clicked in
|
||||||
|
/// this "quick craft".
|
||||||
|
pub quick_craft_slots: HashSet<u16>,
|
||||||
|
// minecraft also has these fields, but i don't
|
||||||
|
// think they're necessary?:
|
||||||
|
// private final NonNullList<ItemStack>
|
||||||
|
// remoteSlots;
|
||||||
|
// private final IntList remoteDataSlots;
|
||||||
|
// private ItemStack remoteCarried;
|
||||||
|
}
|
||||||
|
impl InventoryComponent {
|
||||||
|
/// Returns a reference to the currently active menu. If a container is open
|
||||||
|
/// it'll return [`Self::container_menu`], otherwise
|
||||||
|
/// [`Self::inventory_menu`].
|
||||||
|
///
|
||||||
|
/// Use [`Self::menu_mut`] if you need a mutable reference.
|
||||||
|
pub fn menu(&self) -> &azalea_inventory::Menu {
|
||||||
|
if let Some(menu) = &self.container_menu {
|
||||||
|
menu
|
||||||
|
} else {
|
||||||
|
&self.inventory_menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the currently active menu. If a container
|
||||||
|
/// is open it'll return [`Self::container_menu`], otherwise
|
||||||
|
/// [`Self::inventory_menu`].
|
||||||
|
///
|
||||||
|
/// Use [`Self::menu`] if you don't need a mutable reference.
|
||||||
|
pub fn menu_mut(&mut self) -> &mut azalea_inventory::Menu {
|
||||||
|
if let Some(menu) = &mut self.container_menu {
|
||||||
|
menu
|
||||||
|
} else {
|
||||||
|
&mut self.inventory_menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modify the inventory as if the given operation was performed on it.
|
||||||
|
pub fn simulate_click(
|
||||||
|
&mut self,
|
||||||
|
operation: &ClickOperation,
|
||||||
|
player_abilities: &PlayerAbilities,
|
||||||
|
) {
|
||||||
|
if let ClickOperation::QuickCraft(quick_craft) = operation {
|
||||||
|
let last_quick_craft_status_tmp = self.quick_craft_status.clone();
|
||||||
|
self.quick_craft_status = last_quick_craft_status_tmp.clone();
|
||||||
|
let last_quick_craft_status = last_quick_craft_status_tmp;
|
||||||
|
|
||||||
|
// no carried item, reset
|
||||||
|
if self.carried.is_empty() {
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
}
|
||||||
|
// if we were starting or ending, or now we aren't ending and the status
|
||||||
|
// changed, reset
|
||||||
|
if (last_quick_craft_status == QuickCraftStatusKind::Start
|
||||||
|
|| last_quick_craft_status == QuickCraftStatusKind::End
|
||||||
|
|| self.quick_craft_status != QuickCraftStatusKind::End)
|
||||||
|
&& (self.quick_craft_status != last_quick_craft_status)
|
||||||
|
{
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
}
|
||||||
|
if self.quick_craft_status == QuickCraftStatusKind::Start {
|
||||||
|
self.quick_craft_kind = quick_craft.kind.clone();
|
||||||
|
if self.quick_craft_kind == QuickCraftKind::Middle && player_abilities.instant_break
|
||||||
|
{
|
||||||
|
self.quick_craft_status = QuickCraftStatusKind::Add;
|
||||||
|
self.quick_craft_slots.clear();
|
||||||
|
} else {
|
||||||
|
self.reset_quick_craft();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let QuickCraftStatus::Add { slot } = quick_craft.status {
|
||||||
|
let slot_item = self.menu().slot(slot as usize);
|
||||||
|
if let Some(slot_item) = slot_item {
|
||||||
|
if let ItemSlot::Present(carried) = &self.carried {
|
||||||
|
// minecraft also checks slot.may_place(carried) and
|
||||||
|
// menu.can_drag_to(slot)
|
||||||
|
// but they always return true so they're not relevant for us
|
||||||
|
if can_item_quick_replace(slot_item, &self.carried, true)
|
||||||
|
&& (self.quick_craft_kind == QuickCraftKind::Right
|
||||||
|
|| carried.count as usize > self.quick_craft_slots.len())
|
||||||
|
{
|
||||||
|
self.quick_craft_slots.insert(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.quick_craft_status == QuickCraftStatusKind::End {
|
||||||
|
if !self.quick_craft_slots.is_empty() {
|
||||||
|
if self.quick_craft_slots.len() == 1 {
|
||||||
|
// if we only clicked one slot, then turn this
|
||||||
|
// QuickCraftClick into a PickupClick
|
||||||
|
let slot = *self.quick_craft_slots.iter().next().unwrap();
|
||||||
|
self.reset_quick_craft();
|
||||||
|
self.simulate_click(
|
||||||
|
&match self.quick_craft_kind {
|
||||||
|
QuickCraftKind::Left => {
|
||||||
|
PickupClick::Left { slot: Some(slot) }.into()
|
||||||
|
}
|
||||||
|
QuickCraftKind::Right => {
|
||||||
|
PickupClick::Left { slot: Some(slot) }.into()
|
||||||
|
}
|
||||||
|
QuickCraftKind::Middle => {
|
||||||
|
// idk just do nothing i guess
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
player_abilities,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ItemSlot::Present(mut carried) = self.carried.clone() else {
|
||||||
|
// this should never happen
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut carried_count = carried.count;
|
||||||
|
let mut quick_craft_slots_iter = self.quick_craft_slots.iter();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut slot: &ItemSlot;
|
||||||
|
let mut slot_index: u16;
|
||||||
|
let mut item_stack: &ItemSlot;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(&next_slot) = quick_craft_slots_iter.next() else {
|
||||||
|
carried.count = carried_count;
|
||||||
|
self.carried = ItemSlot::Present(carried);
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
};
|
||||||
|
|
||||||
|
slot = self.menu().slot(next_slot as usize).unwrap();
|
||||||
|
slot_index = next_slot;
|
||||||
|
item_stack = &self.carried;
|
||||||
|
|
||||||
|
if slot.is_present()
|
||||||
|
&& can_item_quick_replace(slot, item_stack, true)
|
||||||
|
// this always returns true in most cases
|
||||||
|
// && slot.may_place(item_stack)
|
||||||
|
&& (
|
||||||
|
self.quick_craft_kind == QuickCraftKind::Middle
|
||||||
|
|| item_stack.count() as i32 >= self.quick_craft_slots.len() as i32
|
||||||
|
)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the ItemSlotData for the slot
|
||||||
|
let ItemSlot::Present(slot) = slot else {
|
||||||
|
unreachable!("the loop above requires the slot to be present to break")
|
||||||
|
};
|
||||||
|
|
||||||
|
// if self.can_drag_to(slot) {
|
||||||
|
let mut new_carried = carried.clone();
|
||||||
|
let slot_item_count = slot.count;
|
||||||
|
get_quick_craft_slot_count(
|
||||||
|
&self.quick_craft_slots,
|
||||||
|
&self.quick_craft_kind,
|
||||||
|
&mut new_carried,
|
||||||
|
slot_item_count,
|
||||||
|
);
|
||||||
|
let max_stack_size = i8::min(
|
||||||
|
new_carried.kind.max_stack_size(),
|
||||||
|
i8::min(
|
||||||
|
new_carried.kind.max_stack_size(),
|
||||||
|
slot.kind.max_stack_size(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if new_carried.count > max_stack_size {
|
||||||
|
new_carried.count = max_stack_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
carried_count -= new_carried.count - slot_item_count;
|
||||||
|
// we have to inline self.menu_mut() here to avoid the borrow checker
|
||||||
|
// complaining
|
||||||
|
let menu = if let Some(menu) = &mut self.container_menu {
|
||||||
|
menu
|
||||||
|
} else {
|
||||||
|
&mut self.inventory_menu
|
||||||
|
};
|
||||||
|
*menu.slot_mut(slot_index as usize).unwrap() =
|
||||||
|
ItemSlot::Present(new_carried);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the quick craft status should always be in start if we're not in quick craft
|
||||||
|
// mode
|
||||||
|
if self.quick_craft_status != QuickCraftStatusKind::Start {
|
||||||
|
return self.reset_quick_craft();
|
||||||
|
}
|
||||||
|
|
||||||
|
match operation {
|
||||||
|
// left clicking outside inventory
|
||||||
|
ClickOperation::Pickup(PickupClick::Left { slot: None }) => {
|
||||||
|
if self.carried.is_present() {
|
||||||
|
// vanilla has `player.drop`s but they're only used
|
||||||
|
// server-side
|
||||||
|
// they're included as comments here in case you want to adapt this for a server
|
||||||
|
// implementation
|
||||||
|
|
||||||
|
// player.drop(self.carried, true);
|
||||||
|
self.carried = ItemSlot::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClickOperation::Pickup(PickupClick::Right { slot: None }) => {
|
||||||
|
if self.carried.is_present() {
|
||||||
|
let _item = self.carried.split(1);
|
||||||
|
// player.drop(item, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClickOperation::Pickup(
|
||||||
|
PickupClick::Left { slot: Some(slot) } | PickupClick::Right { slot: Some(slot) },
|
||||||
|
) => {
|
||||||
|
let Some(slot_item) = self.menu().slot(*slot as usize) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let carried = &self.carried;
|
||||||
|
// vanilla does a check called tryItemClickBehaviourOverride
|
||||||
|
// here
|
||||||
|
// i don't understand it so i didn't implement it
|
||||||
|
match slot_item {
|
||||||
|
ItemSlot::Empty => if carried.is_present() {},
|
||||||
|
ItemSlot::Present(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClickOperation::QuickMove(
|
||||||
|
QuickMoveClick::Left { slot } | QuickMoveClick::Right { slot },
|
||||||
|
) => {
|
||||||
|
// in vanilla it also tests if QuickMove has a slot index of -999
|
||||||
|
// but i don't think that's ever possible so it's not covered here
|
||||||
|
loop {
|
||||||
|
let new_slot_item = self.menu_mut().quick_move_stack(*slot as usize);
|
||||||
|
let slot_item = self.menu().slot(*slot as usize).unwrap();
|
||||||
|
if new_slot_item.is_empty() || slot_item != &new_slot_item {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClickOperation::Swap(s) => {
|
||||||
|
let source_slot_index = s.source_slot as usize;
|
||||||
|
let target_slot_index = s.target_slot as usize;
|
||||||
|
|
||||||
|
let Some(source_slot) = self.menu().slot(source_slot_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(target_slot) = self.menu().slot(target_slot_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if source_slot.is_empty() && target_slot.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_slot.is_empty() {
|
||||||
|
if self.menu().may_pickup(source_slot_index) {
|
||||||
|
let source_slot = source_slot.clone();
|
||||||
|
let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
|
||||||
|
*target_slot = source_slot;
|
||||||
|
}
|
||||||
|
} else if source_slot.is_empty() {
|
||||||
|
let ItemSlot::Present(target_item) = target_slot else {
|
||||||
|
unreachable!("target slot is not empty but is not present");
|
||||||
|
};
|
||||||
|
if self.menu().may_place(source_slot_index, target_item) {
|
||||||
|
// get the target_item but mutable
|
||||||
|
let source_max_stack_size = self.menu().max_stack_size(source_slot_index);
|
||||||
|
|
||||||
|
let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
|
||||||
|
let new_source_slot = target_slot.split(source_max_stack_size);
|
||||||
|
*self.menu_mut().slot_mut(source_slot_index).unwrap() = new_source_slot;
|
||||||
|
}
|
||||||
|
} else if self.menu().may_pickup(source_slot_index) {
|
||||||
|
let ItemSlot::Present(target_item) = target_slot else {
|
||||||
|
unreachable!("target slot is not empty but is not present");
|
||||||
|
};
|
||||||
|
if self.menu().may_place(source_slot_index, target_item) {
|
||||||
|
let source_max_stack = self.menu().max_stack_size(source_slot_index);
|
||||||
|
if target_slot.count() > source_max_stack as i8 {
|
||||||
|
// if there's more than the max stack size in the target slot
|
||||||
|
|
||||||
|
let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
|
||||||
|
let new_source_slot = target_slot.split(source_max_stack);
|
||||||
|
*self.menu_mut().slot_mut(source_slot_index).unwrap() = new_source_slot;
|
||||||
|
// if !self.inventory_menu.add(new_source_slot) {
|
||||||
|
// player.drop(new_source_slot, true);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// normal swap
|
||||||
|
let new_target_slot = source_slot.clone();
|
||||||
|
let new_source_slot = target_slot.clone();
|
||||||
|
|
||||||
|
let target_slot = self.menu_mut().slot_mut(target_slot_index).unwrap();
|
||||||
|
*target_slot = new_target_slot;
|
||||||
|
|
||||||
|
let source_slot = self.menu_mut().slot_mut(source_slot_index).unwrap();
|
||||||
|
*source_slot = new_source_slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClickOperation::Clone(CloneClick { slot }) => {
|
||||||
|
if !player_abilities.instant_break || self.carried.is_present() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(source_slot) = self.menu().slot(*slot as usize) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ItemSlot::Present(source_item) = source_slot else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut new_carried = source_item.clone();
|
||||||
|
new_carried.count = new_carried.kind.max_stack_size();
|
||||||
|
self.carried = ItemSlot::Present(new_carried);
|
||||||
|
}
|
||||||
|
ClickOperation::Throw(c) => {
|
||||||
|
if self.carried.is_present() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ThrowClick::Single { slot: slot_index }
|
||||||
|
| ThrowClick::All { slot: slot_index }) = c;
|
||||||
|
let slot_index = *slot_index as usize;
|
||||||
|
|
||||||
|
let Some(slot) = self.menu_mut().slot_mut(slot_index) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ItemSlot::Present(slot_item) = slot else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dropping_count = match c {
|
||||||
|
ThrowClick::Single { .. } => 1,
|
||||||
|
ThrowClick::All { .. } => slot_item.count,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _dropping = slot_item.split(dropping_count as u8);
|
||||||
|
// player.drop(dropping, true);
|
||||||
|
}
|
||||||
|
ClickOperation::PickupAll(PickupAllClick {
|
||||||
|
slot: source_slot_index,
|
||||||
|
reversed,
|
||||||
|
}) => {
|
||||||
|
let source_slot_index = *source_slot_index as usize;
|
||||||
|
|
||||||
|
let source_slot = self.menu().slot(source_slot_index).unwrap();
|
||||||
|
let target_slot = self.carried.clone();
|
||||||
|
|
||||||
|
if target_slot.is_empty()
|
||||||
|
|| (source_slot.is_present() && self.menu().may_pickup(source_slot_index))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ItemSlot::Present(target_slot_item) = &target_slot else {
|
||||||
|
unreachable!("target slot is not empty but is not present");
|
||||||
|
};
|
||||||
|
|
||||||
|
for round in 0..2 {
|
||||||
|
let iterator: Box<dyn Iterator<Item = usize>> = if *reversed {
|
||||||
|
Box::new((0..self.menu().len()).rev())
|
||||||
|
} else {
|
||||||
|
Box::new(0..self.menu().len())
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in iterator {
|
||||||
|
if target_slot_item.count < target_slot_item.kind.max_stack_size() {
|
||||||
|
let checking_slot = self.menu().slot(i).unwrap();
|
||||||
|
if let ItemSlot::Present(checking_item) = checking_slot {
|
||||||
|
if can_item_quick_replace(checking_slot, &target_slot, true)
|
||||||
|
&& self.menu().may_pickup(i)
|
||||||
|
&& (round != 0
|
||||||
|
|| checking_item.count
|
||||||
|
!= checking_item.kind.max_stack_size())
|
||||||
|
{
|
||||||
|
// get the checking_slot and checking_item again but mutable
|
||||||
|
let checking_slot = self.menu_mut().slot_mut(i).unwrap();
|
||||||
|
|
||||||
|
let taken_item =
|
||||||
|
checking_slot.split(checking_slot.count() as u8);
|
||||||
|
|
||||||
|
// now extend the carried item
|
||||||
|
let target_slot = &mut self.carried;
|
||||||
|
let ItemSlot::Present(target_slot_item) = target_slot else {
|
||||||
|
unreachable!("target slot is not empty but is not present");
|
||||||
|
};
|
||||||
|
target_slot_item.count += taken_item.count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_quick_craft(&mut self) {
|
||||||
|
self.quick_craft_status = QuickCraftStatusKind::Start;
|
||||||
|
self.quick_craft_slots.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_item_quick_replace(
|
||||||
|
target_slot: &ItemSlot,
|
||||||
|
item: &ItemSlot,
|
||||||
|
ignore_item_count: bool,
|
||||||
|
) -> bool {
|
||||||
|
let ItemSlot::Present(target_slot) = target_slot else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let ItemSlot::Present(item) = item else {
|
||||||
|
// i *think* this is what vanilla does
|
||||||
|
// not 100% sure lol probably doesn't matter though
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !item.is_same_item_and_nbt(target_slot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let count = target_slot.count as u16
|
||||||
|
+ if ignore_item_count {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
item.count as u16
|
||||||
|
};
|
||||||
|
count <= item.kind.max_stack_size() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static void getQuickCraftSlotCount(Set<Slot> quickCraftSlots, int
|
||||||
|
// quickCraftType, ItemStack itemStack, int var3) {
|
||||||
|
// switch (quickCraftType) {
|
||||||
|
// case 0:
|
||||||
|
// itemStack.setCount(Mth.floor((float) itemStack.getCount() / (float)
|
||||||
|
// quickCraftSlots.size())); break;
|
||||||
|
// case 1:
|
||||||
|
// itemStack.setCount(1);
|
||||||
|
// break;
|
||||||
|
// case 2:
|
||||||
|
// itemStack.setCount(itemStack.getItem().getMaxStackSize());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// itemStack.grow(var3);
|
||||||
|
// }
|
||||||
|
fn get_quick_craft_slot_count(
|
||||||
|
quick_craft_slots: &HashSet<u16>,
|
||||||
|
quick_craft_kind: &QuickCraftKind,
|
||||||
|
item: &mut ItemSlotData,
|
||||||
|
slot_item_count: i8,
|
||||||
|
) {
|
||||||
|
item.count = match quick_craft_kind {
|
||||||
|
QuickCraftKind::Left => item.count / quick_craft_slots.len() as i8,
|
||||||
|
QuickCraftKind::Right => 1,
|
||||||
|
QuickCraftKind::Middle => item.kind.max_stack_size(),
|
||||||
|
};
|
||||||
|
item.count += slot_item_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InventoryComponent {
|
||||||
|
fn default() -> Self {
|
||||||
|
InventoryComponent {
|
||||||
|
inventory_menu: Menu::Player(azalea_inventory::Player::default()),
|
||||||
|
id: 0,
|
||||||
|
container_menu: None,
|
||||||
|
carried: ItemSlot::Empty,
|
||||||
|
state_id: 0,
|
||||||
|
quick_craft_status: QuickCraftStatusKind::Start,
|
||||||
|
quick_craft_kind: QuickCraftKind::Middle,
|
||||||
|
quick_craft_slots: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sent from the server when a menu (like a chest or crafting table) was
|
||||||
|
/// opened by the client.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MenuOpenedEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub window_id: u32,
|
||||||
|
pub menu_type: MenuKind,
|
||||||
|
pub title: FormattedText,
|
||||||
|
}
|
||||||
|
fn handle_menu_opened_event(
|
||||||
|
mut events: EventReader<MenuOpenedEvent>,
|
||||||
|
mut query: Query<&mut InventoryComponent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let mut inventory = query.get_mut(event.entity).unwrap();
|
||||||
|
inventory.id = event.window_id as u8;
|
||||||
|
inventory.container_menu = Some(Menu::from_kind(event.menu_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tell the server that we want to close a container.
|
||||||
|
///
|
||||||
|
/// Note that this is also sent when the client closes its own inventory, even
|
||||||
|
/// though there is no packet for opening its inventory.
|
||||||
|
pub struct CloseContainerEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
/// The ID of the container to close. 0 for the player's inventory. If this
|
||||||
|
/// is not the same as the currently open inventory, nothing will happen.
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
fn handle_container_close_event(
|
||||||
|
mut events: EventReader<CloseContainerEvent>,
|
||||||
|
mut client_side_events: EventWriter<ClientSideCloseContainerEvent>,
|
||||||
|
query: Query<(&LocalPlayer, &InventoryComponent)>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (local_player, inventory) = query.get(event.entity).unwrap();
|
||||||
|
if event.id != inventory.id {
|
||||||
|
warn!(
|
||||||
|
"Tried to close container with ID {}, but the current container ID is {}",
|
||||||
|
event.id, inventory.id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
local_player.write_packet(
|
||||||
|
ServerboundContainerClosePacket {
|
||||||
|
container_id: inventory.id,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
);
|
||||||
|
client_side_events.send(ClientSideCloseContainerEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close a container without notifying the server.
|
||||||
|
///
|
||||||
|
/// Note that this also gets fired when we get a [`CloseContainerEvent`].
|
||||||
|
pub struct ClientSideCloseContainerEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
}
|
||||||
|
fn handle_client_side_close_container_event(
|
||||||
|
mut events: EventReader<ClientSideCloseContainerEvent>,
|
||||||
|
mut query: Query<&mut InventoryComponent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let mut inventory = query.get_mut(event.entity).unwrap();
|
||||||
|
inventory.container_menu = None;
|
||||||
|
inventory.id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ContainerClickEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub window_id: u8,
|
||||||
|
pub operation: ClickOperation,
|
||||||
|
}
|
||||||
|
fn handle_container_click_event(
|
||||||
|
mut events: EventReader<ContainerClickEvent>,
|
||||||
|
mut query: Query<(&mut InventoryComponent, &LocalPlayer)>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (mut inventory, local_player) = query.get_mut(event.entity).unwrap();
|
||||||
|
if inventory.id != event.window_id {
|
||||||
|
warn!(
|
||||||
|
"Tried to click container with ID {}, but the current container ID is {}",
|
||||||
|
event.window_id, inventory.id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let menu = inventory.menu_mut();
|
||||||
|
let old_slots = menu.slots().clone();
|
||||||
|
|
||||||
|
// menu.click(&event.operation);
|
||||||
|
|
||||||
|
// see which slots changed after clicking and put them in the hashmap
|
||||||
|
// the server uses this to check if we desynced
|
||||||
|
let mut changed_slots: HashMap<u16, ItemSlot> = HashMap::new();
|
||||||
|
for (slot_index, old_slot) in old_slots.iter().enumerate() {
|
||||||
|
let new_slot = &menu.slots()[slot_index];
|
||||||
|
if old_slot != new_slot {
|
||||||
|
changed_slots.insert(slot_index as u16, new_slot.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local_player.write_packet(
|
||||||
|
ServerboundContainerClickPacket {
|
||||||
|
container_id: event.window_id,
|
||||||
|
state_id: inventory.state_id,
|
||||||
|
slot_num: event.operation.slot_num().map(|n| n as i16).unwrap_or(-999),
|
||||||
|
button_num: event.operation.button_num(),
|
||||||
|
click_type: event.operation.click_type(),
|
||||||
|
changed_slots,
|
||||||
|
carried_item: inventory.carried.clone(),
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sent from the server when the contents of a container are replaced. Usually
|
||||||
|
/// triggered by the `ContainerSetContent` packet.
|
||||||
|
pub struct SetContainerContentEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub slots: Vec<ItemSlot>,
|
||||||
|
pub container_id: u8,
|
||||||
|
}
|
||||||
|
fn handle_set_container_content_event(
|
||||||
|
mut events: EventReader<SetContainerContentEvent>,
|
||||||
|
mut query: Query<&mut InventoryComponent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let mut inventory = query.get_mut(event.entity).unwrap();
|
||||||
|
|
||||||
|
if event.container_id != inventory.id {
|
||||||
|
warn!(
|
||||||
|
"Tried to set container content with ID {}, but the current container ID is {}",
|
||||||
|
event.container_id, inventory.id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let menu = inventory.menu_mut();
|
||||||
|
for (i, slot) in event.slots.iter().enumerate() {
|
||||||
|
if let Some(slot_mut) = menu.slot_mut(i) {
|
||||||
|
*slot_mut = slot.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ pub mod disconnect;
|
||||||
mod entity_query;
|
mod entity_query;
|
||||||
mod events;
|
mod events;
|
||||||
mod get_mc_dir;
|
mod get_mc_dir;
|
||||||
|
pub mod interact;
|
||||||
|
pub mod inventory;
|
||||||
mod local_player;
|
mod local_player;
|
||||||
mod movement;
|
mod movement;
|
||||||
pub mod packet_handling;
|
pub mod packet_handling;
|
||||||
|
@ -28,6 +30,7 @@ pub mod task_pool;
|
||||||
pub use account::{Account, AccountOpts};
|
pub use account::{Account, AccountOpts};
|
||||||
pub use client::{
|
pub use client::{
|
||||||
init_ecs_app, start_ecs, Client, ClientInformation, JoinError, JoinedClientBundle, TabList,
|
init_ecs_app, start_ecs, Client, ClientInformation, JoinError, JoinedClientBundle, TabList,
|
||||||
|
TickBroadcast,
|
||||||
};
|
};
|
||||||
pub use events::Event;
|
pub use events::Event;
|
||||||
pub use local_player::{GameProfileComponent, LocalPlayer};
|
pub use local_player::{GameProfileComponent, LocalPlayer};
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
use std::{io, sync::Arc};
|
use std::{io, sync::Arc};
|
||||||
|
|
||||||
use azalea_auth::game_profile::GameProfile;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
use azalea_core::ChunkPos;
|
use azalea_core::{ChunkPos, GameMode};
|
||||||
use azalea_protocol::packets::game::ServerboundGamePacket;
|
use azalea_protocol::packets::game::ServerboundGamePacket;
|
||||||
use azalea_world::{
|
use azalea_world::{
|
||||||
entity::{self, Dead},
|
entity::{self, Dead, WorldName},
|
||||||
Instance, PartialInstance,
|
Instance, InstanceContainer, PartialInstance,
|
||||||
};
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component, entity::Entity, event::EventReader, query::Added, system::Query,
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
event::EventReader,
|
||||||
|
query::Added,
|
||||||
|
system::{Query, Res},
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -75,9 +79,17 @@ pub struct GameProfileComponent(pub GameProfile);
|
||||||
|
|
||||||
/// Marks a [`LocalPlayer`] that's in a loaded chunk. This is updated at the
|
/// Marks a [`LocalPlayer`] that's in a loaded chunk. This is updated at the
|
||||||
/// beginning of every tick.
|
/// beginning of every tick.
|
||||||
#[derive(Component)]
|
#[derive(Component, Clone, Debug, Copy)]
|
||||||
pub struct LocalPlayerInLoadedChunk;
|
pub struct LocalPlayerInLoadedChunk;
|
||||||
|
|
||||||
|
/// The gamemode of a local player. For a non-local player, you can look up the
|
||||||
|
/// player in the [`TabList`].
|
||||||
|
#[derive(Component, Clone, Debug, Copy)]
|
||||||
|
pub struct LocalGameMode {
|
||||||
|
pub current: GameMode,
|
||||||
|
pub previous: Option<GameMode>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalPlayer {
|
impl LocalPlayer {
|
||||||
/// Create a new `LocalPlayer`.
|
/// Create a new `LocalPlayer`.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -104,7 +116,7 @@ impl LocalPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a packet directly to the server.
|
/// Write a packet directly to the server.
|
||||||
pub fn write_packet(&mut self, packet: ServerboundGamePacket) {
|
pub fn write_packet(&self, packet: ServerboundGamePacket) {
|
||||||
self.packet_writer
|
self.packet_writer
|
||||||
.send(packet)
|
.send(packet)
|
||||||
.expect("write_packet shouldn't be able to be called if the connection is closed");
|
.expect("write_packet shouldn't be able to be called if the connection is closed");
|
||||||
|
@ -122,16 +134,15 @@ impl Drop for LocalPlayer {
|
||||||
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
|
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
|
||||||
pub fn update_in_loaded_chunk(
|
pub fn update_in_loaded_chunk(
|
||||||
mut commands: bevy_ecs::system::Commands,
|
mut commands: bevy_ecs::system::Commands,
|
||||||
query: Query<(Entity, &LocalPlayer, &entity::Position)>,
|
query: Query<(Entity, &WorldName, &entity::Position)>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (entity, local_player, position) in &query {
|
for (entity, local_player, position) in &query {
|
||||||
let player_chunk_pos = ChunkPos::from(position);
|
let player_chunk_pos = ChunkPos::from(position);
|
||||||
let in_loaded_chunk = local_player
|
let instance_lock = instance_container
|
||||||
.world
|
.get(local_player)
|
||||||
.read()
|
.expect("local player should always be in an instance");
|
||||||
.chunks
|
let in_loaded_chunk = instance_lock.read().chunks.get(&player_chunk_pos).is_some();
|
||||||
.get(&player_chunk_pos)
|
|
||||||
.is_some();
|
|
||||||
if in_loaded_chunk {
|
if in_loaded_chunk {
|
||||||
commands.entity(entity).insert(LocalPlayerInLoadedChunk);
|
commands.entity(entity).insert(LocalPlayerInLoadedChunk);
|
||||||
} else {
|
} else {
|
||||||
|
@ -176,7 +187,7 @@ pub fn handle_send_packet_event(
|
||||||
mut query: Query<&mut LocalPlayer>,
|
mut query: Query<&mut LocalPlayer>,
|
||||||
) {
|
) {
|
||||||
for event in send_packet_events.iter() {
|
for event in send_packet_events.iter() {
|
||||||
if let Ok(mut local_player) = query.get_mut(event.entity) {
|
if let Ok(local_player) = query.get_mut(event.entity) {
|
||||||
local_player.write_packet(event.packet.clone());
|
local_player.write_packet(event.packet.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use azalea_world::{
|
||||||
};
|
};
|
||||||
use bevy_app::{App, CoreSchedule, IntoSystemAppConfigs, Plugin};
|
use bevy_app::{App, CoreSchedule, IntoSystemAppConfigs, Plugin};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
query::With,
|
query::With,
|
||||||
|
@ -84,18 +85,26 @@ impl Client {
|
||||||
**jumping_ref
|
**jumping_ref
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets your rotation. `y_rot` is yaw (looking to the side), `x_rot` is
|
/// Sets the direction the client is looking. `y_rot` is yaw (looking to the
|
||||||
/// pitch (looking up and down). You can get these numbers from the vanilla
|
/// side), `x_rot` is pitch (looking up and down). You can get these
|
||||||
/// f3 screen.
|
/// numbers from the vanilla f3 screen.
|
||||||
/// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90.
|
/// `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) {
|
pub fn set_direction(&mut self, y_rot: f32, x_rot: f32) {
|
||||||
let mut ecs = self.ecs.lock();
|
let mut ecs = self.ecs.lock();
|
||||||
let mut physics = self.query::<&mut entity::Physics>(&mut ecs);
|
let mut look_direction = self.query::<&mut entity::LookDirection>(&mut ecs);
|
||||||
|
|
||||||
entity::set_rotation(&mut physics, y_rot, x_rot);
|
(look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A component that contains the look direction that was last sent over the
|
||||||
|
/// network.
|
||||||
|
#[derive(Debug, Component, Clone, Default)]
|
||||||
|
pub struct LastSentLookDirection {
|
||||||
|
pub x_rot: f32,
|
||||||
|
pub y_rot: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub(crate) fn send_position(
|
pub(crate) fn send_position(
|
||||||
mut query: Query<
|
mut query: Query<
|
||||||
|
@ -106,6 +115,8 @@ pub(crate) fn send_position(
|
||||||
&entity::Position,
|
&entity::Position,
|
||||||
&mut entity::LastSentPosition,
|
&mut entity::LastSentPosition,
|
||||||
&mut entity::Physics,
|
&mut entity::Physics,
|
||||||
|
&entity::LookDirection,
|
||||||
|
&mut LastSentLookDirection,
|
||||||
&entity::metadata::Sprinting,
|
&entity::metadata::Sprinting,
|
||||||
),
|
),
|
||||||
&LocalPlayerInLoadedChunk,
|
&LocalPlayerInLoadedChunk,
|
||||||
|
@ -118,6 +129,8 @@ pub(crate) fn send_position(
|
||||||
position,
|
position,
|
||||||
mut last_sent_position,
|
mut last_sent_position,
|
||||||
mut physics,
|
mut physics,
|
||||||
|
direction,
|
||||||
|
mut last_direction,
|
||||||
sprinting,
|
sprinting,
|
||||||
) in query.iter_mut()
|
) in query.iter_mut()
|
||||||
{
|
{
|
||||||
|
@ -130,8 +143,8 @@ pub(crate) fn send_position(
|
||||||
let x_delta = position.x - last_sent_position.x;
|
let x_delta = position.x - last_sent_position.x;
|
||||||
let y_delta = position.y - last_sent_position.y;
|
let y_delta = position.y - last_sent_position.y;
|
||||||
let z_delta = position.z - last_sent_position.z;
|
let z_delta = position.z - last_sent_position.z;
|
||||||
let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64;
|
let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
|
||||||
let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64;
|
let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
|
||||||
|
|
||||||
physics_state.position_remainder += 1;
|
physics_state.position_remainder += 1;
|
||||||
|
|
||||||
|
@ -140,19 +153,19 @@ pub(crate) fn send_position(
|
||||||
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
||||||
> 2.0e-4f64.powi(2))
|
> 2.0e-4f64.powi(2))
|
||||||
|| physics_state.position_remainder >= 20;
|
|| physics_state.position_remainder >= 20;
|
||||||
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
||||||
|
|
||||||
// if self.is_passenger() {
|
// if self.is_passenger() {
|
||||||
// TODO: posrot packet for being a passenger
|
// TODO: posrot packet for being a passenger
|
||||||
// }
|
// }
|
||||||
let packet = if sending_position && sending_rotation {
|
let packet = if sending_position && sending_direction {
|
||||||
Some(
|
Some(
|
||||||
ServerboundMovePlayerPosRotPacket {
|
ServerboundMovePlayerPosRotPacket {
|
||||||
x: position.x,
|
x: position.x,
|
||||||
y: position.y,
|
y: position.y,
|
||||||
z: position.z,
|
z: position.z,
|
||||||
x_rot: physics.x_rot,
|
x_rot: direction.x_rot,
|
||||||
y_rot: physics.y_rot,
|
y_rot: direction.y_rot,
|
||||||
on_ground: physics.on_ground,
|
on_ground: physics.on_ground,
|
||||||
}
|
}
|
||||||
.get(),
|
.get(),
|
||||||
|
@ -167,11 +180,11 @@ pub(crate) fn send_position(
|
||||||
}
|
}
|
||||||
.get(),
|
.get(),
|
||||||
)
|
)
|
||||||
} else if sending_rotation {
|
} else if sending_direction {
|
||||||
Some(
|
Some(
|
||||||
ServerboundMovePlayerRotPacket {
|
ServerboundMovePlayerRotPacket {
|
||||||
x_rot: physics.x_rot,
|
x_rot: direction.x_rot,
|
||||||
y_rot: physics.y_rot,
|
y_rot: direction.y_rot,
|
||||||
on_ground: physics.on_ground,
|
on_ground: physics.on_ground,
|
||||||
}
|
}
|
||||||
.get(),
|
.get(),
|
||||||
|
@ -191,9 +204,9 @@ pub(crate) fn send_position(
|
||||||
**last_sent_position = **position;
|
**last_sent_position = **position;
|
||||||
physics_state.position_remainder = 0;
|
physics_state.position_remainder = 0;
|
||||||
}
|
}
|
||||||
if sending_rotation {
|
if sending_direction {
|
||||||
physics.y_rot_last = physics.y_rot;
|
last_direction.y_rot = direction.y_rot;
|
||||||
physics.x_rot_last = physics.x_rot;
|
last_direction.x_rot = direction.x_rot;
|
||||||
}
|
}
|
||||||
|
|
||||||
physics.last_on_ground = physics.on_ground;
|
physics.last_on_ground = physics.on_ground;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
||||||
|
|
||||||
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
connect::{ReadConnection, WriteConnection},
|
connect::{ReadConnection, WriteConnection},
|
||||||
packets::game::{
|
packets::game::{
|
||||||
|
@ -16,7 +16,7 @@ use azalea_protocol::{
|
||||||
use azalea_world::{
|
use azalea_world::{
|
||||||
entity::{
|
entity::{
|
||||||
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
||||||
set_rotation, Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition,
|
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LookDirection,
|
||||||
MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
|
MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
|
||||||
},
|
},
|
||||||
entity::{LoadedBy, RelativeEntityUpdate},
|
entity::{LoadedBy, RelativeEntityUpdate},
|
||||||
|
@ -37,9 +37,13 @@ use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chat::{ChatPacket, ChatReceivedEvent},
|
chat::{ChatPacket, ChatReceivedEvent},
|
||||||
client::TabList,
|
client::{PlayerAbilities, TabList},
|
||||||
disconnect::DisconnectEvent,
|
disconnect::DisconnectEvent,
|
||||||
local_player::{GameProfileComponent, LocalPlayer},
|
inventory::{
|
||||||
|
ClientSideCloseContainerEvent, InventoryComponent, MenuOpenedEvent,
|
||||||
|
SetContainerContentEvent,
|
||||||
|
},
|
||||||
|
local_player::{GameProfileComponent, LocalGameMode, LocalPlayer},
|
||||||
ClientInformation, PlayerInfo,
|
ClientInformation, PlayerInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -194,7 +198,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
)>,
|
)>,
|
||||||
ResMut<InstanceContainer>,
|
ResMut<InstanceContainer>,
|
||||||
)> = SystemState::new(ecs);
|
)> = SystemState::new(ecs);
|
||||||
let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs);
|
let (mut commands, mut query, mut instance_container) = system_state.get_mut(ecs);
|
||||||
let (mut local_player, world_name, game_profile, client_information) =
|
let (mut local_player, world_name, game_profile, client_information) =
|
||||||
query.get_mut(player_entity).unwrap();
|
query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
@ -220,16 +224,16 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
.entity(player_entity)
|
.entity(player_entity)
|
||||||
.insert(WorldName(new_world_name.clone()));
|
.insert(WorldName(new_world_name.clone()));
|
||||||
}
|
}
|
||||||
// add this world to the world_container (or don't if it's already
|
// add this world to the instance_container (or don't if it's already
|
||||||
// there)
|
// there)
|
||||||
let weak_world = world_container.insert(
|
let weak_world = instance_container.insert(
|
||||||
new_world_name.clone(),
|
new_world_name.clone(),
|
||||||
dimension.height,
|
dimension.height,
|
||||||
dimension.min_y,
|
dimension.min_y,
|
||||||
);
|
);
|
||||||
// set the partial_world to an empty world
|
// set the partial_world to an empty world
|
||||||
// (when we add chunks or entities those will be in the
|
// (when we add chunks or entities those will be in the
|
||||||
// world_container)
|
// instance_container)
|
||||||
|
|
||||||
*local_player.partial_instance.write() = PartialInstance::new(
|
*local_player.partial_instance.write() = PartialInstance::new(
|
||||||
client_information.view_distance.into(),
|
client_information.view_distance.into(),
|
||||||
|
@ -250,9 +254,14 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
metadata: PlayerMetadataBundle::default(),
|
metadata: PlayerMetadataBundle::default(),
|
||||||
};
|
};
|
||||||
// insert our components into the ecs :)
|
// insert our components into the ecs :)
|
||||||
commands
|
commands.entity(player_entity).insert((
|
||||||
.entity(player_entity)
|
MinecraftEntityId(p.player_id),
|
||||||
.insert((MinecraftEntityId(p.player_id), player_bundle));
|
LocalGameMode {
|
||||||
|
current: p.game_type,
|
||||||
|
previous: p.previous_game_type.into(),
|
||||||
|
},
|
||||||
|
player_bundle,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the client information that we have set
|
// send the client information that we have set
|
||||||
|
@ -288,6 +297,12 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::PlayerAbilities(p) => {
|
ClientboundGamePacket::PlayerAbilities(p) => {
|
||||||
debug!("Got player abilities packet {:?}", p);
|
debug!("Got player abilities packet {:?}", p);
|
||||||
|
let mut system_state: SystemState<Query<&mut PlayerAbilities>> =
|
||||||
|
SystemState::new(ecs);
|
||||||
|
let mut query = system_state.get_mut(ecs);
|
||||||
|
let mut player_abilities = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
*player_abilities = PlayerAbilities::from(p);
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::SetCarriedItem(p) => {
|
ClientboundGamePacket::SetCarriedItem(p) => {
|
||||||
debug!("Got set carried item packet {:?}", p);
|
debug!("Got set carried item packet {:?}", p);
|
||||||
|
@ -319,16 +334,18 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
// TODO: reply with teleport confirm
|
// TODO: reply with teleport confirm
|
||||||
debug!("Got player position packet {:?}", p);
|
debug!("Got player position packet {:?}", p);
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
let mut system_state: SystemState<
|
let mut system_state: SystemState<
|
||||||
Query<(
|
Query<(
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
&mut Physics,
|
&mut Physics,
|
||||||
|
&mut LookDirection,
|
||||||
&mut Position,
|
&mut Position,
|
||||||
&mut LastSentPosition,
|
&mut LastSentPosition,
|
||||||
)>,
|
)>,
|
||||||
> = SystemState::new(ecs);
|
> = SystemState::new(ecs);
|
||||||
let mut query = system_state.get_mut(ecs);
|
let mut query = system_state.get_mut(ecs);
|
||||||
let Ok((mut local_player, mut physics, mut position, mut last_sent_position)) =
|
let Ok((local_player, mut physics, mut direction, mut position, mut last_sent_position)) =
|
||||||
query.get_mut(player_entity) else {
|
query.get_mut(player_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -364,10 +381,10 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
let mut y_rot = p.y_rot;
|
let mut y_rot = p.y_rot;
|
||||||
let mut x_rot = p.x_rot;
|
let mut x_rot = p.x_rot;
|
||||||
if p.relative_arguments.x_rot {
|
if p.relative_arguments.x_rot {
|
||||||
x_rot += physics.x_rot;
|
x_rot += direction.x_rot;
|
||||||
}
|
}
|
||||||
if p.relative_arguments.y_rot {
|
if p.relative_arguments.y_rot {
|
||||||
y_rot += physics.y_rot;
|
y_rot += direction.y_rot;
|
||||||
}
|
}
|
||||||
|
|
||||||
physics.delta = Vec3 {
|
physics.delta = Vec3 {
|
||||||
|
@ -378,7 +395,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
// we call a function instead of setting the fields ourself since the
|
// we call a function instead of setting the fields ourself since the
|
||||||
// function makes sure the rotations stay in their
|
// function makes sure the rotations stay in their
|
||||||
// ranges
|
// ranges
|
||||||
set_rotation(&mut physics, y_rot, x_rot);
|
(direction.y_rot, direction.x_rot) = (y_rot, x_rot);
|
||||||
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
||||||
// so investigate that ig
|
// so investigate that ig
|
||||||
let new_pos = Vec3 {
|
let new_pos = Vec3 {
|
||||||
|
@ -633,9 +650,6 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
||||||
debug!("Got set default spawn position packet {:?}", p);
|
debug!("Got set default spawn position packet {:?}", p);
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::ContainerSetContent(p) => {
|
|
||||||
debug!("Got container set content packet {:?}", p);
|
|
||||||
}
|
|
||||||
ClientboundGamePacket::SetHealth(p) => {
|
ClientboundGamePacket::SetHealth(p) => {
|
||||||
debug!("Got set health packet {:?}", p);
|
debug!("Got set health packet {:?}", p);
|
||||||
|
|
||||||
|
@ -765,7 +779,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut local_player = query.get_mut(player_entity).unwrap();
|
let local_player = query.get_mut(player_entity).unwrap();
|
||||||
local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get());
|
local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get());
|
||||||
debug!("Sent keep alive packet {p:?} for {player_entity:?}");
|
debug!("Sent keep alive packet {p:?} for {player_entity:?}");
|
||||||
}
|
}
|
||||||
|
@ -831,7 +845,23 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::GameEvent(p) => {
|
ClientboundGamePacket::GameEvent(p) => {
|
||||||
|
use azalea_protocol::packets::game::clientbound_game_event_packet::EventType;
|
||||||
|
|
||||||
debug!("Got game event packet {:?}", p);
|
debug!("Got game event packet {:?}", p);
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match p.event {
|
||||||
|
EventType::ChangeGameMode => {
|
||||||
|
let mut system_state: SystemState<Query<&mut LocalGameMode>> =
|
||||||
|
SystemState::new(ecs);
|
||||||
|
let mut query = system_state.get_mut(ecs);
|
||||||
|
let mut local_game_mode = query.get_mut(player_entity).unwrap();
|
||||||
|
if let Some(new_game_mode) = GameMode::from_id(p.param as u8) {
|
||||||
|
local_game_mode.current = new_game_mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::LevelParticles(p) => {
|
ClientboundGamePacket::LevelParticles(p) => {
|
||||||
debug!("Got level particles packet {:?}", p);
|
debug!("Got level particles packet {:?}", p);
|
||||||
|
@ -855,8 +885,93 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
ClientboundGamePacket::BossEvent(_) => {}
|
ClientboundGamePacket::BossEvent(_) => {}
|
||||||
ClientboundGamePacket::CommandSuggestions(_) => {}
|
ClientboundGamePacket::CommandSuggestions(_) => {}
|
||||||
ClientboundGamePacket::ContainerSetData(_) => {}
|
ClientboundGamePacket::ContainerSetContent(p) => {
|
||||||
ClientboundGamePacket::ContainerSetSlot(_) => {}
|
debug!("Got container set content packet {:?}", p);
|
||||||
|
|
||||||
|
let mut system_state: SystemState<(
|
||||||
|
Query<&mut InventoryComponent>,
|
||||||
|
EventWriter<SetContainerContentEvent>,
|
||||||
|
)> = SystemState::new(ecs);
|
||||||
|
let (mut query, mut events) = system_state.get_mut(ecs);
|
||||||
|
let mut inventory = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
// container id 0 is always the player's inventory
|
||||||
|
if p.container_id == 0 {
|
||||||
|
// this is just so it has the same type as the `else` block
|
||||||
|
for (i, slot) in p.items.iter().enumerate() {
|
||||||
|
if let Some(slot_mut) = inventory.inventory_menu.slot_mut(i) {
|
||||||
|
*slot_mut = slot.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
events.send(SetContainerContentEvent {
|
||||||
|
entity: player_entity,
|
||||||
|
slots: p.items.clone(),
|
||||||
|
container_id: p.container_id as u8,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientboundGamePacket::ContainerSetData(p) => {
|
||||||
|
debug!("Got container set data packet {:?}", p);
|
||||||
|
// let mut system_state: SystemState<Query<&mut
|
||||||
|
// InventoryComponent>> =
|
||||||
|
// SystemState::new(ecs);
|
||||||
|
// let mut query = system_state.get_mut(ecs);
|
||||||
|
// let mut inventory =
|
||||||
|
// query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
// TODO: handle ContainerSetData packet
|
||||||
|
// this is used for various things like the furnace progress
|
||||||
|
// bar
|
||||||
|
// see https://wiki.vg/Protocol#Set_Container_Property
|
||||||
|
}
|
||||||
|
ClientboundGamePacket::ContainerSetSlot(p) => {
|
||||||
|
debug!("Got container set slot packet {:?}", p);
|
||||||
|
|
||||||
|
let mut system_state: SystemState<Query<&mut InventoryComponent>> =
|
||||||
|
SystemState::new(ecs);
|
||||||
|
let mut query = system_state.get_mut(ecs);
|
||||||
|
let mut inventory = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
if p.container_id == -1 {
|
||||||
|
// -1 means carried item
|
||||||
|
inventory.carried = p.item_stack.clone();
|
||||||
|
} else if p.container_id == -2 {
|
||||||
|
if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
|
||||||
|
*slot = p.item_stack.clone();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let is_creative_mode_and_inventory_closed = false;
|
||||||
|
// technically minecraft has slightly different behavior here if you're in
|
||||||
|
// creative mode and have your inventory open
|
||||||
|
if p.container_id == 0
|
||||||
|
&& azalea_inventory::Player::is_hotbar_slot(p.slot.into())
|
||||||
|
{
|
||||||
|
// minecraft also sets a "pop time" here which is used for an animation
|
||||||
|
// but that's not really necessary
|
||||||
|
if let Some(slot) = inventory.inventory_menu.slot_mut(p.slot.into()) {
|
||||||
|
*slot = p.item_stack.clone();
|
||||||
|
}
|
||||||
|
} else if p.container_id == (inventory.id as i8)
|
||||||
|
&& (p.container_id != 0 || !is_creative_mode_and_inventory_closed)
|
||||||
|
{
|
||||||
|
// var2.containerMenu.setItem(var4, var1.getStateId(), var3);
|
||||||
|
if let Some(slot) = inventory.menu_mut().slot_mut(p.slot.into()) {
|
||||||
|
*slot = p.item_stack.clone();
|
||||||
|
inventory.state_id = p.state_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientboundGamePacket::ContainerClose(_p) => {
|
||||||
|
// there's p.container_id but minecraft doesn't actually check it
|
||||||
|
let mut system_state: SystemState<EventWriter<ClientSideCloseContainerEvent>> =
|
||||||
|
SystemState::new(ecs);
|
||||||
|
let mut client_side_close_container_events = system_state.get_mut(ecs);
|
||||||
|
client_side_close_container_events.send(ClientSideCloseContainerEvent {
|
||||||
|
entity: player_entity,
|
||||||
|
})
|
||||||
|
}
|
||||||
ClientboundGamePacket::Cooldown(_) => {}
|
ClientboundGamePacket::Cooldown(_) => {}
|
||||||
ClientboundGamePacket::CustomChatCompletions(_) => {}
|
ClientboundGamePacket::CustomChatCompletions(_) => {}
|
||||||
ClientboundGamePacket::DeleteChat(_) => {}
|
ClientboundGamePacket::DeleteChat(_) => {}
|
||||||
|
@ -867,7 +982,18 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::MerchantOffers(_) => {}
|
ClientboundGamePacket::MerchantOffers(_) => {}
|
||||||
ClientboundGamePacket::MoveVehicle(_) => {}
|
ClientboundGamePacket::MoveVehicle(_) => {}
|
||||||
ClientboundGamePacket::OpenBook(_) => {}
|
ClientboundGamePacket::OpenBook(_) => {}
|
||||||
ClientboundGamePacket::OpenScreen(_) => {}
|
ClientboundGamePacket::OpenScreen(p) => {
|
||||||
|
debug!("Got open screen packet {:?}", p);
|
||||||
|
let mut system_state: SystemState<EventWriter<MenuOpenedEvent>> =
|
||||||
|
SystemState::new(ecs);
|
||||||
|
let mut menu_opened_events = system_state.get_mut(ecs);
|
||||||
|
menu_opened_events.send(MenuOpenedEvent {
|
||||||
|
entity: player_entity,
|
||||||
|
window_id: p.container_id,
|
||||||
|
menu_type: p.menu_type,
|
||||||
|
title: p.title,
|
||||||
|
})
|
||||||
|
}
|
||||||
ClientboundGamePacket::OpenSignEditor(_) => {}
|
ClientboundGamePacket::OpenSignEditor(_) => {}
|
||||||
ClientboundGamePacket::Ping(_) => {}
|
ClientboundGamePacket::Ping(_) => {}
|
||||||
ClientboundGamePacket::PlaceGhostRecipe(_) => {}
|
ClientboundGamePacket::PlaceGhostRecipe(_) => {}
|
||||||
|
@ -935,7 +1061,6 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::TakeItemEntity(_) => {}
|
ClientboundGamePacket::TakeItemEntity(_) => {}
|
||||||
ClientboundGamePacket::DisguisedChat(_) => {}
|
ClientboundGamePacket::DisguisedChat(_) => {}
|
||||||
ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
|
ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
|
||||||
ClientboundGamePacket::ContainerClose(_) => {}
|
|
||||||
ClientboundGamePacket::Bundle(_) => {}
|
ClientboundGamePacket::Bundle(_) => {}
|
||||||
ClientboundGamePacket::DamageEvent(_) => {}
|
ClientboundGamePacket::DamageEvent(_) => {}
|
||||||
ClientboundGamePacket::HurtAnimation(_) => {}
|
ClientboundGamePacket::HurtAnimation(_) => {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use azalea_auth::game_profile::GameProfile;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::GameType;
|
use azalea_core::GameMode;
|
||||||
use azalea_world::entity::EntityInfos;
|
use azalea_world::entity::EntityInfos;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
|
@ -18,7 +18,10 @@ pub struct PlayerInfo {
|
||||||
pub profile: GameProfile,
|
pub profile: GameProfile,
|
||||||
/// The player's UUID.
|
/// The player's UUID.
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub gamemode: GameType,
|
/// The current gamemode of the player, like survival or creative.
|
||||||
|
pub gamemode: GameMode,
|
||||||
|
/// The player's latency in milliseconds. The bars in the tab screen depend
|
||||||
|
/// on this.
|
||||||
pub latency: i32,
|
pub latency: i32,
|
||||||
/// The player's display name in the tab list, but only if it's different
|
/// The player's display name in the tab list, but only if it's different
|
||||||
/// from the player's normal username. Use `player_info.profile.name` to get
|
/// from the player's normal username. Use `player_info.profile.name` to get
|
||||||
|
|
4
azalea-core/Cargo.toml
Normal file → Executable file
4
azalea-core/Cargo.toml
Normal file → Executable file
|
@ -10,8 +10,12 @@ version = "0.6.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
|
azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
|
||||||
|
azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
|
||||||
|
azalea-inventory = { version = "0.1.0", path = "../azalea-inventory" }
|
||||||
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
|
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
|
||||||
|
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
|
||||||
bevy_ecs = { version = "0.10.0", default-features = false, optional = true }
|
bevy_ecs = { version = "0.10.0", default-features = false, optional = true }
|
||||||
|
num-traits = "0.2.15"
|
||||||
serde = { version = "^1.0", optional = true }
|
serde = { version = "^1.0", optional = true }
|
||||||
uuid = "^1.1.2"
|
uuid = "^1.1.2"
|
||||||
|
|
||||||
|
|
|
@ -164,15 +164,15 @@ impl AABB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_relative(&self, x: f64, y: f64, z: f64) -> AABB {
|
pub fn move_relative(&self, delta: &Vec3) -> AABB {
|
||||||
AABB {
|
AABB {
|
||||||
min_x: self.min_x + x,
|
min_x: self.min_x + delta.x,
|
||||||
min_y: self.min_y + y,
|
min_y: self.min_y + delta.y,
|
||||||
min_z: self.min_z + z,
|
min_z: self.min_z + delta.z,
|
||||||
|
|
||||||
max_x: self.max_x + x,
|
max_x: self.max_x + delta.x,
|
||||||
max_y: self.max_y + y,
|
max_y: self.max_y + delta.y,
|
||||||
max_z: self.max_z + z,
|
max_z: self.max_z + delta.z,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,12 +227,11 @@ impl AABB {
|
||||||
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
|
pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option<Vec3> {
|
||||||
let mut t = 1.0;
|
let mut t = 1.0;
|
||||||
let delta = max - min;
|
let delta = max - min;
|
||||||
let _dir = self.get_direction(self, min, &mut t, None, &delta)?;
|
let _dir = Self::get_direction(self, min, &mut t, None, &delta)?;
|
||||||
Some(min + &(delta * t))
|
Some(min + &(delta * t))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clip_iterable(
|
pub fn clip_iterable(
|
||||||
&self,
|
|
||||||
boxes: &Vec<AABB>,
|
boxes: &Vec<AABB>,
|
||||||
from: &Vec3,
|
from: &Vec3,
|
||||||
to: &Vec3,
|
to: &Vec3,
|
||||||
|
@ -243,7 +242,13 @@ impl AABB {
|
||||||
let delta = to - from;
|
let delta = to - from;
|
||||||
|
|
||||||
for aabb in boxes {
|
for aabb in boxes {
|
||||||
dir = self.get_direction(aabb, from, &mut t, dir, &delta);
|
dir = Self::get_direction(
|
||||||
|
&aabb.move_relative(&pos.to_vec3_floored()),
|
||||||
|
from,
|
||||||
|
&mut t,
|
||||||
|
dir,
|
||||||
|
&delta,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let dir = dir?;
|
let dir = dir?;
|
||||||
Some(BlockHitResult {
|
Some(BlockHitResult {
|
||||||
|
@ -256,15 +261,14 @@ impl AABB {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_direction(
|
fn get_direction(
|
||||||
&self,
|
|
||||||
aabb: &AABB,
|
aabb: &AABB,
|
||||||
from: &Vec3,
|
from: &Vec3,
|
||||||
t: &mut f64,
|
t: &mut f64,
|
||||||
dir: Option<Direction>,
|
mut dir: Option<Direction>,
|
||||||
delta: &Vec3,
|
delta: &Vec3,
|
||||||
) -> Option<Direction> {
|
) -> Option<Direction> {
|
||||||
if delta.x > EPSILON {
|
if delta.x > EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta,
|
delta,
|
||||||
|
@ -277,7 +281,7 @@ impl AABB {
|
||||||
start: from,
|
start: from,
|
||||||
});
|
});
|
||||||
} else if delta.x < -EPSILON {
|
} else if delta.x < -EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta,
|
delta,
|
||||||
|
@ -292,7 +296,7 @@ impl AABB {
|
||||||
}
|
}
|
||||||
|
|
||||||
if delta.y > EPSILON {
|
if delta.y > EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta: &Vec3 {
|
delta: &Vec3 {
|
||||||
|
@ -313,7 +317,7 @@ impl AABB {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if delta.y < -EPSILON {
|
} else if delta.y < -EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta: &Vec3 {
|
delta: &Vec3 {
|
||||||
|
@ -336,7 +340,7 @@ impl AABB {
|
||||||
}
|
}
|
||||||
|
|
||||||
if delta.z > EPSILON {
|
if delta.z > EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta: &Vec3 {
|
delta: &Vec3 {
|
||||||
|
@ -357,7 +361,7 @@ impl AABB {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if delta.z < -EPSILON {
|
} else if delta.z < -EPSILON {
|
||||||
return self.clip_point(ClipPointOpts {
|
dir = Self::clip_point(ClipPointOpts {
|
||||||
t,
|
t,
|
||||||
approach_dir: dir,
|
approach_dir: dir,
|
||||||
delta: &Vec3 {
|
delta: &Vec3 {
|
||||||
|
@ -382,18 +386,18 @@ impl AABB {
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_point(&self, opts: ClipPointOpts) -> Option<Direction> {
|
fn clip_point(opts: ClipPointOpts) -> Option<Direction> {
|
||||||
let t_x = (opts.begin - opts.start.x) / opts.delta.x;
|
let d = (opts.begin - opts.start.x) / opts.delta.x;
|
||||||
let t_y = (opts.start.y + t_x) / opts.delta.y;
|
let e = opts.start.y + d * opts.delta.y;
|
||||||
let t_z = (opts.start.z + t_x) / opts.delta.z;
|
let f = opts.start.z + d * opts.delta.z;
|
||||||
if 0.0 < t_x
|
if 0.0 < d
|
||||||
&& t_x < *opts.t
|
&& d < *opts.t
|
||||||
&& opts.min_x - EPSILON < t_y
|
&& opts.min_x - EPSILON < e
|
||||||
&& t_y < opts.max_x + EPSILON
|
&& e < opts.max_x + EPSILON
|
||||||
&& opts.min_z - EPSILON < t_z
|
&& opts.min_z - EPSILON < f
|
||||||
&& t_z < opts.max_z + EPSILON
|
&& f < opts.max_z + EPSILON
|
||||||
{
|
{
|
||||||
*opts.t = t_x;
|
*opts.t = d;
|
||||||
Some(opts.result_dir)
|
Some(opts.result_dir)
|
||||||
} else {
|
} else {
|
||||||
opts.approach_dir
|
opts.approach_dir
|
||||||
|
@ -435,3 +439,28 @@ impl AABB {
|
||||||
axis.choose(self.min_x, self.min_y, self.min_z)
|
axis.choose(self.min_x, self.min_y, self.min_z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aabb_clip_iterable() {
|
||||||
|
assert_ne!(
|
||||||
|
AABB::clip_iterable(
|
||||||
|
&vec![AABB {
|
||||||
|
min_x: 0.,
|
||||||
|
min_y: 0.,
|
||||||
|
min_z: 0.,
|
||||||
|
max_x: 1.,
|
||||||
|
max_y: 1.,
|
||||||
|
max_z: 1.,
|
||||||
|
}],
|
||||||
|
&Vec3::new(-1., -1., -1.),
|
||||||
|
&Vec3::new(1., 1., 1.),
|
||||||
|
&BlockPos::new(0, 0, 0),
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{BlockPos, Direction, Vec3};
|
use crate::{BlockPos, Direction, Vec3};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct BlockHitResult {
|
pub struct BlockHitResult {
|
||||||
pub location: Vec3,
|
pub location: Vec3,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
|
@ -8,3 +8,22 @@ pub struct BlockHitResult {
|
||||||
pub miss: bool,
|
pub miss: bool,
|
||||||
pub inside: bool,
|
pub inside: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockHitResult {
|
||||||
|
pub fn miss(location: Vec3, direction: Direction, block_pos: BlockPos) -> Self {
|
||||||
|
Self {
|
||||||
|
location,
|
||||||
|
direction,
|
||||||
|
block_pos,
|
||||||
|
miss: true,
|
||||||
|
inside: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_direction(&self, direction: Direction) -> Self {
|
||||||
|
Self { direction, ..*self }
|
||||||
|
}
|
||||||
|
pub fn with_position(&self, block_pos: BlockPos) -> Self {
|
||||||
|
Self { block_pos, ..*self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, McBuf, Default)]
|
use crate::Vec3;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, McBuf, Default, Eq, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
#[default]
|
#[default]
|
||||||
Down = 0,
|
Down = 0,
|
||||||
|
@ -11,6 +13,54 @@ pub enum Direction {
|
||||||
East,
|
East,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Direction {
|
||||||
|
pub fn nearest(vec: Vec3) -> Direction {
|
||||||
|
let mut best_direction = Direction::North;
|
||||||
|
let mut best_direction_amount = 0.0;
|
||||||
|
|
||||||
|
for dir in [
|
||||||
|
Direction::Down,
|
||||||
|
Direction::Up,
|
||||||
|
Direction::North,
|
||||||
|
Direction::South,
|
||||||
|
Direction::West,
|
||||||
|
Direction::East,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
let amount = dir.normal().dot(vec);
|
||||||
|
if amount > best_direction_amount {
|
||||||
|
best_direction = *dir;
|
||||||
|
best_direction_amount = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
best_direction
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal(self) -> Vec3 {
|
||||||
|
match self {
|
||||||
|
Direction::Down => Vec3::new(0.0, -1.0, 0.0),
|
||||||
|
Direction::Up => Vec3::new(0.0, 1.0, 0.0),
|
||||||
|
Direction::North => Vec3::new(0.0, 0.0, -1.0),
|
||||||
|
Direction::South => Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
Direction::West => Vec3::new(-1.0, 0.0, 0.0),
|
||||||
|
Direction::East => Vec3::new(1.0, 0.0, 0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opposite(self) -> Direction {
|
||||||
|
match self {
|
||||||
|
Direction::Down => Direction::Up,
|
||||||
|
Direction::Up => Direction::Down,
|
||||||
|
Direction::North => Direction::South,
|
||||||
|
Direction::South => Direction::North,
|
||||||
|
Direction::West => Direction::East,
|
||||||
|
Direction::East => Direction::West,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: make azalea_block use this instead of FacingCardinal
|
// TODO: make azalea_block use this instead of FacingCardinal
|
||||||
#[derive(Clone, Copy, Debug, McBuf)]
|
#[derive(Clone, Copy, Debug, McBuf)]
|
||||||
pub enum CardinalDirection {
|
pub enum CardinalDirection {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
#[derive(Hash, Copy, Clone, Debug, Default)]
|
/// A Minecraft gamemode, like survival or creative.
|
||||||
pub enum GameType {
|
#[derive(Hash, Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum GameMode {
|
||||||
#[default]
|
#[default]
|
||||||
Survival,
|
Survival,
|
||||||
Creative,
|
Creative,
|
||||||
|
@ -10,30 +11,30 @@ pub enum GameType {
|
||||||
Spectator,
|
Spectator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameType {
|
impl GameMode {
|
||||||
pub fn to_id(&self) -> u8 {
|
pub fn to_id(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
GameType::Survival => 0,
|
GameMode::Survival => 0,
|
||||||
GameType::Creative => 1,
|
GameMode::Creative => 1,
|
||||||
GameType::Adventure => 2,
|
GameMode::Adventure => 2,
|
||||||
GameType::Spectator => 3,
|
GameMode::Spectator => 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the id of the game type, but return -1 if the game type is invalid.
|
/// Get the id of the game type, but return -1 if the game type is invalid.
|
||||||
pub fn to_optional_id<T: Into<Option<GameType>>>(game_type: T) -> i8 {
|
pub fn to_optional_id<T: Into<Option<GameMode>>>(game_type: T) -> i8 {
|
||||||
match game_type.into() {
|
match game_type.into() {
|
||||||
Some(game_type) => game_type.to_id() as i8,
|
Some(game_type) => game_type.to_id() as i8,
|
||||||
None => -1,
|
None => -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_id(id: u8) -> Option<GameType> {
|
pub fn from_id(id: u8) -> Option<GameMode> {
|
||||||
Some(match id {
|
Some(match id {
|
||||||
0 => GameType::Survival,
|
0 => GameMode::Survival,
|
||||||
1 => GameType::Creative,
|
1 => GameMode::Creative,
|
||||||
2 => GameType::Adventure,
|
2 => GameMode::Adventure,
|
||||||
3 => GameType::Spectator,
|
3 => GameMode::Spectator,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,7 @@ impl GameType {
|
||||||
Some(
|
Some(
|
||||||
match id {
|
match id {
|
||||||
-1 => None,
|
-1 => None,
|
||||||
id => Some(GameType::from_id(id as u8)?),
|
id => Some(GameMode::from_id(id as u8)?),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
|
@ -52,10 +53,10 @@ impl GameType {
|
||||||
// TODO: these should be translated
|
// TODO: these should be translated
|
||||||
// TranslatableComponent("selectWorld.gameMode." + string2)
|
// TranslatableComponent("selectWorld.gameMode." + string2)
|
||||||
match self {
|
match self {
|
||||||
GameType::Survival => "Survival",
|
GameMode::Survival => "Survival",
|
||||||
GameType::Creative => "Creative",
|
GameMode::Creative => "Creative",
|
||||||
GameType::Adventure => "Adventure",
|
GameMode::Adventure => "Adventure",
|
||||||
GameType::Spectator => "Spectator",
|
GameMode::Spectator => "Spectator",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,32 +64,32 @@ impl GameType {
|
||||||
// TODO: These should be translated TranslatableComponent("gameMode." +
|
// TODO: These should be translated TranslatableComponent("gameMode." +
|
||||||
// string2);
|
// string2);
|
||||||
match self {
|
match self {
|
||||||
GameType::Survival => "Survival Mode",
|
GameMode::Survival => "Survival Mode",
|
||||||
GameType::Creative => "Creative Mode",
|
GameMode::Creative => "Creative Mode",
|
||||||
GameType::Adventure => "Adventure Mode",
|
GameMode::Adventure => "Adventure Mode",
|
||||||
GameType::Spectator => "Spectator Mode",
|
GameMode::Spectator => "Spectator Mode",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_name(name: &str) -> GameType {
|
pub fn from_name(name: &str) -> GameMode {
|
||||||
match name {
|
match name {
|
||||||
"survival" => GameType::Survival,
|
"survival" => GameMode::Survival,
|
||||||
"creative" => GameType::Creative,
|
"creative" => GameMode::Creative,
|
||||||
"adventure" => GameType::Adventure,
|
"adventure" => GameMode::Adventure,
|
||||||
"spectator" => GameType::Spectator,
|
"spectator" => GameMode::Spectator,
|
||||||
_ => panic!("Unknown game type name: {name}"),
|
_ => panic!("Unknown game type name: {name}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufReadable for GameType {
|
impl McBufReadable for GameMode {
|
||||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
let id = u8::read_from(buf)?;
|
let id = u8::read_from(buf)?;
|
||||||
GameType::from_id(id).ok_or(BufReadError::UnexpectedEnumVariant { id: id as i32 })
|
GameMode::from_id(id).ok_or(BufReadError::UnexpectedEnumVariant { id: id as i32 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufWritable for GameType {
|
impl McBufWritable for GameMode {
|
||||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
u8::write_into(&self.to_id(), buf)
|
u8::write_into(&self.to_id(), buf)
|
||||||
}
|
}
|
||||||
|
@ -97,15 +98,15 @@ impl McBufWritable for GameType {
|
||||||
/// Rust doesn't let us `impl McBufReadable for Option<GameType>` so we have to
|
/// Rust doesn't let us `impl McBufReadable for Option<GameType>` so we have to
|
||||||
/// make a new type :(
|
/// make a new type :(
|
||||||
#[derive(Hash, Copy, Clone, Debug)]
|
#[derive(Hash, Copy, Clone, Debug)]
|
||||||
pub struct OptionalGameType(pub Option<GameType>);
|
pub struct OptionalGameType(pub Option<GameMode>);
|
||||||
|
|
||||||
impl From<Option<GameType>> for OptionalGameType {
|
impl From<Option<GameMode>> for OptionalGameType {
|
||||||
fn from(game_type: Option<GameType>) -> Self {
|
fn from(game_type: Option<GameMode>) -> Self {
|
||||||
OptionalGameType(game_type)
|
OptionalGameType(game_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OptionalGameType> for Option<GameType> {
|
impl From<OptionalGameType> for Option<GameMode> {
|
||||||
fn from(optional_game_type: OptionalGameType) -> Self {
|
fn from(optional_game_type: OptionalGameType) -> Self {
|
||||||
optional_game_type.0
|
optional_game_type.0
|
||||||
}
|
}
|
||||||
|
@ -114,12 +115,12 @@ impl From<OptionalGameType> for Option<GameType> {
|
||||||
impl McBufReadable for OptionalGameType {
|
impl McBufReadable for OptionalGameType {
|
||||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
let id = i8::read_from(buf)?;
|
let id = i8::read_from(buf)?;
|
||||||
GameType::from_optional_id(id).ok_or(BufReadError::UnexpectedEnumVariant { id: id as i32 })
|
GameMode::from_optional_id(id).ok_or(BufReadError::UnexpectedEnumVariant { id: id as i32 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufWritable for OptionalGameType {
|
impl McBufWritable for OptionalGameType {
|
||||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
GameType::to_optional_id(*self).write_into(buf)
|
GameMode::to_optional_id(*self).write_into(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,6 @@ pub use resource_location::*;
|
||||||
mod game_type;
|
mod game_type;
|
||||||
pub use game_type::*;
|
pub use game_type::*;
|
||||||
|
|
||||||
mod slot;
|
|
||||||
pub use slot::*;
|
|
||||||
|
|
||||||
mod position;
|
mod position;
|
||||||
pub use position::*;
|
pub use position::*;
|
||||||
|
|
||||||
|
@ -40,6 +37,8 @@ pub use aabb::*;
|
||||||
mod block_hit_result;
|
mod block_hit_result;
|
||||||
pub use block_hit_result::*;
|
pub use block_hit_result::*;
|
||||||
|
|
||||||
|
// some random math things used in minecraft are defined down here
|
||||||
|
|
||||||
// TODO: make this generic
|
// TODO: make this generic
|
||||||
pub fn binary_search(mut min: i32, max: i32, predicate: &dyn Fn(i32) -> bool) -> i32 {
|
pub fn binary_search(mut min: i32, max: i32, predicate: &dyn Fn(i32) -> bool) -> i32 {
|
||||||
let mut diff = max - min;
|
let mut diff = max - min;
|
||||||
|
@ -70,6 +69,10 @@ pub fn gcd(mut a: u32, mut b: u32) -> u32 {
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lerp<T: num_traits::Float>(amount: T, a: T, b: T) -> T {
|
||||||
|
a + amount * (b - a)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{BlockPos, Slot};
|
use crate::BlockPos;
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
|
|
||||||
#[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::component::Component))]
|
#[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::component::Component))]
|
||||||
#[derive(Debug, Clone, McBuf, Default)]
|
#[derive(Debug, Clone, McBuf, Default)]
|
||||||
|
@ -139,7 +140,7 @@ pub struct DustColorTransitionParticle {
|
||||||
|
|
||||||
#[derive(Debug, Clone, McBuf)]
|
#[derive(Debug, Clone, McBuf)]
|
||||||
pub struct ItemParticle {
|
pub struct ItemParticle {
|
||||||
pub item: Slot,
|
pub item: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, McBuf)]
|
#[derive(Debug, Clone, McBuf)]
|
||||||
|
|
|
@ -18,6 +18,12 @@ macro_rules! vec3_impl {
|
||||||
self.x * self.x + self.y * self.y + self.z * self.z
|
self.x * self.x + self.y * self.y + self.z * self.z
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the squared distance from this position to another position.
|
||||||
|
/// Equivalent to `(self - other).length_sqr()`.
|
||||||
|
pub fn distance_to_sqr(&self, other: &Self) -> $type {
|
||||||
|
(self - other).length_sqr()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a new instance of this position with the y coordinate
|
/// Return a new instance of this position with the y coordinate
|
||||||
/// decreased by the given number.
|
/// decreased by the given number.
|
||||||
pub fn down(&self, y: $type) -> Self {
|
pub fn down(&self, y: $type) -> Self {
|
||||||
|
@ -36,6 +42,10 @@ macro_rules! vec3_impl {
|
||||||
z: self.z,
|
z: self.z,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dot(&self, other: Self) -> $type {
|
||||||
|
self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for &$name {
|
impl Add for &$name {
|
||||||
|
@ -142,6 +152,15 @@ impl BlockPos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the block position into a Vec3 without centering it.
|
||||||
|
pub fn to_vec3_floored(&self) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x as f64,
|
||||||
|
y: self.y as f64,
|
||||||
|
z: self.z as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the distance of this vector from the origin by doing `x + y + z`.
|
/// Get the distance of this vector from the origin by doing `x + y + z`.
|
||||||
pub fn length_manhattan(&self) -> u32 {
|
pub fn length_manhattan(&self) -> u32 {
|
||||||
(self.x.abs() + self.y.abs() + self.z.abs()) as u32
|
(self.x.abs() + self.y.abs() + self.z.abs()) as u32
|
||||||
|
|
12
azalea-inventory/Cargo.toml
Normal file
12
azalea-inventory/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
edition = "2021"
|
||||||
|
name = "azalea-inventory"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
azalea-buf = { version = "0.6.0", path = "../azalea-buf" }
|
||||||
|
azalea-inventory-macros = { version = "0.1.0", path = "./azalea-inventory-macros" }
|
||||||
|
azalea-nbt = { version = "0.6.0", path = "../azalea-nbt" }
|
||||||
|
azalea-registry = { version = "0.6.0", path = "../azalea-registry" }
|
2
azalea-inventory/README.md
Normal file
2
azalea-inventory/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Representations of various inventory data structures in Minecraft.
|
||||||
|
|
14
azalea-inventory/azalea-inventory-macros/Cargo.toml
Normal file
14
azalea-inventory/azalea-inventory-macros/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "azalea-inventory-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0.47"
|
||||||
|
quote = "1.0.21"
|
||||||
|
syn = "1.0.104"
|
45
azalea-inventory/azalea-inventory-macros/src/lib.rs
Normal file
45
azalea-inventory/azalea-inventory-macros/src/lib.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
mod location_enum;
|
||||||
|
mod menu_enum;
|
||||||
|
mod menu_impl;
|
||||||
|
mod parse_macro;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use parse_macro::{DeclareMenus, Field};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{self, parse_macro_input, Ident};
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn declare_menus(input: TokenStream) -> TokenStream {
|
||||||
|
let mut input = parse_macro_input!(input as DeclareMenus);
|
||||||
|
|
||||||
|
// implicitly add a `player` field at the end unless an `inventory` field
|
||||||
|
// is present
|
||||||
|
for menu in &mut input.menus {
|
||||||
|
let mut inventory_field_missing = true;
|
||||||
|
for field in &menu.fields {
|
||||||
|
if matches!(field.name.to_string().as_str(), "inventory" | "player") {
|
||||||
|
inventory_field_missing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inventory_field_missing {
|
||||||
|
menu.fields.push(Field {
|
||||||
|
name: Ident::new("player", Span::call_site()),
|
||||||
|
length: 36,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let menu_enum = menu_enum::generate(&input);
|
||||||
|
let menu_impl = menu_impl::generate(&input);
|
||||||
|
let location_enum = location_enum::generate(&input);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#menu_enum
|
||||||
|
#menu_impl
|
||||||
|
|
||||||
|
#location_enum
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::{parse_macro::DeclareMenus, utils::to_pascal_case};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
pub fn generate(input: &DeclareMenus) -> TokenStream {
|
||||||
|
// pub enum MenuLocation {
|
||||||
|
// Player(PlayerMenuLocation),
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// pub enum PlayerMenuLocation {
|
||||||
|
// CraftResult,
|
||||||
|
// Craft,
|
||||||
|
// Armor,
|
||||||
|
// Inventory,
|
||||||
|
// Offhand,
|
||||||
|
// }
|
||||||
|
// ...
|
||||||
|
|
||||||
|
let mut menu_location_variants = quote! {};
|
||||||
|
let mut enums = quote! {};
|
||||||
|
for menu in &input.menus {
|
||||||
|
let name_snake_case = &menu.name;
|
||||||
|
let variant_name = Ident::new(
|
||||||
|
&to_pascal_case(&name_snake_case.to_string()),
|
||||||
|
name_snake_case.span(),
|
||||||
|
);
|
||||||
|
let enum_name = Ident::new(
|
||||||
|
&format!("{}MenuLocation", variant_name),
|
||||||
|
variant_name.span(),
|
||||||
|
);
|
||||||
|
menu_location_variants.extend(quote! {
|
||||||
|
#variant_name(#enum_name),
|
||||||
|
});
|
||||||
|
let mut individual_menu_location_variants = quote! {};
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
let variant_name =
|
||||||
|
Ident::new(&to_pascal_case(&field_name.to_string()), field_name.span());
|
||||||
|
individual_menu_location_variants.extend(quote! {
|
||||||
|
#variant_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enums.extend(quote! {
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum #enum_name {
|
||||||
|
#individual_menu_location_variants
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub enum MenuLocation {
|
||||||
|
#menu_location_variants
|
||||||
|
}
|
||||||
|
|
||||||
|
#enums
|
||||||
|
}
|
||||||
|
}
|
70
azalea-inventory/azalea-inventory-macros/src/menu_enum.rs
Normal file
70
azalea-inventory/azalea-inventory-macros/src/menu_enum.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//! Generate the `enum menu` and nothing else. Implementations are in
|
||||||
|
//! impl_menu.rs
|
||||||
|
|
||||||
|
use crate::parse_macro::{DeclareMenus, Field, Menu};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate(input: &DeclareMenus) -> TokenStream {
|
||||||
|
let mut variants = quote! {};
|
||||||
|
let mut player_fields = None;
|
||||||
|
for menu in &input.menus {
|
||||||
|
if menu.name == "Player" {
|
||||||
|
player_fields = Some(generate_fields(&menu.fields, true));
|
||||||
|
} else {
|
||||||
|
variants.extend(generate_variant_for_menu(menu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let player_fields = player_fields.expect("Player variant must be present");
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Player {
|
||||||
|
#player_fields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A menu, which is a fixed collection of slots.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Menu {
|
||||||
|
Player(Player),
|
||||||
|
#variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Player {
|
||||||
|
/// craft_result: ItemSlot,
|
||||||
|
/// craft: [ItemSlot; 4],
|
||||||
|
/// armor: [ItemSlot; 4],
|
||||||
|
/// inventory: [ItemSlot; 36],
|
||||||
|
/// offhand: ItemSlot,
|
||||||
|
/// },
|
||||||
|
fn generate_variant_for_menu(menu: &Menu) -> TokenStream {
|
||||||
|
let name = &menu.name;
|
||||||
|
let fields = generate_fields(&menu.fields, false);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#name {
|
||||||
|
#fields
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_fields(fields: &[Field], public: bool) -> TokenStream {
|
||||||
|
let mut generated_fields = quote! {};
|
||||||
|
for field in fields {
|
||||||
|
let field_length = field.length;
|
||||||
|
let field_type = if field.length == 1 {
|
||||||
|
quote! { ItemSlot }
|
||||||
|
} else {
|
||||||
|
quote! { SlotList<#field_length> }
|
||||||
|
};
|
||||||
|
let field_name = &field.name;
|
||||||
|
if public {
|
||||||
|
generated_fields.extend(quote! { pub #field_name: #field_type, })
|
||||||
|
} else {
|
||||||
|
generated_fields.extend(quote! { #field_name: #field_type, })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generated_fields
|
||||||
|
}
|
448
azalea-inventory/azalea-inventory-macros/src/menu_impl.rs
Normal file
448
azalea-inventory/azalea-inventory-macros/src/menu_impl.rs
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
use crate::{
|
||||||
|
parse_macro::{DeclareMenus, Menu},
|
||||||
|
utils::{to_pascal_case, to_snake_case},
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
pub fn generate(input: &DeclareMenus) -> TokenStream {
|
||||||
|
let mut slot_mut_match_variants = quote! {};
|
||||||
|
let mut slot_match_variants = quote! {};
|
||||||
|
let mut len_match_variants = quote! {};
|
||||||
|
let mut kind_match_variants = quote! {};
|
||||||
|
let mut slots_match_variants = quote! {};
|
||||||
|
let mut contents_match_variants = quote! {};
|
||||||
|
let mut location_match_variants = quote! {};
|
||||||
|
let mut player_slots_range_match_variants = quote! {};
|
||||||
|
|
||||||
|
let mut player_consts = quote! {};
|
||||||
|
let mut menu_consts = quote! {};
|
||||||
|
|
||||||
|
let mut hotbar_slots_start = 0;
|
||||||
|
let mut hotbar_slots_end = 0;
|
||||||
|
let mut inventory_without_hotbar_slots_start = 0;
|
||||||
|
let mut inventory_without_hotbar_slots_end = 0;
|
||||||
|
|
||||||
|
for menu in &input.menus {
|
||||||
|
slot_mut_match_variants.extend(generate_match_variant_for_slot_mut(menu, true));
|
||||||
|
slot_match_variants.extend(generate_match_variant_for_slot_mut(menu, false));
|
||||||
|
len_match_variants.extend(generate_match_variant_for_len(menu));
|
||||||
|
kind_match_variants.extend(generate_match_variant_for_kind(menu));
|
||||||
|
slots_match_variants.extend(generate_match_variant_for_slots(menu));
|
||||||
|
contents_match_variants.extend(generate_match_variant_for_contents(menu));
|
||||||
|
location_match_variants.extend(generate_match_variant_for_location(menu));
|
||||||
|
player_slots_range_match_variants
|
||||||
|
.extend(generate_match_variant_for_player_slots_range(menu));
|
||||||
|
|
||||||
|
// this part is only used to generate `Player::is_hotbar_slot`
|
||||||
|
if menu.name == "Player" {
|
||||||
|
let mut i = 0;
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
let start = i;
|
||||||
|
i += field.length;
|
||||||
|
let end = i - 1;
|
||||||
|
|
||||||
|
if field_name == "inventory" {
|
||||||
|
// it only subtracts 8 here since it's inclusive (there's 9 total hotbar slots)
|
||||||
|
hotbar_slots_start = end - 8;
|
||||||
|
hotbar_slots_end = end;
|
||||||
|
|
||||||
|
inventory_without_hotbar_slots_start = start;
|
||||||
|
inventory_without_hotbar_slots_end = end - 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if start == end {
|
||||||
|
let const_name = Ident::new(
|
||||||
|
&format!("{}_SLOT", field_name.to_string().to_uppercase()),
|
||||||
|
field_name.span(),
|
||||||
|
);
|
||||||
|
player_consts.extend(quote! {
|
||||||
|
pub const #const_name: usize = #start;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let const_name = Ident::new(
|
||||||
|
&format!("{}_SLOTS", field_name.to_string().to_uppercase()),
|
||||||
|
field_name.span(),
|
||||||
|
);
|
||||||
|
player_consts.extend(quote! {
|
||||||
|
pub const #const_name: RangeInclusive<usize> = #start..=#end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu_consts.extend(generate_menu_consts(menu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(hotbar_slots_start != 0 && hotbar_slots_end != 0);
|
||||||
|
quote! {
|
||||||
|
impl Player {
|
||||||
|
pub const HOTBAR_SLOTS: RangeInclusive<usize> = #hotbar_slots_start..=#hotbar_slots_end;
|
||||||
|
pub const INVENTORY_WITHOUT_HOTBAR_SLOTS: RangeInclusive<usize> = #inventory_without_hotbar_slots_start..=#inventory_without_hotbar_slots_end;
|
||||||
|
#player_consts
|
||||||
|
|
||||||
|
/// Returns whether the given protocol index is in the player's hotbar.
|
||||||
|
///
|
||||||
|
/// Equivalent to `Player::HOTBAR_SLOTS.contains(&i)`.
|
||||||
|
pub fn is_hotbar_slot(i: usize) -> bool {
|
||||||
|
Self::HOTBAR_SLOTS.contains(&i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
#menu_consts
|
||||||
|
|
||||||
|
/// Get a mutable reference to the [`ItemSlot`] at the given protocol index.
|
||||||
|
///
|
||||||
|
/// If you're trying to get an item in a menu without caring about
|
||||||
|
/// protocol indexes, you should just `match` it and index the
|
||||||
|
/// [`ItemSlot`] you get.
|
||||||
|
///
|
||||||
|
/// Use [`Menu::slot`] if you don't need a mutable reference to the slot.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `None` if the index is out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn slot_mut(&mut self, i: usize) -> Option<&mut ItemSlot> {
|
||||||
|
Some(match self {
|
||||||
|
#slot_mut_match_variants
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the [`ItemSlot`] at the given protocol index.
|
||||||
|
///
|
||||||
|
/// If you're trying to get an item in a menu without caring about
|
||||||
|
/// protocol indexes, you should just `match` it and index the
|
||||||
|
/// [`ItemSlot`] you get.
|
||||||
|
///
|
||||||
|
/// Use [`Menu::slot_mut`] if you need a mutable reference to the slot.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `None` if the index is out of bounds.
|
||||||
|
pub fn slot(&self, i: usize) -> Option<&ItemSlot> {
|
||||||
|
Some(match self {
|
||||||
|
#slot_match_variants
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of slots in the menu.
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
pub const fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
#len_match_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_kind(kind: azalea_registry::MenuKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
#kind_match_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the contents of the menu, including the player's inventory.
|
||||||
|
///
|
||||||
|
/// The indexes in this will match up with [`Menu::slot_mut`].
|
||||||
|
///
|
||||||
|
/// If you don't want to include the player's inventory, use [`Menu::contents`] instead.
|
||||||
|
pub fn slots(&self) -> Vec<ItemSlot> {
|
||||||
|
match self {
|
||||||
|
#slots_match_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the contents of the menu, not including the player's inventory.
|
||||||
|
///
|
||||||
|
/// If you want to include the player's inventory, use [`Menu::slots`] instead.
|
||||||
|
pub fn contents(&self) -> Vec<ItemSlot> {
|
||||||
|
match self {
|
||||||
|
#contents_match_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn location_for_slot(&self, i: usize) -> Option<MenuLocation> {
|
||||||
|
Some(match self {
|
||||||
|
#location_match_variants
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the range of slot indexes that contain the player's inventory. This may be different for each menu.
|
||||||
|
pub fn player_slots_range(&self) -> RangeInclusive<usize> {
|
||||||
|
match self {
|
||||||
|
#player_slots_range_match_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the range of slot indexes that contain the player's hotbar. This may be different for each menu.
|
||||||
|
pub fn hotbar_slots_range(&self) -> RangeInclusive<usize> {
|
||||||
|
// hotbar is always last 9 slots in the player's inventory
|
||||||
|
((*self.player_slots_range().end() - 8)..=*self.player_slots_range().end())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the range of slot indexes that contain the player's inventory, not including the hotbar. This may be different for each menu.
|
||||||
|
pub fn player_slots_without_hotbar_range(&self) -> RangeInclusive<usize> {
|
||||||
|
(*self.player_slots_range().start()..=*self.player_slots_range().end() - 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the given index would be in the player's hotbar.
|
||||||
|
///
|
||||||
|
/// Equivalent to `self.hotbar_slots_range().contains(&i)`.
|
||||||
|
pub fn is_hotbar_slot(&self, i: usize) -> bool {
|
||||||
|
self.hotbar_slots_range().contains(&i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Menu::Player {
|
||||||
|
/// craft_result,
|
||||||
|
/// craft,
|
||||||
|
/// armor,
|
||||||
|
/// inventory,
|
||||||
|
/// offhand,
|
||||||
|
/// } => {
|
||||||
|
/// match i {
|
||||||
|
/// 0 => craft_result,
|
||||||
|
/// 1..=4 => craft,
|
||||||
|
/// 5..=8 => armor,
|
||||||
|
/// // ...
|
||||||
|
/// _ => return None,
|
||||||
|
/// }
|
||||||
|
/// } // ...
|
||||||
|
pub fn generate_match_variant_for_slot_mut(menu: &Menu, mutable: bool) -> TokenStream {
|
||||||
|
let mut match_arms = quote! {};
|
||||||
|
let mut i = 0;
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
let start = i;
|
||||||
|
i += field.length;
|
||||||
|
let end = i - 1;
|
||||||
|
match_arms.extend(if start == end {
|
||||||
|
quote! { #start => #field_name, }
|
||||||
|
} else if start == 0 {
|
||||||
|
if mutable {
|
||||||
|
quote! { #start..=#end => &mut #field_name[i], }
|
||||||
|
} else {
|
||||||
|
quote! { #start..=#end => &#field_name[i], }
|
||||||
|
}
|
||||||
|
} else if mutable {
|
||||||
|
quote! { #start..=#end => &mut #field_name[i - #start], }
|
||||||
|
} else {
|
||||||
|
quote! { #start..=#end => &#field_name[i - #start], }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_matcher(
|
||||||
|
menu,
|
||||||
|
"e! {
|
||||||
|
match i {
|
||||||
|
#match_arms
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_len(menu: &Menu) -> TokenStream {
|
||||||
|
let length = menu.fields.iter().map(|f| f.length).sum::<usize>();
|
||||||
|
generate_matcher(
|
||||||
|
menu,
|
||||||
|
"e! {
|
||||||
|
#length
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_kind(menu: &Menu) -> TokenStream {
|
||||||
|
// azalea_registry::MenuKind::Generic9x3 => Menu::Generic9x3 { contents:
|
||||||
|
// Default::default(), player: Default::default() },
|
||||||
|
|
||||||
|
let menu_name = &menu.name;
|
||||||
|
let menu_field_names = if menu.name == "Player" {
|
||||||
|
// player isn't in MenuKind
|
||||||
|
return quote! {};
|
||||||
|
} else {
|
||||||
|
let mut menu_field_names = quote! {};
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
menu_field_names.extend(quote! { #field_name: Default::default(), })
|
||||||
|
}
|
||||||
|
quote! { { #menu_field_names } }
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
azalea_registry::MenuKind::#menu_name => Menu::#menu_name #menu_field_names,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_slots(menu: &Menu) -> TokenStream {
|
||||||
|
let mut instructions = quote! {};
|
||||||
|
let mut length = 0;
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
instructions.extend(if field.length == 1 {
|
||||||
|
quote! { items.push(#field_name.clone()); }
|
||||||
|
} else {
|
||||||
|
quote! { items.extend(#field_name.iter().cloned()); }
|
||||||
|
});
|
||||||
|
length += field.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_matcher(
|
||||||
|
menu,
|
||||||
|
"e! {
|
||||||
|
let mut items = Vec::with_capacity(#length);
|
||||||
|
#instructions
|
||||||
|
items
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_contents(menu: &Menu) -> TokenStream {
|
||||||
|
let mut instructions = quote! {};
|
||||||
|
let mut length = 0;
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
if field_name == "player" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
instructions.extend(if field.length == 1 {
|
||||||
|
quote! { items.push(#field_name.clone()); }
|
||||||
|
} else {
|
||||||
|
quote! { items.extend(#field_name.iter().cloned()); }
|
||||||
|
});
|
||||||
|
length += field.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_matcher(
|
||||||
|
menu,
|
||||||
|
"e! {
|
||||||
|
let mut items = Vec::with_capacity(#length);
|
||||||
|
#instructions
|
||||||
|
items
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_location(menu: &Menu) -> TokenStream {
|
||||||
|
let mut match_arms = quote! {};
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let menu_name = Ident::new(&to_pascal_case(&menu.name.to_string()), menu.name.span());
|
||||||
|
let menu_enum_name = Ident::new(&format!("{menu_name}MenuLocation"), menu_name.span());
|
||||||
|
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = Ident::new(&to_pascal_case(&field.name.to_string()), field.name.span());
|
||||||
|
let start = i;
|
||||||
|
i += field.length;
|
||||||
|
let end = i - 1;
|
||||||
|
match_arms.extend(if start == end {
|
||||||
|
quote! { #start => #menu_enum_name::#field_name, }
|
||||||
|
} else {
|
||||||
|
quote! { #start..=#end => #menu_enum_name::#field_name, }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_matcher(
|
||||||
|
menu,
|
||||||
|
"e! {
|
||||||
|
MenuLocation::#menu_name(match i {
|
||||||
|
#match_arms
|
||||||
|
_ => return None
|
||||||
|
})
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_match_variant_for_player_slots_range(menu: &Menu) -> TokenStream {
|
||||||
|
// Menu::Player(Player { .. }) => Player::INVENTORY_SLOTS_RANGE,,
|
||||||
|
// Menu::Generic9x3 { .. } => Menu::GENERIC9X3_SLOTS_RANGE,
|
||||||
|
// ..
|
||||||
|
|
||||||
|
match menu.name.to_string().as_str() {
|
||||||
|
"Player" => {
|
||||||
|
quote! {
|
||||||
|
Menu::Player(Player { .. }) => Player::INVENTORY_SLOTS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let menu_name = &menu.name;
|
||||||
|
let menu_slots_range_name = Ident::new(
|
||||||
|
&format!(
|
||||||
|
"{}_PLAYER_SLOTS",
|
||||||
|
to_snake_case(&menu.name.to_string()).to_uppercase()
|
||||||
|
),
|
||||||
|
menu.name.span(),
|
||||||
|
);
|
||||||
|
quote! {
|
||||||
|
Menu::#menu_name { .. } => Menu::#menu_slots_range_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_menu_consts(menu: &Menu) -> TokenStream {
|
||||||
|
let mut menu_consts = quote! {};
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name_start = format!(
|
||||||
|
"{}_{}",
|
||||||
|
to_snake_case(&menu.name.to_string()).to_uppercase(),
|
||||||
|
to_snake_case(&field.name.to_string()).to_uppercase()
|
||||||
|
);
|
||||||
|
let field_index_start = i;
|
||||||
|
i += field.length;
|
||||||
|
let field_index_end = i - 1;
|
||||||
|
|
||||||
|
if field.length == 1 {
|
||||||
|
let field_name = Ident::new(
|
||||||
|
format!("{}_SLOT", field_name_start).as_str(),
|
||||||
|
field.name.span(),
|
||||||
|
);
|
||||||
|
menu_consts.extend(quote! { pub const #field_name: usize = #field_index_start; });
|
||||||
|
} else {
|
||||||
|
let field_name = Ident::new(
|
||||||
|
format!("{}_SLOTS", field_name_start).as_str(),
|
||||||
|
field.name.span(),
|
||||||
|
);
|
||||||
|
menu_consts.extend(quote! { pub const #field_name: RangeInclusive<usize> = #field_index_start..=#field_index_end; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_consts
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_matcher(menu: &Menu, match_arms: &TokenStream, needs_fields: bool) -> TokenStream {
|
||||||
|
let menu_name = &menu.name;
|
||||||
|
let menu_field_names = if needs_fields {
|
||||||
|
let mut menu_field_names = quote! {};
|
||||||
|
for field in &menu.fields {
|
||||||
|
let field_name = &field.name;
|
||||||
|
menu_field_names.extend(quote! { #field_name, })
|
||||||
|
}
|
||||||
|
menu_field_names
|
||||||
|
} else {
|
||||||
|
quote! { .. }
|
||||||
|
};
|
||||||
|
|
||||||
|
let matcher = if menu.name == "Player" {
|
||||||
|
quote! { (Player { #menu_field_names }) }
|
||||||
|
} else {
|
||||||
|
quote! { { #menu_field_names } }
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
Menu::#menu_name #matcher => {
|
||||||
|
#match_arms
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
69
azalea-inventory/azalea-inventory-macros/src/parse_macro.rs
Normal file
69
azalea-inventory/azalea-inventory-macros/src/parse_macro.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use syn::{
|
||||||
|
self, braced,
|
||||||
|
parse::{Parse, ParseStream, Result},
|
||||||
|
Ident, LitInt, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An identifier, colon, and number
|
||||||
|
/// `craft_result: 1`
|
||||||
|
pub struct Field {
|
||||||
|
pub name: Ident,
|
||||||
|
pub length: usize,
|
||||||
|
}
|
||||||
|
impl Parse for Field {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let name = input.parse::<Ident>()?;
|
||||||
|
let _ = input.parse::<Token![:]>()?;
|
||||||
|
let length = input.parse::<LitInt>()?.base10_parse()?;
|
||||||
|
Ok(Self { name, length })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier and a list of `Field` in curly brackets
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// Player {
|
||||||
|
/// craft_result: 1,
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Menu {
|
||||||
|
/// The menu name, e.g. `Player`
|
||||||
|
pub name: Ident,
|
||||||
|
pub fields: Vec<Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Menu {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let name = input.parse::<Ident>()?;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let fields = content
|
||||||
|
.parse_terminated::<Field, Token![,]>(Field::parse)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Self { name, fields })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of `Menu`s
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// Player {
|
||||||
|
/// craft_result: 1,
|
||||||
|
/// ...
|
||||||
|
/// },
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
pub struct DeclareMenus {
|
||||||
|
pub menus: Vec<Menu>,
|
||||||
|
}
|
||||||
|
impl Parse for DeclareMenus {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let menus = input
|
||||||
|
.parse_terminated::<Menu, Token![,]>(Menu::parse)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
Ok(Self { menus })
|
||||||
|
}
|
||||||
|
}
|
54
azalea-inventory/azalea-inventory-macros/src/utils.rs
Normal file
54
azalea-inventory/azalea-inventory-macros/src/utils.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
pub fn to_pascal_case(s: &str) -> String {
|
||||||
|
// we get the first item later so this is to make it impossible for that
|
||||||
|
// to error
|
||||||
|
if s.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut prev_was_underscore = true; // set to true by default so the first character is capitalized
|
||||||
|
if s.chars().next().unwrap().is_numeric() {
|
||||||
|
result.push('_');
|
||||||
|
}
|
||||||
|
for c in s.chars() {
|
||||||
|
if c == '_' {
|
||||||
|
prev_was_underscore = true;
|
||||||
|
} else if prev_was_underscore {
|
||||||
|
result.push(c.to_ascii_uppercase());
|
||||||
|
prev_was_underscore = false;
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_snake_case(s: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut prev_was_uppercase = true;
|
||||||
|
for c in s.chars() {
|
||||||
|
if c.is_ascii_uppercase() {
|
||||||
|
if !prev_was_uppercase {
|
||||||
|
result.push('_');
|
||||||
|
}
|
||||||
|
result.push(c.to_ascii_lowercase());
|
||||||
|
prev_was_uppercase = true;
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
prev_was_uppercase = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snake_case() {
|
||||||
|
assert_eq!(to_snake_case("HelloWorld"), "hello_world");
|
||||||
|
assert_eq!(to_snake_case("helloWorld"), "hello_world");
|
||||||
|
assert_eq!(to_snake_case("hello_world"), "hello_world");
|
||||||
|
}
|
||||||
|
}
|
21
azalea-inventory/src/item/mod.rs
Normal file
21
azalea-inventory/src/item/mod.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
pub trait MaxStackSizeExt {
|
||||||
|
/// Get the maximum stack size for this item.
|
||||||
|
///
|
||||||
|
/// This is a signed integer to be consistent with the `count` field of
|
||||||
|
/// [`ItemSlotData`].
|
||||||
|
fn max_stack_size(&self) -> i8;
|
||||||
|
|
||||||
|
/// Whether this item can be stacked with other items.
|
||||||
|
///
|
||||||
|
/// This is equivalent to `self.max_stack_size() > 1`.
|
||||||
|
fn stackable(&self) -> bool {
|
||||||
|
self.max_stack_size() > 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaxStackSizeExt for azalea_registry::Item {
|
||||||
|
fn max_stack_size(&self) -> i8 {
|
||||||
|
// TODO: have the properties for every item defined somewhere
|
||||||
|
64
|
||||||
|
}
|
||||||
|
}
|
172
azalea-inventory/src/lib.rs
Normal file
172
azalea-inventory/src/lib.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
pub mod item;
|
||||||
|
pub mod operations;
|
||||||
|
mod slot;
|
||||||
|
|
||||||
|
use std::ops::{Deref, DerefMut, RangeInclusive};
|
||||||
|
|
||||||
|
use azalea_inventory_macros::declare_menus;
|
||||||
|
pub use slot::{ItemSlot, ItemSlotData};
|
||||||
|
|
||||||
|
// TODO: remove this here and in azalea-inventory-macros when rust makes
|
||||||
|
// Default be implemented for all array sizes (since right now it's only up to
|
||||||
|
// 32)
|
||||||
|
|
||||||
|
/// A fixed-size list of [`ItemSlot`]s.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SlotList<const N: usize>([ItemSlot; N]);
|
||||||
|
impl<const N: usize> Deref for SlotList<N> {
|
||||||
|
type Target = [ItemSlot; N];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> DerefMut for SlotList<N> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Default for SlotList<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
SlotList([(); N].map(|_| ItemSlot::Empty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
/// Get the [`Player`] from this [`Menu`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if the menu isn't `Menu::Player`.
|
||||||
|
pub fn as_player(&self) -> &Player {
|
||||||
|
if let Menu::Player(player) = &self {
|
||||||
|
player
|
||||||
|
} else {
|
||||||
|
unreachable!("Called `Menu::as_player` on a menu that wasn't `Player`.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the player inventory part is always the last 36 slots (except in the Player
|
||||||
|
// menu), so we don't have to explicitly specify it
|
||||||
|
|
||||||
|
// Client {
|
||||||
|
// ...
|
||||||
|
// pub menu: Menu,
|
||||||
|
// pub inventory: Arc<[Slot; 36]>
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Generate a `struct Player`, `enum Menu`, and `impl Menu`.
|
||||||
|
// a "player" field gets implicitly added with the player inventory
|
||||||
|
|
||||||
|
declare_menus! {
|
||||||
|
Player {
|
||||||
|
craft_result: 1,
|
||||||
|
craft: 4,
|
||||||
|
armor: 4,
|
||||||
|
inventory: 36,
|
||||||
|
offhand: 1,
|
||||||
|
},
|
||||||
|
Generic9x1 {
|
||||||
|
contents: 9,
|
||||||
|
},
|
||||||
|
Generic9x2 {
|
||||||
|
contents: 18,
|
||||||
|
},
|
||||||
|
Generic9x3 {
|
||||||
|
contents: 27,
|
||||||
|
},
|
||||||
|
Generic9x4 {
|
||||||
|
contents: 36,
|
||||||
|
},
|
||||||
|
Generic9x5 {
|
||||||
|
contents: 45,
|
||||||
|
},
|
||||||
|
Generic9x6 {
|
||||||
|
contents: 54,
|
||||||
|
},
|
||||||
|
Generic3x3 {
|
||||||
|
contents: 9,
|
||||||
|
},
|
||||||
|
Anvil {
|
||||||
|
first: 1,
|
||||||
|
second: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Beacon {
|
||||||
|
payment: 1,
|
||||||
|
},
|
||||||
|
BlastFurnace {
|
||||||
|
ingredient: 1,
|
||||||
|
fuel: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
BrewingStand {
|
||||||
|
bottles: 3,
|
||||||
|
ingredient: 1,
|
||||||
|
fuel: 1,
|
||||||
|
},
|
||||||
|
Crafting {
|
||||||
|
result: 1,
|
||||||
|
grid: 9,
|
||||||
|
},
|
||||||
|
Enchantment {
|
||||||
|
item: 1,
|
||||||
|
lapis: 1,
|
||||||
|
},
|
||||||
|
Furnace {
|
||||||
|
ingredient: 1,
|
||||||
|
fuel: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Grindstone {
|
||||||
|
input: 1,
|
||||||
|
additional: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Hopper {
|
||||||
|
contents: 5,
|
||||||
|
},
|
||||||
|
Lectern {
|
||||||
|
book: 1,
|
||||||
|
},
|
||||||
|
Loom {
|
||||||
|
banner: 1,
|
||||||
|
dye: 1,
|
||||||
|
pattern: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Merchant {
|
||||||
|
payments: 2,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
ShulkerBox {
|
||||||
|
contents: 27,
|
||||||
|
},
|
||||||
|
LegacySmithing {
|
||||||
|
input: 1,
|
||||||
|
additional: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Smithing {
|
||||||
|
template: 1,
|
||||||
|
base: 1,
|
||||||
|
additional: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Smoker {
|
||||||
|
ingredient: 1,
|
||||||
|
fuel: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
CartographyTable {
|
||||||
|
map: 1,
|
||||||
|
additional: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
Stonecutter {
|
||||||
|
input: 1,
|
||||||
|
result: 1,
|
||||||
|
},
|
||||||
|
}
|
698
azalea-inventory/src/operations.rs
Normal file
698
azalea-inventory/src/operations.rs
Normal file
|
@ -0,0 +1,698 @@
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
use azalea_buf::McBuf;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
item::MaxStackSizeExt, AnvilMenuLocation, BeaconMenuLocation, BlastFurnaceMenuLocation,
|
||||||
|
BrewingStandMenuLocation, CartographyTableMenuLocation, CraftingMenuLocation,
|
||||||
|
EnchantmentMenuLocation, FurnaceMenuLocation, Generic3x3MenuLocation, Generic9x1MenuLocation,
|
||||||
|
Generic9x2MenuLocation, Generic9x3MenuLocation, Generic9x4MenuLocation, Generic9x5MenuLocation,
|
||||||
|
Generic9x6MenuLocation, GrindstoneMenuLocation, HopperMenuLocation, ItemSlot, ItemSlotData,
|
||||||
|
LecternMenuLocation, LegacySmithingMenuLocation, LoomMenuLocation, Menu, MenuLocation,
|
||||||
|
MerchantMenuLocation, Player, PlayerMenuLocation, ShulkerBoxMenuLocation, SmithingMenuLocation,
|
||||||
|
SmokerMenuLocation, StonecutterMenuLocation,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ClickOperation {
|
||||||
|
Pickup(PickupClick),
|
||||||
|
QuickMove(QuickMoveClick),
|
||||||
|
Swap(SwapClick),
|
||||||
|
Clone(CloneClick),
|
||||||
|
Throw(ThrowClick),
|
||||||
|
QuickCraft(QuickCraftClick),
|
||||||
|
PickupAll(PickupAllClick),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PickupClick {
|
||||||
|
/// Left mouse click. Note that in the protocol, None is represented as
|
||||||
|
/// -999.
|
||||||
|
Left { slot: Option<u16> },
|
||||||
|
/// Right mouse click. Note that in the protocol, None is represented as
|
||||||
|
/// -999.
|
||||||
|
Right { slot: Option<u16> },
|
||||||
|
/// Drop cursor stack.
|
||||||
|
LeftOutside,
|
||||||
|
/// Drop cursor single item.
|
||||||
|
RightOutside,
|
||||||
|
}
|
||||||
|
impl From<PickupClick> for ClickOperation {
|
||||||
|
fn from(click: PickupClick) -> Self {
|
||||||
|
ClickOperation::Pickup(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shift click
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum QuickMoveClick {
|
||||||
|
/// Shift + left mouse click
|
||||||
|
Left { slot: u16 },
|
||||||
|
/// Shift + right mouse click (identical behavior)
|
||||||
|
Right { slot: u16 },
|
||||||
|
}
|
||||||
|
impl From<QuickMoveClick> for ClickOperation {
|
||||||
|
fn from(click: QuickMoveClick) -> Self {
|
||||||
|
ClickOperation::QuickMove(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used when you press number keys or F in an inventory.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SwapClick {
|
||||||
|
pub source_slot: u16,
|
||||||
|
pub target_slot: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SwapClick> for ClickOperation {
|
||||||
|
fn from(click: SwapClick) -> Self {
|
||||||
|
ClickOperation::Swap(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Middle click, only defined for creative players in non-player
|
||||||
|
/// inventories.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CloneClick {
|
||||||
|
pub slot: u16,
|
||||||
|
}
|
||||||
|
impl From<CloneClick> for ClickOperation {
|
||||||
|
fn from(click: CloneClick) -> Self {
|
||||||
|
ClickOperation::Clone(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ThrowClick {
|
||||||
|
/// Drop key (Q)
|
||||||
|
Single { slot: u16 },
|
||||||
|
/// Ctrl + drop key (Q)
|
||||||
|
All { slot: u16 },
|
||||||
|
}
|
||||||
|
impl From<ThrowClick> for ClickOperation {
|
||||||
|
fn from(click: ThrowClick) -> Self {
|
||||||
|
ClickOperation::Throw(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct QuickCraftClick {
|
||||||
|
pub kind: QuickCraftKind,
|
||||||
|
pub status: QuickCraftStatus,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum QuickCraftKind {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Middle,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum QuickCraftStatusKind {
|
||||||
|
/// Starting drag
|
||||||
|
Start,
|
||||||
|
/// Add slot
|
||||||
|
Add,
|
||||||
|
/// Ending drag
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum QuickCraftStatus {
|
||||||
|
/// Starting drag
|
||||||
|
Start,
|
||||||
|
/// Add a slot.
|
||||||
|
Add { slot: u16 },
|
||||||
|
/// Ending drag
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
impl From<QuickCraftStatus> for QuickCraftStatusKind {
|
||||||
|
fn from(status: QuickCraftStatus) -> Self {
|
||||||
|
match status {
|
||||||
|
QuickCraftStatus::Start => QuickCraftStatusKind::Start,
|
||||||
|
QuickCraftStatus::Add { .. } => QuickCraftStatusKind::Add,
|
||||||
|
QuickCraftStatus::End => QuickCraftStatusKind::End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Double click
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PickupAllClick {
|
||||||
|
/// The slot that we're double clicking on. It should be empty or at least
|
||||||
|
/// not pickup-able (since the carried item is used as the filter).
|
||||||
|
pub slot: u16,
|
||||||
|
/// Impossible in vanilla clients.
|
||||||
|
pub reversed: bool,
|
||||||
|
}
|
||||||
|
impl From<PickupAllClick> for ClickOperation {
|
||||||
|
fn from(click: PickupAllClick) -> Self {
|
||||||
|
ClickOperation::PickupAll(click)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClickOperation {
|
||||||
|
/// Return the slot number that this operation is acting on, if any.
|
||||||
|
///
|
||||||
|
/// Note that in the protocol, "None" is represented as -999.
|
||||||
|
pub fn slot_num(&self) -> Option<u16> {
|
||||||
|
match self {
|
||||||
|
ClickOperation::Pickup(pickup) => match pickup {
|
||||||
|
PickupClick::Left { slot } => *slot,
|
||||||
|
PickupClick::Right { slot } => *slot,
|
||||||
|
PickupClick::LeftOutside => None,
|
||||||
|
PickupClick::RightOutside => None,
|
||||||
|
},
|
||||||
|
ClickOperation::QuickMove(quick_move) => match quick_move {
|
||||||
|
QuickMoveClick::Left { slot } => Some(*slot),
|
||||||
|
QuickMoveClick::Right { slot } => Some(*slot),
|
||||||
|
},
|
||||||
|
ClickOperation::Swap(swap) => Some(swap.source_slot),
|
||||||
|
ClickOperation::Clone(clone) => Some(clone.slot),
|
||||||
|
ClickOperation::Throw(throw) => match throw {
|
||||||
|
ThrowClick::Single { slot } => Some(*slot),
|
||||||
|
ThrowClick::All { slot } => Some(*slot),
|
||||||
|
},
|
||||||
|
ClickOperation::QuickCraft(quick_craft) => match quick_craft.status {
|
||||||
|
QuickCraftStatus::Start => None,
|
||||||
|
QuickCraftStatus::Add { slot } => Some(slot),
|
||||||
|
QuickCraftStatus::End => None,
|
||||||
|
},
|
||||||
|
ClickOperation::PickupAll(pickup_all) => Some(pickup_all.slot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button_num(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
ClickOperation::Pickup(pickup) => match pickup {
|
||||||
|
PickupClick::Left { .. } => 0,
|
||||||
|
PickupClick::Right { .. } => 1,
|
||||||
|
PickupClick::LeftOutside => 0,
|
||||||
|
PickupClick::RightOutside => 1,
|
||||||
|
},
|
||||||
|
ClickOperation::QuickMove(quick_move) => match quick_move {
|
||||||
|
QuickMoveClick::Left { .. } => 0,
|
||||||
|
QuickMoveClick::Right { .. } => 1,
|
||||||
|
},
|
||||||
|
ClickOperation::Swap(swap) => swap.target_slot,
|
||||||
|
ClickOperation::Clone(_) => 2,
|
||||||
|
ClickOperation::Throw(throw) => match throw {
|
||||||
|
ThrowClick::Single { .. } => 0,
|
||||||
|
ThrowClick::All { .. } => 1,
|
||||||
|
},
|
||||||
|
ClickOperation::QuickCraft(quick_craft) => match quick_craft {
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Left,
|
||||||
|
status: QuickCraftStatus::Start,
|
||||||
|
} => 0,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Right,
|
||||||
|
status: QuickCraftStatus::Start,
|
||||||
|
} => 4,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Middle,
|
||||||
|
status: QuickCraftStatus::Start,
|
||||||
|
} => 8,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Left,
|
||||||
|
status: QuickCraftStatus::Add { .. },
|
||||||
|
} => 1,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Right,
|
||||||
|
status: QuickCraftStatus::Add { .. },
|
||||||
|
} => 5,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Middle,
|
||||||
|
status: QuickCraftStatus::Add { .. },
|
||||||
|
} => 9,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Left,
|
||||||
|
status: QuickCraftStatus::End,
|
||||||
|
} => 2,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Right,
|
||||||
|
status: QuickCraftStatus::End,
|
||||||
|
} => 6,
|
||||||
|
QuickCraftClick {
|
||||||
|
kind: QuickCraftKind::Middle,
|
||||||
|
status: QuickCraftStatus::End,
|
||||||
|
} => 10,
|
||||||
|
},
|
||||||
|
ClickOperation::PickupAll(_) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn click_type(&self) -> ClickType {
|
||||||
|
match self {
|
||||||
|
ClickOperation::Pickup(_) => ClickType::Pickup,
|
||||||
|
ClickOperation::QuickMove(_) => ClickType::QuickMove,
|
||||||
|
ClickOperation::Swap(_) => ClickType::Swap,
|
||||||
|
ClickOperation::Clone(_) => ClickType::Clone,
|
||||||
|
ClickOperation::Throw(_) => ClickType::Throw,
|
||||||
|
ClickOperation::QuickCraft(_) => ClickType::QuickCraft,
|
||||||
|
ClickOperation::PickupAll(_) => ClickType::PickupAll,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(McBuf, Clone, Copy, Debug)]
|
||||||
|
pub enum ClickType {
|
||||||
|
Pickup = 0,
|
||||||
|
QuickMove = 1,
|
||||||
|
Swap = 2,
|
||||||
|
Clone = 3,
|
||||||
|
Throw = 4,
|
||||||
|
QuickCraft = 5,
|
||||||
|
PickupAll = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
/// Shift-click a slot in this menu.
|
||||||
|
pub fn quick_move_stack(&mut self, slot_index: usize) -> ItemSlot {
|
||||||
|
let slot = self.slot(slot_index);
|
||||||
|
if slot.is_none() {
|
||||||
|
return ItemSlot::Empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
let slot_location = self
|
||||||
|
.location_for_slot(slot_index)
|
||||||
|
.expect("we just checked to make sure the slot is Some above, so this shouldn't be able to error");
|
||||||
|
match slot_location {
|
||||||
|
MenuLocation::Player(l) => match l {
|
||||||
|
PlayerMenuLocation::CraftResult => {
|
||||||
|
self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
|
||||||
|
}
|
||||||
|
PlayerMenuLocation::Craft => {
|
||||||
|
self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
|
||||||
|
}
|
||||||
|
PlayerMenuLocation::Armor => {
|
||||||
|
self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO: armor handling (see quickMoveStack in
|
||||||
|
// InventoryMenu.java)
|
||||||
|
|
||||||
|
// if slot.kind().is_armor() &&
|
||||||
|
|
||||||
|
// also offhand handling
|
||||||
|
|
||||||
|
if l == PlayerMenuLocation::Inventory {
|
||||||
|
// shift-clicking in hotbar moves to inventory, and vice versa
|
||||||
|
if Player::is_hotbar_slot(slot_index) {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
Player::INVENTORY_WITHOUT_HOTBAR_SLOTS,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.try_move_item_to_slots(slot_index, Player::HOTBAR_SLOTS);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x1(l) => match l {
|
||||||
|
Generic9x1MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x1MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X1_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x2(l) => match l {
|
||||||
|
Generic9x2MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x2MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X2_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x3(l) => match l {
|
||||||
|
Generic9x3MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x3MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X3_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x4(l) => match l {
|
||||||
|
Generic9x4MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x4MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X4_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x5(l) => match l {
|
||||||
|
Generic9x5MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x5MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X5_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic9x6(l) => match l {
|
||||||
|
Generic9x6MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic9x6MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC9X6_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Generic3x3(l) => match l {
|
||||||
|
Generic3x3MenuLocation::Contents => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
Generic3x3MenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GENERIC3X3_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Anvil(l) => match l {
|
||||||
|
AnvilMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::ANVIL_FIRST_SLOT..=Menu::ANVIL_SECOND_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Beacon(l) => match l {
|
||||||
|
BeaconMenuLocation::Payment => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
BeaconMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
Menu::BEACON_PAYMENT_SLOT..=Menu::BEACON_PAYMENT_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::BlastFurnace(l) => match l {
|
||||||
|
BlastFurnaceMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
Menu::BLAST_FURNACE_INGREDIENT_SLOT..=Menu::BLAST_FURNACE_FUEL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::BrewingStand(l) => match l {
|
||||||
|
BrewingStandMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
*Menu::BREWING_STAND_BOTTLES_SLOTS.start()
|
||||||
|
..=Menu::BREWING_STAND_INGREDIENT_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Crafting(l) => match l {
|
||||||
|
CraftingMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::CRAFTING_GRID_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Enchantment(l) => match l {
|
||||||
|
EnchantmentMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::ENCHANTMENT_ITEM_SLOT..=Menu::ENCHANTMENT_LAPIS_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Furnace(l) => match l {
|
||||||
|
FurnaceMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
Menu::FURNACE_INGREDIENT_SLOT..=Menu::FURNACE_FUEL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Grindstone(l) => match l {
|
||||||
|
GrindstoneMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::GRINDSTONE_INPUT_SLOT..=Menu::GRINDSTONE_ADDITIONAL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Hopper(l) => match l {
|
||||||
|
HopperMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::HOPPER_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Lectern(l) => match l {
|
||||||
|
LecternMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::LECTERN_BOOK_SLOT..=Menu::LECTERN_BOOK_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Loom(l) => match l {
|
||||||
|
LoomMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::LOOM_BANNER_SLOT..=Menu::LOOM_PATTERN_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Merchant(l) => match l {
|
||||||
|
MerchantMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::MERCHANT_PAYMENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::ShulkerBox(l) => match l {
|
||||||
|
ShulkerBoxMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::SHULKER_BOX_CONTENTS_SLOTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::LegacySmithing(l) => match l {
|
||||||
|
LegacySmithingMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::LEGACY_SMITHING_INPUT_SLOT..=Menu::LEGACY_SMITHING_ADDITIONAL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Smithing(l) => match l {
|
||||||
|
SmithingMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::SMITHING_TEMPLATE_SLOT..=Menu::SMITHING_ADDITIONAL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Smoker(l) => match l {
|
||||||
|
SmokerMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
Menu::SMOKER_INGREDIENT_SLOT..=Menu::SMOKER_FUEL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::CartographyTable(l) => match l {
|
||||||
|
CartographyTableMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::CARTOGRAPHY_TABLE_MAP_SLOT..=Menu::CARTOGRAPHY_TABLE_ADDITIONAL_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuLocation::Stonecutter(l) => match l {
|
||||||
|
StonecutterMenuLocation::Player => {
|
||||||
|
self.try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
slot_index,
|
||||||
|
Menu::STONECUTTER_INPUT_SLOT..=Menu::STONECUTTER_INPUT_SLOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.try_move_item_to_slots(slot_index, self.player_slots_range());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemSlot::Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_move_item_to_slots_or_toggle_hotbar(
|
||||||
|
&mut self,
|
||||||
|
slot_index: usize,
|
||||||
|
target_slot_indexes: RangeInclusive<usize>,
|
||||||
|
) {
|
||||||
|
if !self.try_move_item_to_slots(slot_index, target_slot_indexes) {
|
||||||
|
self.try_move_item_to_slots(
|
||||||
|
slot_index,
|
||||||
|
if self.is_hotbar_slot(slot_index) {
|
||||||
|
self.player_slots_without_hotbar_range()
|
||||||
|
} else {
|
||||||
|
self.hotbar_slots_range()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the given item could be placed in this menu.
|
||||||
|
///
|
||||||
|
/// TODO: right now this always returns true
|
||||||
|
pub fn may_place(&self, _target_slot_index: usize, _item: &ItemSlotData) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the item in the given slot could be clicked and picked up.
|
||||||
|
/// TODO: right now this always returns true
|
||||||
|
pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the maximum number of items that can be placed in this slot.
|
||||||
|
pub fn max_stack_size(&self, _target_slot_index: usize) -> u8 {
|
||||||
|
64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try moving an item to a set of slots in this menu.
|
||||||
|
///
|
||||||
|
/// Returns the updated item slot.
|
||||||
|
fn try_move_item_to_slots(
|
||||||
|
&mut self,
|
||||||
|
item_slot_index: usize,
|
||||||
|
target_slot_indexes: RangeInclusive<usize>,
|
||||||
|
) -> bool {
|
||||||
|
let mut item_slot = self.slot(item_slot_index).unwrap().clone();
|
||||||
|
|
||||||
|
// first see if we can stack it with another item
|
||||||
|
if item_slot.kind().stackable() {
|
||||||
|
for target_slot_index in target_slot_indexes.clone() {
|
||||||
|
self.move_item_to_slot_if_stackable(&mut item_slot, target_slot_index);
|
||||||
|
if item_slot.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and if not then just try putting it in an empty slot
|
||||||
|
if item_slot.is_present() {
|
||||||
|
for target_slot_index in target_slot_indexes {
|
||||||
|
self.move_item_to_slot_if_empty(&mut item_slot, target_slot_index);
|
||||||
|
if item_slot.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item_slot.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge this item slot into the target item slot, only if the target item
|
||||||
|
/// slot is present and the same item.
|
||||||
|
fn move_item_to_slot_if_stackable(
|
||||||
|
&mut self,
|
||||||
|
item_slot: &mut ItemSlot,
|
||||||
|
target_slot_index: usize,
|
||||||
|
) {
|
||||||
|
let ItemSlot::Present(item) = item_slot else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let target_slot = self.slot(target_slot_index).unwrap();
|
||||||
|
if let ItemSlot::Present(target_item) = target_slot {
|
||||||
|
// the target slot is empty, so we can just move the item there
|
||||||
|
if self.may_place(target_slot_index, item) && target_item.is_same_item_and_nbt(item) {
|
||||||
|
let slot_item_limit = self.max_stack_size(target_slot_index);
|
||||||
|
let new_target_slot_data = item.split(u8::min(slot_item_limit, item.count as u8));
|
||||||
|
|
||||||
|
// get the target slot again but mut this time so we can update it
|
||||||
|
let target_slot = self.slot_mut(target_slot_index).unwrap();
|
||||||
|
*target_slot = ItemSlot::Present(new_target_slot_data);
|
||||||
|
|
||||||
|
item_slot.update_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_item_to_slot_if_empty(&mut self, item_slot: &mut ItemSlot, target_slot_index: usize) {
|
||||||
|
let ItemSlot::Present(item) = item_slot else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let target_slot = self.slot(target_slot_index).unwrap();
|
||||||
|
if target_slot.is_empty() && self.may_place(target_slot_index, item) {
|
||||||
|
let slot_item_limit = self.max_stack_size(target_slot_index);
|
||||||
|
let new_target_slot_data = item.split(u8::min(slot_item_limit, item.count as u8));
|
||||||
|
|
||||||
|
let target_slot = self.slot_mut(target_slot_index).unwrap();
|
||||||
|
*target_slot = ItemSlot::Present(new_target_slot_data);
|
||||||
|
item_slot.update_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
azalea-inventory/src/slot.rs
Normal file
146
azalea-inventory/src/slot.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
|
||||||
|
use azalea_nbt::Nbt;
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
|
/// Either an item in an inventory or nothing.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub enum ItemSlot {
|
||||||
|
#[default]
|
||||||
|
Empty,
|
||||||
|
Present(ItemSlotData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemSlot {
|
||||||
|
/// Check if the slot is ItemSlot::Empty, if the count is <= 0, or if the
|
||||||
|
/// item is air.
|
||||||
|
///
|
||||||
|
/// This is the opposite of [`ItemSlot::is_present`].
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ItemSlot::Empty => true,
|
||||||
|
ItemSlot::Present(item) => item.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Check if the slot is not ItemSlot::Empty, if the count is > 0, and if
|
||||||
|
/// the item is not air.
|
||||||
|
///
|
||||||
|
/// This is the opposite of [`ItemSlot::is_empty`].
|
||||||
|
pub fn is_present(&self) -> bool {
|
||||||
|
!self.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the amount of the item in the slot, or 0 if the slot is empty.
|
||||||
|
///
|
||||||
|
/// Note that it's possible for the count to be zero or negative when the
|
||||||
|
/// slot is present.
|
||||||
|
pub fn count(&self) -> i8 {
|
||||||
|
match self {
|
||||||
|
ItemSlot::Empty => 0,
|
||||||
|
ItemSlot::Present(i) => i.count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove `count` items from this slot, returning the removed items.
|
||||||
|
pub fn split(&mut self, count: u8) -> ItemSlot {
|
||||||
|
match self {
|
||||||
|
ItemSlot::Empty => ItemSlot::Empty,
|
||||||
|
ItemSlot::Present(i) => {
|
||||||
|
let returning = i.split(count);
|
||||||
|
if i.is_empty() {
|
||||||
|
*self = ItemSlot::Empty;
|
||||||
|
}
|
||||||
|
ItemSlot::Present(returning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the `kind` of the item in this slot, or
|
||||||
|
/// [`azalea_registry::Item::Air`]
|
||||||
|
pub fn kind(&self) -> azalea_registry::Item {
|
||||||
|
match self {
|
||||||
|
ItemSlot::Empty => azalea_registry::Item::Air,
|
||||||
|
ItemSlot::Present(i) => i.kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update whether this slot is empty, based on the count.
|
||||||
|
pub fn update_empty(&mut self) {
|
||||||
|
if let ItemSlot::Present(i) = self {
|
||||||
|
if i.is_empty() {
|
||||||
|
*self = ItemSlot::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An item in an inventory, with a count and NBT. Usually you want [`ItemSlot`]
|
||||||
|
/// or [`azalea_registry::Item`] instead.
|
||||||
|
#[derive(Debug, Clone, McBuf, PartialEq)]
|
||||||
|
pub struct ItemSlotData {
|
||||||
|
pub kind: azalea_registry::Item,
|
||||||
|
/// The amount of the item in this slot.
|
||||||
|
///
|
||||||
|
/// The count can be zero or negative, but this is rare.
|
||||||
|
pub count: i8,
|
||||||
|
pub nbt: Nbt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemSlotData {
|
||||||
|
/// Remove `count` items from this slot, returning the removed items.
|
||||||
|
pub fn split(&mut self, count: u8) -> ItemSlotData {
|
||||||
|
let returning_count = i8::min(count as i8, self.count);
|
||||||
|
let mut returning = self.clone();
|
||||||
|
returning.count = returning_count;
|
||||||
|
self.count -= returning_count;
|
||||||
|
returning
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the count of the item is <= 0 or if the item is air.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.count <= 0 || self.kind == azalea_registry::Item::Air
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this item is the same as another item, ignoring the count.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use azalea_inventory::ItemSlotData;
|
||||||
|
/// # use azalea_registry::Item;
|
||||||
|
/// let mut a = ItemSlotData {
|
||||||
|
/// kind: Item::Stone,
|
||||||
|
/// count: 1,
|
||||||
|
/// nbt: Default::default(),
|
||||||
|
/// };
|
||||||
|
/// let mut b = ItemSlotData {
|
||||||
|
/// kind: Item::Stone,
|
||||||
|
/// count: 2,
|
||||||
|
/// nbt: Default::default(),
|
||||||
|
/// };
|
||||||
|
/// assert!(a.is_same_item_and_nbt(&b));
|
||||||
|
///
|
||||||
|
/// b.kind = Item::Dirt;
|
||||||
|
/// assert!(!a.is_same_item_and_nbt(&b));
|
||||||
|
/// ```
|
||||||
|
pub fn is_same_item_and_nbt(&self, other: &ItemSlotData) -> bool {
|
||||||
|
self.kind == other.kind && self.nbt == other.nbt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl McBufReadable for ItemSlot {
|
||||||
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
|
let slot = Option::<ItemSlotData>::read_from(buf)?;
|
||||||
|
Ok(slot.map_or(ItemSlot::Empty, ItemSlot::Present))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl McBufWritable for ItemSlot {
|
||||||
|
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
|
match self {
|
||||||
|
ItemSlot::Empty => false.write_into(buf)?,
|
||||||
|
ItemSlot::Present(i) => {
|
||||||
|
true.write_into(buf)?;
|
||||||
|
i.write_into(buf)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,21 +22,21 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
|
||||||
let mut group = c.benchmark_group(filename);
|
let mut group = c.benchmark_group(filename);
|
||||||
group.throughput(Throughput::Bytes(input.len() as u64));
|
group.throughput(Throughput::Bytes(input.len() as u64));
|
||||||
|
|
||||||
// group.bench_function("azalea_parse", |b| {
|
group.bench_function("azalea_parse", |b| {
|
||||||
// b.iter(|| {
|
b.iter(|| {
|
||||||
// let input = black_box(input);
|
let input = black_box(input);
|
||||||
// let nbt = azalea_nbt::Nbt::read(&mut Cursor::new(&input)).unwrap();
|
let nbt = azalea_nbt::Nbt::read(&mut Cursor::new(&input)).unwrap();
|
||||||
// black_box(nbt);
|
black_box(nbt);
|
||||||
// })
|
})
|
||||||
// });
|
});
|
||||||
|
|
||||||
// group.bench_function("graphite_parse", |b| {
|
group.bench_function("graphite_parse", |b| {
|
||||||
// b.iter(|| {
|
b.iter(|| {
|
||||||
// let input = black_box(input);
|
let input = black_box(input);
|
||||||
// let nbt = graphite_binary::nbt::decode::read(&mut
|
let nbt = graphite_binary::nbt::decode::read(&mut &input[..]).unwrap();
|
||||||
// &input[..]).unwrap(); black_box(nbt);
|
black_box(nbt);
|
||||||
// })
|
})
|
||||||
// });
|
});
|
||||||
|
|
||||||
// group.bench_function("valence_parse", |b| {
|
// group.bench_function("valence_parse", |b| {
|
||||||
// b.iter(|| {
|
// b.iter(|| {
|
||||||
|
|
|
@ -11,6 +11,7 @@ version = "0.6.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
azalea-block = { path = "../azalea-block", version = "^0.6.0" }
|
azalea-block = { path = "../azalea-block", version = "^0.6.0" }
|
||||||
azalea-core = { path = "../azalea-core", version = "^0.6.0" }
|
azalea-core = { path = "../azalea-core", version = "^0.6.0" }
|
||||||
|
azalea-inventory = { version = "0.1.0", path = "../azalea-inventory" }
|
||||||
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
|
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
|
||||||
azalea-world = { path = "../azalea-world", version = "^0.6.0" }
|
azalea-world = { path = "../azalea-world", version = "^0.6.0" }
|
||||||
bevy_app = "0.10.0"
|
bevy_app = "0.10.0"
|
||||||
|
|
232
azalea-physics/src/clip.rs
Normal file
232
azalea-physics/src/clip.rs
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
use azalea_block::BlockState;
|
||||||
|
use azalea_core::{lerp, BlockHitResult, BlockPos, Direction, Vec3, EPSILON};
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
|
use azalea_world::ChunkStorage;
|
||||||
|
use bevy_ecs::entity::Entity;
|
||||||
|
|
||||||
|
use crate::collision::{BlockWithShape, VoxelShape};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ClipContext {
|
||||||
|
pub from: Vec3,
|
||||||
|
pub to: Vec3,
|
||||||
|
pub block_shape_type: BlockShapeType,
|
||||||
|
pub fluid_pick_type: FluidPickType,
|
||||||
|
// pub collision_context: EntityCollisionContext,
|
||||||
|
}
|
||||||
|
impl ClipContext {
|
||||||
|
// minecraft passes in the world and blockpos here... but it doesn't actually
|
||||||
|
// seem necessary?
|
||||||
|
pub fn block_shape(&self, block_state: BlockState) -> &VoxelShape {
|
||||||
|
// TODO: implement the other shape getters
|
||||||
|
// (see the ClipContext.Block class in the vanilla source)
|
||||||
|
match self.block_shape_type {
|
||||||
|
BlockShapeType::Collider => block_state.shape(),
|
||||||
|
BlockShapeType::Outline => block_state.shape(),
|
||||||
|
BlockShapeType::Visual => block_state.shape(),
|
||||||
|
BlockShapeType::FallDamageResetting => block_state.shape(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum BlockShapeType {
|
||||||
|
Collider,
|
||||||
|
Outline,
|
||||||
|
Visual,
|
||||||
|
FallDamageResetting,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum FluidPickType {
|
||||||
|
None,
|
||||||
|
SourceOnly,
|
||||||
|
Any,
|
||||||
|
Water,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EntityCollisionContext {
|
||||||
|
pub descending: bool,
|
||||||
|
pub entity_bottom: f64,
|
||||||
|
pub held_item: ItemSlot,
|
||||||
|
// pub can_stand_on_fluid: Box<dyn Fn(&FluidState) -> bool>,
|
||||||
|
pub entity: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResult {
|
||||||
|
traverse_blocks(
|
||||||
|
context.from,
|
||||||
|
context.to,
|
||||||
|
context,
|
||||||
|
|context, block_pos| {
|
||||||
|
let block_state = chunk_storage.get_block_state(block_pos).unwrap_or_default();
|
||||||
|
// TODO: add fluid stuff to this (see getFluidState in vanilla source)
|
||||||
|
let block_shape = context.block_shape(block_state);
|
||||||
|
clip_with_interaction_override(
|
||||||
|
&context.from,
|
||||||
|
&context.to,
|
||||||
|
block_pos,
|
||||||
|
block_shape,
|
||||||
|
&block_state,
|
||||||
|
)
|
||||||
|
// let block_distance = if let Some(block_hit_result) =
|
||||||
|
// block_hit_result { context.from.distance_to_sqr(&
|
||||||
|
// block_hit_result.location) } else {
|
||||||
|
// f64::MAX
|
||||||
|
// };
|
||||||
|
},
|
||||||
|
|context| {
|
||||||
|
let vec = context.from - context.to;
|
||||||
|
BlockHitResult::miss(
|
||||||
|
context.to,
|
||||||
|
Direction::nearest(vec),
|
||||||
|
BlockPos::from(context.to),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// default BlockHitResult clipWithInteractionOverride(Vec3 world, Vec3 from,
|
||||||
|
// BlockPos to, VoxelShape shape, BlockState block) {
|
||||||
|
// BlockHitResult blockHitResult = shape.clip(world, from, to);
|
||||||
|
// if (blockHitResult != null) {
|
||||||
|
// BlockHitResult var7 = block.getInteractionShape(this, to).clip(world,
|
||||||
|
// from, to); if (var7 != null
|
||||||
|
// && var7.getLocation().subtract(world).lengthSqr() <
|
||||||
|
// blockHitResult.getLocation().subtract(world).lengthSqr()) { return
|
||||||
|
// blockHitResult.withDirection(var7.getDirection()); }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return blockHitResult;
|
||||||
|
// }
|
||||||
|
fn clip_with_interaction_override(
|
||||||
|
from: &Vec3,
|
||||||
|
to: &Vec3,
|
||||||
|
block_pos: &BlockPos,
|
||||||
|
block_shape: &VoxelShape,
|
||||||
|
block_state: &BlockState,
|
||||||
|
) -> Option<BlockHitResult> {
|
||||||
|
let block_hit_result = block_shape.clip(from, to, block_pos);
|
||||||
|
if let Some(block_hit_result) = block_hit_result {
|
||||||
|
// TODO: minecraft calls .getInteractionShape here
|
||||||
|
// are there even any blocks that have a physics shape different from the
|
||||||
|
// interaction shape???
|
||||||
|
// (if not then you can delete this comment)
|
||||||
|
// (if there are then you have to implement BlockState::interaction_shape, lol
|
||||||
|
// have fun)
|
||||||
|
let interaction_shape = block_state.shape();
|
||||||
|
let interaction_hit_result = interaction_shape.clip(from, to, block_pos);
|
||||||
|
if let Some(interaction_hit_result) = interaction_hit_result {
|
||||||
|
if interaction_hit_result.location.distance_to_sqr(from)
|
||||||
|
< block_hit_result.location.distance_to_sqr(from)
|
||||||
|
{
|
||||||
|
return Some(block_hit_result.with_direction(interaction_hit_result.direction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(block_hit_result)
|
||||||
|
} else {
|
||||||
|
block_hit_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traverse_blocks<C, T>(
|
||||||
|
from: Vec3,
|
||||||
|
to: Vec3,
|
||||||
|
context: C,
|
||||||
|
get_hit_result: impl Fn(&C, &BlockPos) -> Option<T>,
|
||||||
|
get_miss_result: impl Fn(&C) -> T,
|
||||||
|
) -> T {
|
||||||
|
if from == to {
|
||||||
|
return get_miss_result(&context);
|
||||||
|
}
|
||||||
|
|
||||||
|
let right_after_end = Vec3 {
|
||||||
|
x: lerp(-EPSILON, to.x, from.x),
|
||||||
|
y: lerp(-EPSILON, to.y, from.y),
|
||||||
|
z: lerp(-EPSILON, to.z, from.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_before_start = Vec3 {
|
||||||
|
x: lerp(-EPSILON, from.x, to.x),
|
||||||
|
y: lerp(-EPSILON, from.y, to.y),
|
||||||
|
z: lerp(-EPSILON, from.z, to.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_block = BlockPos::from(right_before_start);
|
||||||
|
if let Some(data) = get_hit_result(&context, ¤t_block) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
let vec = right_after_end - right_before_start;
|
||||||
|
|
||||||
|
/// Returns either -1, 0, or 1, depending on whether the number is negative,
|
||||||
|
/// zero, or positive.
|
||||||
|
///
|
||||||
|
/// This function exists because f64::signum doesn't check for 0.
|
||||||
|
fn get_number_sign(num: f64) -> f64 {
|
||||||
|
if num == 0. {
|
||||||
|
0.
|
||||||
|
} else {
|
||||||
|
num.signum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vec_sign = Vec3 {
|
||||||
|
x: get_number_sign(vec.x),
|
||||||
|
y: get_number_sign(vec.y),
|
||||||
|
z: get_number_sign(vec.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let percentage_step = Vec3 {
|
||||||
|
x: if vec_sign.x == 0. { f64::MAX } else { vec_sign.x / vec.x },
|
||||||
|
y: if vec_sign.y == 0. { f64::MAX } else { vec_sign.y / vec.y },
|
||||||
|
z: if vec_sign.z == 0. { f64::MAX } else { vec_sign.z / vec.z },
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut percentage = Vec3 {
|
||||||
|
x: percentage_step.x
|
||||||
|
* if vec_sign.x > 0. {
|
||||||
|
1. - right_before_start.x.fract()
|
||||||
|
} else {
|
||||||
|
right_before_start.x.fract().abs()
|
||||||
|
},
|
||||||
|
y: percentage_step.y
|
||||||
|
* if vec_sign.y > 0. {
|
||||||
|
1. - right_before_start.y.fract()
|
||||||
|
} else {
|
||||||
|
right_before_start.y.fract().abs()
|
||||||
|
},
|
||||||
|
z: percentage_step.z
|
||||||
|
* if vec_sign.z > 0. {
|
||||||
|
1. - right_before_start.z.fract()
|
||||||
|
} else {
|
||||||
|
right_before_start.z.fract().abs()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if percentage.x > 1. && percentage.y > 1. && percentage.z > 1. {
|
||||||
|
return get_miss_result(&context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if percentage.x < percentage.y {
|
||||||
|
if percentage.x < percentage.z {
|
||||||
|
current_block.x += vec_sign.x as i32;
|
||||||
|
percentage.x += percentage_step.x;
|
||||||
|
} else {
|
||||||
|
current_block.z += vec_sign.z as i32;
|
||||||
|
percentage.z += percentage_step.z;
|
||||||
|
}
|
||||||
|
} else if percentage.y < percentage.z {
|
||||||
|
current_block.y += vec_sign.y as i32;
|
||||||
|
percentage.y += percentage_step.y;
|
||||||
|
} else {
|
||||||
|
current_block.z += vec_sign.z as i32;
|
||||||
|
percentage.z += percentage_step.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data) = get_hit_result(&context, ¤t_block) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ impl DiscreteVoxelShape {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let (x, y, z) = (x as u32, y as u32, z as u32);
|
let (x, y, z) = (x as u32, y as u32, z as u32);
|
||||||
|
|
||||||
(x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z))
|
(x < self.size(Axis::X) && y < self.size(Axis::Y) && z < self.size(Axis::Z))
|
||||||
&& (self.is_full(x, y, z))
|
&& (self.is_full(x, y, z))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,7 @@ mod shape;
|
||||||
mod world_collisions;
|
mod world_collisions;
|
||||||
|
|
||||||
use azalea_core::{Axis, Vec3, AABB, EPSILON};
|
use azalea_core::{Axis, Vec3, AABB, EPSILON};
|
||||||
use azalea_world::{
|
use azalea_world::{entity, Instance, MoveEntityError};
|
||||||
entity::{self},
|
|
||||||
Instance, MoveEntityError,
|
|
||||||
};
|
|
||||||
pub use blocks::BlockWithShape;
|
pub use blocks::BlockWithShape;
|
||||||
pub use discrete_voxel_shape::*;
|
pub use discrete_voxel_shape::*;
|
||||||
pub use shape::*;
|
pub use shape::*;
|
||||||
|
@ -219,7 +216,11 @@ fn collide_with_shapes(
|
||||||
if y_movement != 0. {
|
if y_movement != 0. {
|
||||||
y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
|
y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
|
||||||
if y_movement != 0. {
|
if y_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(0., y_movement, 0.);
|
entity_box = entity_box.move_relative(&Vec3 {
|
||||||
|
x: 0.,
|
||||||
|
y: y_movement,
|
||||||
|
z: 0.,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,14 +231,22 @@ fn collide_with_shapes(
|
||||||
if more_z_movement && z_movement != 0. {
|
if more_z_movement && z_movement != 0. {
|
||||||
z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
|
z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
|
||||||
if z_movement != 0. {
|
if z_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(0., 0., z_movement);
|
entity_box = entity_box.move_relative(&Vec3 {
|
||||||
|
x: 0.,
|
||||||
|
y: 0.,
|
||||||
|
z: z_movement,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if x_movement != 0. {
|
if x_movement != 0. {
|
||||||
x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
|
x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
|
||||||
if x_movement != 0. {
|
if x_movement != 0. {
|
||||||
entity_box = entity_box.move_relative(x_movement, 0., 0.);
|
entity_box = entity_box.move_relative(&Vec3 {
|
||||||
|
x: x_movement,
|
||||||
|
y: 0.,
|
||||||
|
z: 0.,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use super::mergers::IndexMerger;
|
use super::mergers::IndexMerger;
|
||||||
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
||||||
use azalea_core::{binary_search, Axis, AxisCycle, EPSILON};
|
use azalea_core::{
|
||||||
|
binary_search, Axis, AxisCycle, BlockHitResult, BlockPos, Direction, Vec3, EPSILON,
|
||||||
|
};
|
||||||
use std::{cmp, num::NonZeroU32};
|
use std::{cmp, num::NonZeroU32};
|
||||||
|
|
||||||
pub struct Shapes {}
|
pub struct Shapes;
|
||||||
|
|
||||||
pub fn block_shape() -> VoxelShape {
|
pub fn block_shape() -> VoxelShape {
|
||||||
let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1);
|
let mut shape = BitSetDiscreteVoxelShape::new(1, 1, 1);
|
||||||
|
@ -390,6 +392,33 @@ impl VoxelShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clip(&self, from: &Vec3, to: &Vec3, block_pos: &BlockPos) -> Option<BlockHitResult> {
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let vector = to - from;
|
||||||
|
if vector.length_sqr() < EPSILON {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let right_after_start = from + &(vector * 0.0001);
|
||||||
|
|
||||||
|
if self.shape().is_full_wide(
|
||||||
|
self.find_index(Axis::X, right_after_start.x - block_pos.x as f64),
|
||||||
|
self.find_index(Axis::Y, right_after_start.y - block_pos.y as f64),
|
||||||
|
self.find_index(Axis::Z, right_after_start.z - block_pos.z as f64),
|
||||||
|
) {
|
||||||
|
Some(BlockHitResult {
|
||||||
|
block_pos: *block_pos,
|
||||||
|
direction: Direction::nearest(vector).opposite(),
|
||||||
|
location: right_after_start,
|
||||||
|
inside: true,
|
||||||
|
miss: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
AABB::clip_iterable(&self.to_aabbs(), from, to, block_pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 {
|
pub fn collide(&self, axis: &Axis, entity_box: &AABB, movement: f64) -> f64 {
|
||||||
self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement)
|
self.collide_x(AxisCycle::between(*axis, Axis::X), entity_box, movement)
|
||||||
}
|
}
|
||||||
|
@ -531,19 +560,34 @@ impl VoxelShape {
|
||||||
let y_coords = self.get_coords(Axis::Y);
|
let y_coords = self.get_coords(Axis::Y);
|
||||||
let z_coords = self.get_coords(Axis::Z);
|
let z_coords = self.get_coords(Axis::Z);
|
||||||
self.shape().for_all_boxes(
|
self.shape().for_all_boxes(
|
||||||
|var4x, var5, var6, var7, var8, var9| {
|
|min_x, min_y, min_z, max_x, max_y, max_z| {
|
||||||
consumer(
|
consumer(
|
||||||
x_coords[var4x as usize],
|
x_coords[min_x as usize],
|
||||||
y_coords[var5 as usize],
|
y_coords[min_y as usize],
|
||||||
z_coords[var6 as usize],
|
z_coords[min_z as usize],
|
||||||
x_coords[var7 as usize],
|
x_coords[max_x as usize],
|
||||||
y_coords[var8 as usize],
|
y_coords[max_y as usize],
|
||||||
z_coords[var9 as usize],
|
z_coords[max_z as usize],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_aabbs(&self) -> Vec<AABB> {
|
||||||
|
let mut aabbs = Vec::new();
|
||||||
|
self.for_all_boxes(|min_x, min_y, min_z, max_x, max_y, max_z| {
|
||||||
|
aabbs.push(AABB {
|
||||||
|
min_x,
|
||||||
|
min_y,
|
||||||
|
min_z,
|
||||||
|
max_x,
|
||||||
|
max_y,
|
||||||
|
max_z,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
aabbs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AABB> for VoxelShape {
|
impl From<AABB> for VoxelShape {
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![feature(trait_alias)]
|
#![feature(trait_alias)]
|
||||||
|
|
||||||
|
pub mod clip;
|
||||||
pub mod collision;
|
pub mod collision;
|
||||||
|
|
||||||
use azalea_block::{Block, BlockState};
|
use azalea_block::{Block, BlockState};
|
||||||
use azalea_core::{BlockPos, Vec3};
|
use azalea_core::{BlockPos, Vec3};
|
||||||
use azalea_world::{
|
use azalea_world::{
|
||||||
entity::{
|
entity::{
|
||||||
metadata::Sprinting, move_relative, Attributes, Jumping, Local, Physics, Position,
|
clamp_look_direction, metadata::Sprinting, move_relative, Attributes, Jumping, Local,
|
||||||
WorldName,
|
LookDirection, Physics, Position, WorldName,
|
||||||
},
|
},
|
||||||
Instance, InstanceContainer,
|
Instance, InstanceContainer,
|
||||||
};
|
};
|
||||||
|
@ -30,7 +31,11 @@ pub struct PhysicsPlugin;
|
||||||
impl Plugin for PhysicsPlugin {
|
impl Plugin for PhysicsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<ForceJumpEvent>()
|
app.add_event::<ForceJumpEvent>()
|
||||||
.add_system(force_jump_listener.before(azalea_world::entity::update_bounding_box))
|
.add_system(
|
||||||
|
force_jump_listener
|
||||||
|
.before(azalea_world::entity::update_bounding_box)
|
||||||
|
.after(clamp_look_direction),
|
||||||
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
(ai_step, travel)
|
(ai_step, travel)
|
||||||
.chain()
|
.chain()
|
||||||
|
@ -43,11 +48,20 @@ impl Plugin for PhysicsPlugin {
|
||||||
/// Move the entity with the given acceleration while handling friction,
|
/// Move the entity with the given acceleration while handling friction,
|
||||||
/// gravity, collisions, and some other stuff.
|
/// gravity, collisions, and some other stuff.
|
||||||
fn travel(
|
fn travel(
|
||||||
mut query: Query<(&mut Physics, &mut Position, &Attributes, &WorldName), With<Local>>,
|
mut query: Query<
|
||||||
world_container: Res<InstanceContainer>,
|
(
|
||||||
|
&mut Physics,
|
||||||
|
&mut LookDirection,
|
||||||
|
&mut Position,
|
||||||
|
&Attributes,
|
||||||
|
&WorldName,
|
||||||
|
),
|
||||||
|
With<Local>,
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (mut physics, mut position, attributes, world_name) in &mut query {
|
for (mut physics, direction, mut position, attributes, world_name) in &mut query {
|
||||||
let world_lock = world_container
|
let world_lock = instance_container
|
||||||
.get(world_name)
|
.get(world_name)
|
||||||
.expect("All entities should be in a valid world");
|
.expect("All entities should be in a valid world");
|
||||||
let world = world_lock.read();
|
let world = world_lock.read();
|
||||||
|
@ -85,6 +99,7 @@ fn travel(
|
||||||
block_friction,
|
block_friction,
|
||||||
&world,
|
&world,
|
||||||
&mut physics,
|
&mut physics,
|
||||||
|
&direction,
|
||||||
&mut position,
|
&mut position,
|
||||||
attributes,
|
attributes,
|
||||||
);
|
);
|
||||||
|
@ -158,13 +173,21 @@ pub fn ai_step(
|
||||||
pub struct ForceJumpEvent(pub Entity);
|
pub struct ForceJumpEvent(pub Entity);
|
||||||
|
|
||||||
pub fn force_jump_listener(
|
pub fn force_jump_listener(
|
||||||
mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>,
|
mut query: Query<(
|
||||||
world_container: Res<InstanceContainer>,
|
&mut Physics,
|
||||||
|
&Position,
|
||||||
|
&LookDirection,
|
||||||
|
&Sprinting,
|
||||||
|
&WorldName,
|
||||||
|
)>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
mut events: EventReader<ForceJumpEvent>,
|
mut events: EventReader<ForceJumpEvent>,
|
||||||
) {
|
) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
if let Ok((mut physics, position, sprinting, world_name)) = query.get_mut(event.0) {
|
if let Ok((mut physics, position, direction, sprinting, world_name)) =
|
||||||
let world_lock = world_container
|
query.get_mut(event.0)
|
||||||
|
{
|
||||||
|
let world_lock = instance_container
|
||||||
.get(world_name)
|
.get(world_name)
|
||||||
.expect("All entities should be in a valid world");
|
.expect("All entities should be in a valid world");
|
||||||
let world = world_lock.read();
|
let world = world_lock.read();
|
||||||
|
@ -178,7 +201,7 @@ pub fn force_jump_listener(
|
||||||
};
|
};
|
||||||
if **sprinting {
|
if **sprinting {
|
||||||
// sprint jumping gives some extra velocity
|
// sprint jumping gives some extra velocity
|
||||||
let y_rot = physics.y_rot * 0.017453292;
|
let y_rot = direction.y_rot * 0.017453292;
|
||||||
physics.delta += Vec3 {
|
physics.delta += Vec3 {
|
||||||
x: (-f32::sin(y_rot) * 0.2) as f64,
|
x: (-f32::sin(y_rot) * 0.2) as f64,
|
||||||
y: 0.,
|
y: 0.,
|
||||||
|
@ -204,11 +227,13 @@ fn handle_relative_friction_and_calculate_movement(
|
||||||
block_friction: f32,
|
block_friction: f32,
|
||||||
world: &Instance,
|
world: &Instance,
|
||||||
physics: &mut Physics,
|
physics: &mut Physics,
|
||||||
|
direction: &LookDirection,
|
||||||
position: &mut Position,
|
position: &mut Position,
|
||||||
attributes: &Attributes,
|
attributes: &Attributes,
|
||||||
) -> Vec3 {
|
) -> Vec3 {
|
||||||
move_relative(
|
move_relative(
|
||||||
physics,
|
physics,
|
||||||
|
direction,
|
||||||
get_friction_influenced_speed(physics, attributes, block_friction),
|
get_friction_influenced_speed(physics, attributes, block_friction),
|
||||||
&Vec3 {
|
&Vec3 {
|
||||||
x: physics.xxa as f64,
|
x: physics.xxa as f64,
|
||||||
|
|
|
@ -25,6 +25,7 @@ azalea-core = { path = "../azalea-core", optional = true, version = "^0.6.0", fe
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
azalea-crypto = { path = "../azalea-crypto", version = "^0.6.0" }
|
azalea-crypto = { path = "../azalea-crypto", version = "^0.6.0" }
|
||||||
|
azalea-inventory = { version = "0.1.0", path = "../azalea-inventory" }
|
||||||
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0", features = [
|
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
pub struct ClientboundContainerSetContentPacket {
|
pub struct ClientboundContainerSetContentPacket {
|
||||||
pub container_id: u8,
|
pub container_id: i8,
|
||||||
#[var]
|
#[var]
|
||||||
pub state_id: u32,
|
pub state_id: u32,
|
||||||
pub items: Vec<Slot>,
|
pub items: Vec<ItemSlot>,
|
||||||
pub carried_item: Slot,
|
pub carried_item: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
pub struct ClientboundContainerSetDataPacket {
|
pub struct ClientboundContainerSetDataPacket {
|
||||||
pub container_id: u8,
|
pub container_id: i8,
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
pub value: u16,
|
pub value: u16,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
pub struct ClientboundContainerSetSlotPacket {
|
pub struct ClientboundContainerSetSlotPacket {
|
||||||
pub container_id: u8,
|
pub container_id: i8,
|
||||||
#[var]
|
#[var]
|
||||||
pub state_id: u32,
|
pub state_id: u32,
|
||||||
pub slot: u16,
|
pub slot: u16,
|
||||||
pub item_stack: Slot,
|
pub item_stack: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use self::registry::RegistryHolder;
|
use self::registry::RegistryHolder;
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::{GameType, GlobalPos, OptionalGameType, ResourceLocation};
|
use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation};
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
/// The first packet sent by the server to the client after login.
|
/// The first packet sent by the server to the client after login.
|
||||||
|
@ -11,7 +11,7 @@ use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
pub struct ClientboundLoginPacket {
|
pub struct ClientboundLoginPacket {
|
||||||
pub player_id: u32,
|
pub player_id: u32,
|
||||||
pub hardcore: bool,
|
pub hardcore: bool,
|
||||||
pub game_type: GameType,
|
pub game_type: GameMode,
|
||||||
pub previous_game_type: OptionalGameType,
|
pub previous_game_type: OptionalGameType,
|
||||||
pub levels: Vec<ResourceLocation>,
|
pub levels: Vec<ResourceLocation>,
|
||||||
pub registry_holder: RegistryHolder,
|
pub registry_holder: RegistryHolder,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
|
@ -17,9 +17,9 @@ pub struct ClientboundMerchantOffersPacket {
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
pub struct MerchantOffer {
|
pub struct MerchantOffer {
|
||||||
pub base_cost_a: Slot,
|
pub base_cost_a: ItemSlot,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
pub cost_b: Slot,
|
pub cost_b: ItemSlot,
|
||||||
pub out_of_stock: bool,
|
pub out_of_stock: bool,
|
||||||
pub uses: u32,
|
pub uses: u32,
|
||||||
pub max_uses: u32,
|
pub max_uses: u32,
|
||||||
|
|
|
@ -6,6 +6,6 @@ use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
pub struct ClientboundOpenScreenPacket {
|
pub struct ClientboundOpenScreenPacket {
|
||||||
#[var]
|
#[var]
|
||||||
pub container_id: u32,
|
pub container_id: u32,
|
||||||
pub menu_type: azalea_registry::Menu,
|
pub menu_type: azalea_registry::MenuKind,
|
||||||
pub title: FormattedText,
|
pub title: FormattedText,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use azalea_buf::{
|
||||||
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
||||||
};
|
};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{FixedBitSet, GameType};
|
use azalea_core::{FixedBitSet, GameMode};
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -24,7 +24,7 @@ pub struct PlayerInfoEntry {
|
||||||
pub profile: GameProfile,
|
pub profile: GameProfile,
|
||||||
pub listed: bool,
|
pub listed: bool,
|
||||||
pub latency: i32,
|
pub latency: i32,
|
||||||
pub game_mode: GameType,
|
pub game_mode: GameMode,
|
||||||
pub display_name: Option<FormattedText>,
|
pub display_name: Option<FormattedText>,
|
||||||
pub chat_session: Option<RemoteChatSessionData>,
|
pub chat_session: Option<RemoteChatSessionData>,
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ pub struct InitializeChatAction {
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
pub struct UpdateGameModeAction {
|
pub struct UpdateGameModeAction {
|
||||||
pub game_mode: GameType,
|
pub game_mode: GameMode,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
pub struct UpdateListedAction {
|
pub struct UpdateListedAction {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::{GameType, GlobalPos, OptionalGameType, ResourceLocation};
|
use azalea_core::{GameMode, GlobalPos, OptionalGameType, ResourceLocation};
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
|
@ -7,7 +7,7 @@ pub struct ClientboundRespawnPacket {
|
||||||
pub dimension_type: ResourceLocation,
|
pub dimension_type: ResourceLocation,
|
||||||
pub dimension: ResourceLocation,
|
pub dimension: ResourceLocation,
|
||||||
pub seed: u64,
|
pub seed: u64,
|
||||||
pub player_game_type: GameType,
|
pub player_game_type: GameMode,
|
||||||
pub previous_player_game_type: OptionalGameType,
|
pub previous_player_game_type: OptionalGameType,
|
||||||
pub is_debug: bool,
|
pub is_debug: bool,
|
||||||
pub is_flat: bool,
|
pub is_flat: bool,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use azalea_buf::{BufReadError, McBuf};
|
use azalea_buf::{BufReadError, McBuf};
|
||||||
use azalea_buf::{McBufReadable, McBufWritable};
|
use azalea_buf::{McBufReadable, McBufWritable};
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ pub struct ClientboundSetEquipmentPacket {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EquipmentSlots {
|
pub struct EquipmentSlots {
|
||||||
pub slots: Vec<(EquipmentSlot, Slot)>,
|
pub slots: Vec<(EquipmentSlot, ItemSlot)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufReadable for EquipmentSlots {
|
impl McBufReadable for EquipmentSlots {
|
||||||
|
@ -28,7 +28,7 @@ impl McBufReadable for EquipmentSlots {
|
||||||
id: equipment_byte.into(),
|
id: equipment_byte.into(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let item = Slot::read_from(buf)?;
|
let item = ItemSlot::read_from(buf)?;
|
||||||
slots.push((equipment_slot, item));
|
slots.push((equipment_slot, item));
|
||||||
if equipment_byte & 128 == 0 {
|
if equipment_byte & 128 == 0 {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{ResourceLocation, Slot};
|
use azalea_core::ResourceLocation;
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
@ -25,7 +26,7 @@ pub struct Advancement {
|
||||||
pub struct DisplayInfo {
|
pub struct DisplayInfo {
|
||||||
pub title: FormattedText,
|
pub title: FormattedText,
|
||||||
pub description: FormattedText,
|
pub description: FormattedText,
|
||||||
pub icon: Slot,
|
pub icon: ItemSlot,
|
||||||
pub frame: FrameType,
|
pub frame: FrameType,
|
||||||
pub show_toast: bool,
|
pub show_toast: bool,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
|
@ -130,7 +131,7 @@ mod tests {
|
||||||
display: Some(DisplayInfo {
|
display: Some(DisplayInfo {
|
||||||
title: FormattedText::from("title".to_string()),
|
title: FormattedText::from("title".to_string()),
|
||||||
description: FormattedText::from("description".to_string()),
|
description: FormattedText::from("description".to_string()),
|
||||||
icon: Slot::Empty,
|
icon: ItemSlot::Empty,
|
||||||
frame: FrameType::Task,
|
frame: FrameType::Task,
|
||||||
show_toast: true,
|
show_toast: true,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use azalea_buf::{
|
use azalea_buf::{
|
||||||
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
||||||
};
|
};
|
||||||
use azalea_core::{ResourceLocation, Slot};
|
use azalea_core::ResourceLocation;
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use azalea_registry::RecipeSerializer;
|
use azalea_registry::RecipeSerializer;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ pub struct ShapelessRecipe {
|
||||||
pub group: String,
|
pub group: String,
|
||||||
pub category: CraftingBookCategory,
|
pub category: CraftingBookCategory,
|
||||||
pub ingredients: Vec<Ingredient>,
|
pub ingredients: Vec<Ingredient>,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShapedRecipe {
|
pub struct ShapedRecipe {
|
||||||
|
@ -35,7 +36,7 @@ pub struct ShapedRecipe {
|
||||||
pub group: String,
|
pub group: String,
|
||||||
pub category: CraftingBookCategory,
|
pub category: CraftingBookCategory,
|
||||||
pub ingredients: Vec<Ingredient>,
|
pub ingredients: Vec<Ingredient>,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
pub show_notification: bool,
|
pub show_notification: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ impl McBufReadable for ShapedRecipe {
|
||||||
for _ in 0..width * height {
|
for _ in 0..width * height {
|
||||||
ingredients.push(Ingredient::read_from(buf)?);
|
ingredients.push(Ingredient::read_from(buf)?);
|
||||||
}
|
}
|
||||||
let result = Slot::read_from(buf)?;
|
let result = ItemSlot::read_from(buf)?;
|
||||||
let show_notification = bool::read_from(buf)?;
|
let show_notification = bool::read_from(buf)?;
|
||||||
|
|
||||||
Ok(ShapedRecipe {
|
Ok(ShapedRecipe {
|
||||||
|
@ -91,7 +92,7 @@ pub struct CookingRecipe {
|
||||||
pub group: String,
|
pub group: String,
|
||||||
pub category: CraftingBookCategory,
|
pub category: CraftingBookCategory,
|
||||||
pub ingredient: Ingredient,
|
pub ingredient: Ingredient,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
pub experience: f32,
|
pub experience: f32,
|
||||||
#[var]
|
#[var]
|
||||||
pub cooking_time: u32,
|
pub cooking_time: u32,
|
||||||
|
@ -100,13 +101,13 @@ pub struct CookingRecipe {
|
||||||
pub struct StoneCutterRecipe {
|
pub struct StoneCutterRecipe {
|
||||||
pub group: String,
|
pub group: String,
|
||||||
pub ingredient: Ingredient,
|
pub ingredient: Ingredient,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
pub struct SmithingRecipe {
|
pub struct SmithingRecipe {
|
||||||
pub base: Ingredient,
|
pub base: Ingredient,
|
||||||
pub addition: Ingredient,
|
pub addition: Ingredient,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
|
@ -119,7 +120,7 @@ pub struct SmithingTransformRecipe {
|
||||||
pub template: Ingredient,
|
pub template: Ingredient,
|
||||||
pub base: Ingredient,
|
pub base: Ingredient,
|
||||||
pub addition: Ingredient,
|
pub addition: Ingredient,
|
||||||
pub result: Slot,
|
pub result: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
|
@ -159,7 +160,7 @@ pub enum RecipeData {
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf)]
|
#[derive(Clone, Debug, McBuf)]
|
||||||
pub struct Ingredient {
|
pub struct Ingredient {
|
||||||
pub allowed: Vec<Slot>,
|
pub allowed: Vec<ItemSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufWritable for Recipe {
|
impl McBufWritable for Recipe {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::{operations::ClickType, ItemSlot};
|
||||||
use azalea_protocol_macros::ServerboundGamePacket;
|
use azalea_protocol_macros::ServerboundGamePacket;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -8,20 +8,9 @@ pub struct ServerboundContainerClickPacket {
|
||||||
pub container_id: u8,
|
pub container_id: u8,
|
||||||
#[var]
|
#[var]
|
||||||
pub state_id: u32,
|
pub state_id: u32,
|
||||||
pub slot_num: u16,
|
pub slot_num: i16,
|
||||||
pub button_num: u8,
|
pub button_num: u8,
|
||||||
pub click_type: ClickType,
|
pub click_type: ClickType,
|
||||||
pub changed_slots: HashMap<u16, Slot>,
|
pub changed_slots: HashMap<u16, ItemSlot>,
|
||||||
pub carried_item: Slot,
|
pub carried_item: ItemSlot,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(McBuf, Clone, Copy, Debug)]
|
|
||||||
pub enum ClickType {
|
|
||||||
Pickup = 0,
|
|
||||||
QuickMove = 1,
|
|
||||||
Swap = 2,
|
|
||||||
Clone = 3,
|
|
||||||
Throw = 4,
|
|
||||||
QuickCraft = 5,
|
|
||||||
PickupAll = 6,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::Slot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_protocol_macros::ServerboundGamePacket;
|
use azalea_protocol_macros::ServerboundGamePacket;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
|
||||||
pub struct ServerboundSetCreativeModeSlotPacket {
|
pub struct ServerboundSetCreativeModeSlotPacket {
|
||||||
pub slot_num: u16,
|
pub slot_num: u16,
|
||||||
pub item_stack: Slot,
|
pub item_stack: ItemSlot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,26 @@ use std::io::{Cursor, Write};
|
||||||
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
|
||||||
pub struct ServerboundUseItemOnPacket {
|
pub struct ServerboundUseItemOnPacket {
|
||||||
pub hand: InteractionHand,
|
pub hand: InteractionHand,
|
||||||
pub block_hit: BlockHitResult,
|
pub block_hit: BlockHit,
|
||||||
#[var]
|
#[var]
|
||||||
pub sequence: u32,
|
pub sequence: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BlockHitResult {
|
pub struct BlockHit {
|
||||||
|
/// The block that we clicked.
|
||||||
pub block_pos: BlockPos,
|
pub block_pos: BlockPos,
|
||||||
|
/// The face of the block that was clicked.
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
|
/// The exact coordinates of the world where the block was clicked. In the
|
||||||
|
/// network, this is transmitted as the difference between the location and
|
||||||
|
/// block position.
|
||||||
pub location: Vec3,
|
pub location: Vec3,
|
||||||
|
/// Whether the player's head is inside of a block.
|
||||||
pub inside: bool,
|
pub inside: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufWritable for BlockHitResult {
|
impl McBufWritable for BlockHit {
|
||||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||||
self.block_pos.write_into(buf)?;
|
self.block_pos.write_into(buf)?;
|
||||||
self.direction.write_into(buf)?;
|
self.direction.write_into(buf)?;
|
||||||
|
@ -41,7 +47,7 @@ impl McBufWritable for BlockHitResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl McBufReadable for BlockHitResult {
|
impl McBufReadable for BlockHit {
|
||||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
let block_pos = BlockPos::read_from(buf)?;
|
let block_pos = BlockPos::read_from(buf)?;
|
||||||
let direction = Direction::read_from(buf)?;
|
let direction = Direction::read_from(buf)?;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@ azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
|
||||||
azalea-core = { path = "../azalea-core", version = "^0.6.0", features = [
|
azalea-core = { path = "../azalea-core", version = "^0.6.0", features = [
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
] }
|
] }
|
||||||
|
azalea-inventory = { version = "0.1.0", path = "../azalea-inventory" }
|
||||||
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
|
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
|
||||||
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
|
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
|
||||||
bevy_app = "0.10.0"
|
bevy_app = "0.10.0"
|
||||||
|
@ -30,3 +31,6 @@ uuid = "1.1.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
azalea-client = { path = "../azalea-client" }
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
# Azalea World
|
|
||||||
|
|
||||||
The Minecraft world representation used in Azalea.
|
The Minecraft world representation used in Azalea.
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{ChunkStorage, Instance};
|
use crate::{entity::WorldName, ChunkStorage, Instance};
|
||||||
|
|
||||||
/// A container of [`Instance`]s (aka worlds). Instances are stored as a Weak
|
/// A container of [`Instance`]s (aka worlds). Instances are stored as a Weak
|
||||||
/// pointer here, so if no clients are using an instance it will be forgotten.
|
/// pointer here, so if no clients are using an instance it will be forgotten.
|
||||||
|
@ -37,7 +37,7 @@ impl InstanceContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a world from the container.
|
/// Get a world from the container.
|
||||||
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<RwLock<Instance>>> {
|
pub fn get(&self, name: &WorldName) -> Option<Arc<RwLock<Instance>>> {
|
||||||
self.worlds.get(name).and_then(|world| world.upgrade())
|
self.worlds.get(name).and_then(|world| world.upgrade())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ use azalea_buf::{
|
||||||
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
BufReadError, McBuf, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable,
|
||||||
};
|
};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{BlockPos, Direction, GlobalPos, Particle, Slot, Vec3};
|
use azalea_core::{BlockPos, Direction, GlobalPos, Particle, Vec3};
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
use bevy_ecs::component::Component;
|
use bevy_ecs::component::Component;
|
||||||
use derive_more::Deref;
|
use derive_more::Deref;
|
||||||
use enum_as_inner::EnumAsInner;
|
use enum_as_inner::EnumAsInner;
|
||||||
|
@ -60,7 +61,7 @@ pub enum EntityDataValue {
|
||||||
String(String),
|
String(String),
|
||||||
FormattedText(FormattedText),
|
FormattedText(FormattedText),
|
||||||
OptionalFormattedText(Option<FormattedText>),
|
OptionalFormattedText(Option<FormattedText>),
|
||||||
ItemStack(Slot),
|
ItemStack(ItemSlot),
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Rotations(Rotations),
|
Rotations(Rotations),
|
||||||
BlockPos(BlockPos),
|
BlockPos(BlockPos),
|
||||||
|
|
|
@ -29,7 +29,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::Local;
|
use super::{Local, LookDirection};
|
||||||
|
|
||||||
/// A Bevy [`SystemSet`] for various types of entity updates.
|
/// A Bevy [`SystemSet`] for various types of entity updates.
|
||||||
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
|
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
|
||||||
|
@ -75,6 +75,7 @@ impl Plugin for EntityPlugin {
|
||||||
debug_detect_updates_received_on_local_entities,
|
debug_detect_updates_received_on_local_entities,
|
||||||
add_dead,
|
add_dead,
|
||||||
update_bounding_box,
|
update_bounding_box,
|
||||||
|
clamp_look_direction,
|
||||||
))
|
))
|
||||||
.init_resource::<EntityInfos>();
|
.init_resource::<EntityInfos>();
|
||||||
}
|
}
|
||||||
|
@ -218,10 +219,10 @@ fn update_entity_chunk_positions(
|
||||||
),
|
),
|
||||||
Changed<entity::Position>,
|
Changed<entity::Position>,
|
||||||
>,
|
>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (entity, pos, last_pos, world_name) in query.iter_mut() {
|
for (entity, pos, last_pos, world_name) in query.iter_mut() {
|
||||||
let world_lock = world_container.get(world_name).unwrap();
|
let world_lock = instance_container.get(world_name).unwrap();
|
||||||
let mut world = world_lock.write();
|
let mut world = world_lock.write();
|
||||||
|
|
||||||
let old_chunk = ChunkPos::from(*last_pos);
|
let old_chunk = ChunkPos::from(*last_pos);
|
||||||
|
@ -285,11 +286,11 @@ fn debug_detect_updates_received_on_local_entities(
|
||||||
fn remove_despawned_entities_from_indexes(
|
fn remove_despawned_entities_from_indexes(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut entity_infos: ResMut<EntityInfos>,
|
mut entity_infos: ResMut<EntityInfos>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed<LoadedBy>>,
|
query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed<LoadedBy>>,
|
||||||
) {
|
) {
|
||||||
for (entity, uuid, position, world_name, loaded_by) in &query {
|
for (entity, uuid, position, world_name, loaded_by) in &query {
|
||||||
let world_lock = world_container.get(world_name).unwrap();
|
let world_lock = instance_container.get(world_name).unwrap();
|
||||||
let mut world = world_lock.write();
|
let mut world = world_lock.write();
|
||||||
|
|
||||||
// if the entity has no references left, despawn it
|
// if the entity has no references left, despawn it
|
||||||
|
@ -322,6 +323,13 @@ fn remove_despawned_entities_from_indexes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
|
||||||
|
for mut look_direction in &mut query {
|
||||||
|
look_direction.y_rot %= 360.0;
|
||||||
|
look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for EntityInfos {
|
impl Debug for EntityInfos {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("EntityInfos").finish()
|
f.debug_struct("EntityInfos").finish()
|
||||||
|
|
|
@ -8,7 +8,8 @@ use super::{
|
||||||
SnifferState, VillagerData,
|
SnifferState, VillagerData,
|
||||||
};
|
};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{BlockPos, Direction, Particle, Slot, Vec3};
|
use azalea_core::{BlockPos, Direction, Particle, Vec3};
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -2140,7 +2141,7 @@ impl Default for DrownedMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct EggItemStack(pub Slot);
|
pub struct EggItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Egg;
|
pub struct Egg;
|
||||||
impl Egg {
|
impl Egg {
|
||||||
|
@ -2186,7 +2187,7 @@ impl Default for EggMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
egg_item_stack: EggItemStack(Slot::Empty),
|
egg_item_stack: EggItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2397,7 +2398,7 @@ impl Default for EnderDragonMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct EnderPearlItemStack(pub Slot);
|
pub struct EnderPearlItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct EnderPearl;
|
pub struct EnderPearl;
|
||||||
impl EnderPearl {
|
impl EnderPearl {
|
||||||
|
@ -2443,7 +2444,7 @@ impl Default for EnderPearlMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
ender_pearl_item_stack: EnderPearlItemStack(Slot::Empty),
|
ender_pearl_item_stack: EnderPearlItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2733,7 +2734,7 @@ impl Default for EvokerFangsMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ExperienceBottleItemStack(pub Slot);
|
pub struct ExperienceBottleItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct ExperienceBottle;
|
pub struct ExperienceBottle;
|
||||||
impl ExperienceBottle {
|
impl ExperienceBottle {
|
||||||
|
@ -2779,7 +2780,7 @@ impl Default for ExperienceBottleMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
experience_bottle_item_stack: ExperienceBottleItemStack(Slot::Empty),
|
experience_bottle_item_stack: ExperienceBottleItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2830,7 +2831,7 @@ impl Default for ExperienceOrbMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct EyeOfEnderItemStack(pub Slot);
|
pub struct EyeOfEnderItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct EyeOfEnder;
|
pub struct EyeOfEnder;
|
||||||
impl EyeOfEnder {
|
impl EyeOfEnder {
|
||||||
|
@ -2876,7 +2877,7 @@ impl Default for EyeOfEnderMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
eye_of_ender_item_stack: EyeOfEnderItemStack(Slot::Empty),
|
eye_of_ender_item_stack: EyeOfEnderItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2934,7 +2935,7 @@ impl Default for FallingBlockMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct FireballItemStack(pub Slot);
|
pub struct FireballItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Fireball;
|
pub struct Fireball;
|
||||||
impl Fireball {
|
impl Fireball {
|
||||||
|
@ -2980,13 +2981,13 @@ impl Default for FireballMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
fireball_item_stack: FireballItemStack(Slot::Empty),
|
fireball_item_stack: FireballItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct FireworksItem(pub Slot);
|
pub struct FireworksItem(pub ItemSlot);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct AttachedToTarget(pub OptionalUnsignedInt);
|
pub struct AttachedToTarget(pub OptionalUnsignedInt);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
|
@ -3044,7 +3045,7 @@ impl Default for FireworkRocketMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
fireworks_item: FireworksItem(Slot::Empty),
|
fireworks_item: FireworksItem(ItemSlot::Empty),
|
||||||
attached_to_target: AttachedToTarget(OptionalUnsignedInt(None)),
|
attached_to_target: AttachedToTarget(OptionalUnsignedInt(None)),
|
||||||
shot_at_angle: ShotAtAngle(false),
|
shot_at_angle: ShotAtAngle(false),
|
||||||
}
|
}
|
||||||
|
@ -3521,7 +3522,7 @@ impl Default for GiantMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ItemFrameItem(pub Slot);
|
pub struct ItemFrameItem(pub ItemSlot);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct Rotation(pub i32);
|
pub struct Rotation(pub i32);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
@ -3567,7 +3568,7 @@ impl Default for GlowItemFrameMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
item_frame_item: ItemFrameItem(Slot::Empty),
|
item_frame_item: ItemFrameItem(ItemSlot::Empty),
|
||||||
rotation: Rotation(0),
|
rotation: Rotation(0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4356,7 +4357,7 @@ impl Default for IronGolemMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ItemItem(pub Slot);
|
pub struct ItemItem(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Item;
|
pub struct Item;
|
||||||
impl Item {
|
impl Item {
|
||||||
|
@ -4402,7 +4403,7 @@ impl Default for ItemMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
item_item: ItemItem(Slot::Empty),
|
item_item: ItemItem(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4436,7 +4437,7 @@ pub struct ItemDisplayHeight(pub f32);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ItemDisplayGlowColorOverride(pub i32);
|
pub struct ItemDisplayGlowColorOverride(pub i32);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ItemDisplayItemStack(pub Slot);
|
pub struct ItemDisplayItemStack(pub ItemSlot);
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct ItemDisplayItemDisplay(pub u8);
|
pub struct ItemDisplayItemDisplay(pub u8);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
@ -4580,7 +4581,7 @@ impl Default for ItemDisplayMetadataBundle {
|
||||||
item_display_width: ItemDisplayWidth(0.0),
|
item_display_width: ItemDisplayWidth(0.0),
|
||||||
item_display_height: ItemDisplayHeight(0.0),
|
item_display_height: ItemDisplayHeight(0.0),
|
||||||
item_display_glow_color_override: ItemDisplayGlowColorOverride(-1),
|
item_display_glow_color_override: ItemDisplayGlowColorOverride(-1),
|
||||||
item_display_item_stack: ItemDisplayItemStack(Slot::Empty),
|
item_display_item_stack: ItemDisplayItemStack(ItemSlot::Empty),
|
||||||
item_display_item_display: ItemDisplayItemDisplay(Default::default()),
|
item_display_item_display: ItemDisplayItemDisplay(Default::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4635,7 +4636,7 @@ impl Default for ItemFrameMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
item_frame_item: ItemFrameItem(Slot::Empty),
|
item_frame_item: ItemFrameItem(ItemSlot::Empty),
|
||||||
rotation: Rotation(0),
|
rotation: Rotation(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6192,7 +6193,7 @@ impl Default for PolarBearMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct PotionItemStack(pub Slot);
|
pub struct PotionItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Potion;
|
pub struct Potion;
|
||||||
impl Potion {
|
impl Potion {
|
||||||
|
@ -6238,7 +6239,7 @@ impl Default for PotionMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
potion_item_stack: PotionItemStack(Slot::Empty),
|
potion_item_stack: PotionItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7070,7 +7071,7 @@ impl Default for SlimeMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct SmallFireballItemStack(pub Slot);
|
pub struct SmallFireballItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct SmallFireball;
|
pub struct SmallFireball;
|
||||||
impl SmallFireball {
|
impl SmallFireball {
|
||||||
|
@ -7116,7 +7117,7 @@ impl Default for SmallFireballMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
small_fireball_item_stack: SmallFireballItemStack(Slot::Empty),
|
small_fireball_item_stack: SmallFireballItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7281,7 +7282,7 @@ impl Default for SnowGolemMetadataBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Clone)]
|
#[derive(Component, Deref, DerefMut, Clone)]
|
||||||
pub struct SnowballItemStack(pub Slot);
|
pub struct SnowballItemStack(pub ItemSlot);
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Snowball;
|
pub struct Snowball;
|
||||||
impl Snowball {
|
impl Snowball {
|
||||||
|
@ -7327,7 +7328,7 @@ impl Default for SnowballMetadataBundle {
|
||||||
pose: Pose::default(),
|
pose: Pose::default(),
|
||||||
ticks_frozen: TicksFrozen(0),
|
ticks_frozen: TicksFrozen(0),
|
||||||
},
|
},
|
||||||
snowball_item_stack: SnowballItemStack(Slot::Empty),
|
snowball_item_stack: SnowballItemStack(ItemSlot::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ pub use data::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
pub use dimensions::{update_bounding_box, EntityDimensions};
|
pub use dimensions::{update_bounding_box, EntityDimensions};
|
||||||
pub use info::{
|
pub use info::{
|
||||||
EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy, PartialEntityInfos, RelativeEntityUpdate,
|
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy, PartialEntityInfos,
|
||||||
|
RelativeEntityUpdate,
|
||||||
};
|
};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -38,19 +39,18 @@ impl std::hash::Hash for MinecraftEntityId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_relative(physics: &mut Physics, speed: f32, acceleration: &Vec3) {
|
pub fn move_relative(
|
||||||
let input_vector = input_vector(physics, speed, acceleration);
|
physics: &mut Physics,
|
||||||
|
direction: &LookDirection,
|
||||||
|
speed: f32,
|
||||||
|
acceleration: &Vec3,
|
||||||
|
) {
|
||||||
|
let input_vector = input_vector(direction, speed, acceleration);
|
||||||
physics.delta += input_vector;
|
physics.delta += input_vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_vector(physics: &mut Physics, speed: f32, acceleration: &Vec3) -> Vec3 {
|
pub fn input_vector(direction: &LookDirection, speed: f32, acceleration: &Vec3) -> Vec3 {
|
||||||
let distance = acceleration.length_squared();
|
let distance = acceleration.length_squared();
|
||||||
if distance < 1.0E-7 {
|
if distance < 1.0E-7 {
|
||||||
return Vec3::default();
|
return Vec3::default();
|
||||||
|
@ -61,8 +61,8 @@ pub fn input_vector(physics: &mut Physics, speed: f32, acceleration: &Vec3) -> V
|
||||||
*acceleration
|
*acceleration
|
||||||
}
|
}
|
||||||
.scale(speed as f64);
|
.scale(speed as f64);
|
||||||
let y_rot = f32::sin(physics.y_rot * 0.017453292f32);
|
let y_rot = f32::sin(direction.y_rot * 0.017453292f32);
|
||||||
let x_rot = f32::cos(physics.y_rot * 0.017453292f32);
|
let x_rot = f32::cos(direction.y_rot * 0.017453292f32);
|
||||||
Vec3 {
|
Vec3 {
|
||||||
x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64),
|
x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64),
|
||||||
y: acceleration.y,
|
y: acceleration.y,
|
||||||
|
@ -70,6 +70,20 @@ pub fn input_vector(physics: &mut Physics, speed: f32, acceleration: &Vec3) -> V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view_vector(look_direction: &LookDirection) -> Vec3 {
|
||||||
|
let x_rot = look_direction.x_rot * 0.017453292;
|
||||||
|
let y_rot = -look_direction.y_rot * 0.017453292;
|
||||||
|
let y_rot_cos = f32::cos(y_rot);
|
||||||
|
let y_rot_sin = f32::sin(y_rot);
|
||||||
|
let x_rot_cos = f32::cos(x_rot);
|
||||||
|
let x_rot_sin = f32::sin(x_rot);
|
||||||
|
Vec3 {
|
||||||
|
x: (y_rot_sin * x_rot_cos) as f64,
|
||||||
|
y: (-x_rot_sin) as f64,
|
||||||
|
z: (y_rot_cos * x_rot_cos) as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the position of the block below the entity, but a little lower.
|
/// Get the position of the block below the entity, but a little lower.
|
||||||
pub fn on_pos_legacy(chunk_storage: &ChunkStorage, position: &Position) -> BlockPos {
|
pub fn on_pos_legacy(chunk_storage: &ChunkStorage, position: &Position) -> BlockPos {
|
||||||
on_pos(0.2, chunk_storage, position)
|
on_pos(0.2, chunk_storage, position)
|
||||||
|
@ -128,6 +142,11 @@ impl Debug for EntityUuid {
|
||||||
/// automatically.
|
/// automatically.
|
||||||
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
||||||
pub struct Position(Vec3);
|
pub struct Position(Vec3);
|
||||||
|
impl From<&Position> for Vec3 {
|
||||||
|
fn from(value: &Position) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<Position> for ChunkPos {
|
impl From<Position> for ChunkPos {
|
||||||
fn from(value: Position) -> Self {
|
fn from(value: Position) -> Self {
|
||||||
ChunkPos::from(&value.0)
|
ChunkPos::from(&value.0)
|
||||||
|
@ -149,9 +168,14 @@ impl From<&Position> for BlockPos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The last position of the entity that was sent to the network.
|
/// The last position of the entity that was sent over the network.
|
||||||
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
|
||||||
pub struct LastSentPosition(Vec3);
|
pub struct LastSentPosition(Vec3);
|
||||||
|
impl From<&LastSentPosition> for Vec3 {
|
||||||
|
fn from(value: &LastSentPosition) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<LastSentPosition> for ChunkPos {
|
impl From<LastSentPosition> for ChunkPos {
|
||||||
fn from(value: LastSentPosition) -> Self {
|
fn from(value: LastSentPosition) -> Self {
|
||||||
ChunkPos::from(&value.0)
|
ChunkPos::from(&value.0)
|
||||||
|
@ -182,9 +206,16 @@ pub struct WorldName(pub ResourceLocation);
|
||||||
///
|
///
|
||||||
/// If this is true, the entity will try to jump every tick. (It's equivalent to
|
/// If this is true, the entity will try to jump every tick. (It's equivalent to
|
||||||
/// the space key being held in vanilla.)
|
/// the space key being held in vanilla.)
|
||||||
#[derive(Debug, Component, Deref, DerefMut)]
|
#[derive(Debug, Component, Clone, Deref, DerefMut)]
|
||||||
pub struct Jumping(bool);
|
pub struct Jumping(bool);
|
||||||
|
|
||||||
|
/// A component that contains the direction an entity is looking.
|
||||||
|
#[derive(Debug, Component, Clone, Default)]
|
||||||
|
pub struct LookDirection {
|
||||||
|
pub x_rot: f32,
|
||||||
|
pub y_rot: f32,
|
||||||
|
}
|
||||||
|
|
||||||
/// The physics data relating to the entity, such as position, velocity, and
|
/// The physics data relating to the entity, such as position, velocity, and
|
||||||
/// bounding box.
|
/// bounding box.
|
||||||
#[derive(Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
|
@ -198,12 +229,6 @@ pub struct Physics {
|
||||||
/// Z acceleration.
|
/// Z acceleration.
|
||||||
pub zza: f32,
|
pub zza: f32,
|
||||||
|
|
||||||
pub x_rot: f32,
|
|
||||||
pub y_rot: f32,
|
|
||||||
|
|
||||||
pub x_rot_last: f32,
|
|
||||||
pub y_rot_last: f32,
|
|
||||||
|
|
||||||
pub on_ground: bool,
|
pub on_ground: bool,
|
||||||
pub last_on_ground: bool,
|
pub last_on_ground: bool,
|
||||||
|
|
||||||
|
@ -237,10 +262,38 @@ pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A component that contains the offset of the entity's eyes from the entity
|
||||||
|
/// coordinates.
|
||||||
|
///
|
||||||
|
/// This is used to calculate the camera position for players, when spectating
|
||||||
|
/// an entity, and when raytracing from the entity.
|
||||||
|
#[derive(Component, Clone, Copy, Debug, PartialEq, Deref, DerefMut)]
|
||||||
|
pub struct EyeHeight(f32);
|
||||||
|
impl From<EyeHeight> for f32 {
|
||||||
|
fn from(value: EyeHeight) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<EyeHeight> for f64 {
|
||||||
|
fn from(value: EyeHeight) -> Self {
|
||||||
|
value.0 as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&EyeHeight> for f32 {
|
||||||
|
fn from(value: &EyeHeight) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&EyeHeight> for f64 {
|
||||||
|
fn from(value: &EyeHeight) -> Self {
|
||||||
|
value.0 as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component NewType for [`azalea_registry::EntityKind`].
|
/// A component NewType for [`azalea_registry::EntityKind`].
|
||||||
///
|
///
|
||||||
/// Most of the time, you should be using `azalea_registry::EntityKind`
|
/// Most of the time, you should be using `azalea_registry::EntityKind`
|
||||||
/// instead.
|
/// directly instead.
|
||||||
#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)]
|
#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)]
|
||||||
pub struct EntityKind(pub azalea_registry::EntityKind);
|
pub struct EntityKind(pub azalea_registry::EntityKind);
|
||||||
|
|
||||||
|
@ -254,6 +307,8 @@ pub struct EntityBundle {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub last_sent_position: LastSentPosition,
|
pub last_sent_position: LastSentPosition,
|
||||||
pub physics: Physics,
|
pub physics: Physics,
|
||||||
|
pub direction: LookDirection,
|
||||||
|
pub eye_height: EyeHeight,
|
||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
pub jumping: Jumping,
|
pub jumping: Jumping,
|
||||||
}
|
}
|
||||||
|
@ -265,11 +320,12 @@ impl EntityBundle {
|
||||||
kind: azalea_registry::EntityKind,
|
kind: azalea_registry::EntityKind,
|
||||||
world_name: ResourceLocation,
|
world_name: ResourceLocation,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// TODO: get correct entity dimensions by having them codegened somewhere
|
// TODO: get correct entity dimensions by having them codegen'd somewhere
|
||||||
let dimensions = EntityDimensions {
|
let dimensions = EntityDimensions {
|
||||||
width: 0.6,
|
width: 0.6,
|
||||||
height: 1.8,
|
height: 1.8,
|
||||||
};
|
};
|
||||||
|
let eye_height = dimensions.height * 0.85;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
kind: EntityKind(kind),
|
kind: EntityKind(kind),
|
||||||
|
@ -284,12 +340,6 @@ impl EntityBundle {
|
||||||
yya: 0.,
|
yya: 0.,
|
||||||
zza: 0.,
|
zza: 0.,
|
||||||
|
|
||||||
x_rot: 0.,
|
|
||||||
y_rot: 0.,
|
|
||||||
|
|
||||||
y_rot_last: 0.,
|
|
||||||
x_rot_last: 0.,
|
|
||||||
|
|
||||||
on_ground: false,
|
on_ground: false,
|
||||||
last_on_ground: false,
|
last_on_ground: false,
|
||||||
|
|
||||||
|
@ -299,6 +349,8 @@ impl EntityBundle {
|
||||||
|
|
||||||
has_impulse: false,
|
has_impulse: false,
|
||||||
},
|
},
|
||||||
|
eye_height: EyeHeight(eye_height),
|
||||||
|
direction: LookDirection::default(),
|
||||||
|
|
||||||
attributes: Attributes {
|
attributes: Attributes {
|
||||||
// TODO: do the correct defaults for everything, some
|
// TODO: do the correct defaults for everything, some
|
||||||
|
|
|
@ -59,11 +59,11 @@ pub fn deduplicate_entities(
|
||||||
(Changed<MinecraftEntityId>, Without<Local>),
|
(Changed<MinecraftEntityId>, Without<Local>),
|
||||||
>,
|
>,
|
||||||
mut loaded_by_query: Query<&mut LoadedBy>,
|
mut loaded_by_query: Query<&mut LoadedBy>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
// if this entity already exists, remove it
|
// if this entity already exists, remove it
|
||||||
for (new_entity, id, world_name) in query.iter_mut() {
|
for (new_entity, id, world_name) in query.iter_mut() {
|
||||||
if let Some(world_lock) = world_container.get(world_name) {
|
if let Some(world_lock) = instance_container.get(world_name) {
|
||||||
let world = world_lock.write();
|
let world = world_lock.write();
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
if old_entity == &new_entity {
|
if old_entity == &new_entity {
|
||||||
|
@ -104,11 +104,11 @@ pub fn deduplicate_local_entities(
|
||||||
(Entity, &MinecraftEntityId, &WorldName),
|
(Entity, &MinecraftEntityId, &WorldName),
|
||||||
(Changed<MinecraftEntityId>, With<Local>),
|
(Changed<MinecraftEntityId>, With<Local>),
|
||||||
>,
|
>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
// if this entity already exists, remove the old one
|
// if this entity already exists, remove the old one
|
||||||
for (new_entity, id, world_name) in query.iter_mut() {
|
for (new_entity, id, world_name) in query.iter_mut() {
|
||||||
if let Some(world_lock) = world_container.get(world_name) {
|
if let Some(world_lock) = instance_container.get(world_name) {
|
||||||
let world = world_lock.write();
|
let world = world_lock.write();
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
if old_entity == &new_entity {
|
if old_entity == &new_entity {
|
||||||
|
@ -154,11 +154,11 @@ pub fn update_uuid_index(
|
||||||
// mut commands: Commands,
|
// mut commands: Commands,
|
||||||
// partial_entity_infos: &mut PartialEntityInfos,
|
// partial_entity_infos: &mut PartialEntityInfos,
|
||||||
// chunk: &ChunkPos,
|
// chunk: &ChunkPos,
|
||||||
// world_container: &WorldContainer,
|
// instance_container: &WorldContainer,
|
||||||
// world_name: &WorldName,
|
// world_name: &WorldName,
|
||||||
// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>,
|
// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>,
|
||||||
// ) {
|
// ) {
|
||||||
// let world_lock = world_container.get(world_name).unwrap();
|
// let world_lock = instance_container.get(world_name).unwrap();
|
||||||
// let world = world_lock.read();
|
// let world = world_lock.read();
|
||||||
|
|
||||||
// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
||||||
|
@ -195,6 +195,12 @@ impl Instance {
|
||||||
///
|
///
|
||||||
/// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2`, for
|
/// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2`, for
|
||||||
/// optimization purposes.
|
/// optimization purposes.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn example(client: &azalea_client::Client) {
|
||||||
|
/// client.world().read().find_block(client.position(), &azalea_registry::Block::Chest.into());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn find_block(
|
pub fn find_block(
|
||||||
&self,
|
&self,
|
||||||
nearest_to: impl Into<BlockPos>,
|
nearest_to: impl Into<BlockPos>,
|
||||||
|
@ -290,10 +296,10 @@ pub fn update_entity_by_id_index(
|
||||||
(Entity, &MinecraftEntityId, &WorldName, Option<&Local>),
|
(Entity, &MinecraftEntityId, &WorldName, Option<&Local>),
|
||||||
Changed<MinecraftEntityId>,
|
Changed<MinecraftEntityId>,
|
||||||
>,
|
>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (entity, id, world_name, local) in query.iter_mut() {
|
for (entity, id, world_name, local) in query.iter_mut() {
|
||||||
let world_lock = world_container.get(world_name).unwrap();
|
let world_lock = instance_container.get(world_name).unwrap();
|
||||||
let mut world = world_lock.write();
|
let mut world = world_lock.write();
|
||||||
if local.is_none() {
|
if local.is_none() {
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ azalea-block = { version = "0.6.0", path = "../azalea-block" }
|
||||||
azalea-chat = { version = "0.6.0", path = "../azalea-chat" }
|
azalea-chat = { version = "0.6.0", path = "../azalea-chat" }
|
||||||
azalea-client = { version = "0.6.0", path = "../azalea-client" }
|
azalea-client = { version = "0.6.0", path = "../azalea-client" }
|
||||||
azalea-core = { version = "0.6.0", path = "../azalea-core" }
|
azalea-core = { version = "0.6.0", path = "../azalea-core" }
|
||||||
|
azalea-inventory = { version = "0.1.0", path = "../azalea-inventory" }
|
||||||
azalea-physics = { version = "0.6.0", path = "../azalea-physics" }
|
azalea-physics = { version = "0.6.0", path = "../azalea-physics" }
|
||||||
azalea-protocol = { version = "0.6.0", path = "../azalea-protocol" }
|
azalea-protocol = { version = "0.6.0", path = "../azalea-protocol" }
|
||||||
azalea-registry = { version = "0.6.0", path = "../azalea-registry" }
|
azalea-registry = { version = "0.6.0", path = "../azalea-registry" }
|
||||||
|
|
76
azalea/examples/steal.rs
Normal file
76
azalea/examples/steal.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
//! Steal all the diamonds from all the nearby chests.
|
||||||
|
|
||||||
|
use azalea::{prelude::*, BlockPos};
|
||||||
|
use azalea_inventory::operations::QuickMoveClick;
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let account = Account::offline("bot");
|
||||||
|
// or let bot = Account::microsoft("email").await;
|
||||||
|
|
||||||
|
ClientBuilder::new()
|
||||||
|
.set_handler(handle)
|
||||||
|
.start(account, "localhost")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Component)]
|
||||||
|
struct State {
|
||||||
|
pub checked_chests: Arc<Mutex<Vec<BlockPos>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
|
||||||
|
match event {
|
||||||
|
Event::Chat(m) => {
|
||||||
|
if m.username() == Some(bot.profile.name.clone()) {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if m.content() != "go" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
state.checked_chests.lock().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
let chest_block = bot
|
||||||
|
.world()
|
||||||
|
.read()
|
||||||
|
.find_block(bot.position(), &azalea::Block::Chest.into());
|
||||||
|
// TODO: update this when find_blocks is implemented
|
||||||
|
let Some(chest_block) = chest_block else {
|
||||||
|
bot.chat("No chest found");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
// bot.goto(BlockPosGoal::from(chest_block));
|
||||||
|
let Some(chest) = bot.open_container(chest_block).await else {
|
||||||
|
println!("Couldn't open chest");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Getting contents");
|
||||||
|
for (index, slot) in chest
|
||||||
|
.contents()
|
||||||
|
.expect("we just opened the chest")
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
println!("Checking slot {index}: {slot:?}");
|
||||||
|
if let ItemSlot::Present(item) = slot {
|
||||||
|
if item.kind == azalea::Item::Diamond {
|
||||||
|
println!("clicking slot ^");
|
||||||
|
chest.click(QuickMoveClick::Left { slot: index as u16 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Done");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
use azalea::ecs::query::With;
|
use azalea::ecs::query::With;
|
||||||
use azalea::entity::metadata::Player;
|
use azalea::entity::metadata::Player;
|
||||||
use azalea::entity::Position;
|
use azalea::entity::{EyeHeight, Position};
|
||||||
|
use azalea::interact::HitResultComponent;
|
||||||
|
use azalea::inventory::ItemSlot;
|
||||||
use azalea::pathfinder::BlockPosGoal;
|
use azalea::pathfinder::BlockPosGoal;
|
||||||
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
|
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
|
||||||
use azalea::{Account, Client, Event};
|
use azalea::{Account, Client, Event};
|
||||||
|
@ -46,7 +48,7 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let mut accounts = Vec::new();
|
let mut accounts = Vec::new();
|
||||||
let mut states = Vec::new();
|
let mut states = Vec::new();
|
||||||
|
|
||||||
for i in 0..5 {
|
for i in 0..1 {
|
||||||
accounts.push(Account::offline(&format!("bot{i}")));
|
accounts.push(Account::offline(&format!("bot{i}")));
|
||||||
states.push(State::default());
|
states.push(State::default());
|
||||||
}
|
}
|
||||||
|
@ -112,7 +114,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
bot.chat(&format!("You're at {pos:?}",));
|
bot.chat(&format!("You're at {pos:?}",));
|
||||||
}
|
}
|
||||||
"whereareyou" => {
|
"whereareyou" => {
|
||||||
let pos = bot.component::<Position>();
|
let pos = bot.position();
|
||||||
bot.chat(&format!("I'm at {pos:?}",));
|
bot.chat(&format!("I'm at {pos:?}",));
|
||||||
}
|
}
|
||||||
"goto" => {
|
"goto" => {
|
||||||
|
@ -122,10 +124,11 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
bot.goto(BlockPosGoal::from(target_pos));
|
bot.goto(BlockPosGoal::from(target_pos));
|
||||||
}
|
}
|
||||||
"look" => {
|
"look" => {
|
||||||
let entity_pos = bot.entity_component::<Position>(entity);
|
let entity_pos = bot
|
||||||
let target_pos: BlockPos = entity_pos.into();
|
.entity_component::<Position>(entity)
|
||||||
println!("target_pos: {target_pos:?}");
|
.up(bot.entity_component::<EyeHeight>(entity).into());
|
||||||
bot.look_at(target_pos.center());
|
println!("entity_pos: {entity_pos:?}");
|
||||||
|
bot.look_at(entity_pos);
|
||||||
}
|
}
|
||||||
"jump" => {
|
"jump" => {
|
||||||
bot.set_jumping(true);
|
bot.set_jumping(true);
|
||||||
|
@ -140,18 +143,21 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
"lag" => {
|
"lag" => {
|
||||||
std::thread::sleep(Duration::from_millis(1000));
|
std::thread::sleep(Duration::from_millis(1000));
|
||||||
}
|
}
|
||||||
|
"inventory" => {
|
||||||
|
println!("inventory: {:?}", bot.menu());
|
||||||
|
}
|
||||||
"findblock" => {
|
"findblock" => {
|
||||||
let target_pos = bot.world().read().find_block(
|
let target_pos = bot
|
||||||
bot.component::<Position>(),
|
.world()
|
||||||
&azalea_registry::Block::DiamondBlock.into(),
|
.read()
|
||||||
);
|
.find_block(bot.position(), &azalea::Block::DiamondBlock.into());
|
||||||
bot.chat(&format!("target_pos: {target_pos:?}",));
|
bot.chat(&format!("target_pos: {target_pos:?}",));
|
||||||
}
|
}
|
||||||
"gotoblock" => {
|
"gotoblock" => {
|
||||||
let target_pos = bot.world().read().find_block(
|
let target_pos = bot
|
||||||
bot.component::<Position>(),
|
.world()
|
||||||
&azalea_registry::Block::DiamondBlock.into(),
|
.read()
|
||||||
);
|
.find_block(bot.position(), &azalea::Block::DiamondBlock.into());
|
||||||
if let Some(target_pos) = target_pos {
|
if let Some(target_pos) = target_pos {
|
||||||
// +1 to stand on top of the block
|
// +1 to stand on top of the block
|
||||||
bot.goto(BlockPosGoal::from(target_pos.up(1)));
|
bot.goto(BlockPosGoal::from(target_pos.up(1)));
|
||||||
|
@ -159,6 +165,49 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
bot.chat("no diamond block found");
|
bot.chat("no diamond block found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"lever" => {
|
||||||
|
let target_pos = bot
|
||||||
|
.world()
|
||||||
|
.read()
|
||||||
|
.find_block(bot.position(), &azalea::Block::Lever.into());
|
||||||
|
let Some(target_pos) = target_pos else {
|
||||||
|
bot.chat("no lever found");
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
bot.goto(BlockPosGoal::from(target_pos));
|
||||||
|
bot.look_at(target_pos.center());
|
||||||
|
bot.block_interact(target_pos);
|
||||||
|
}
|
||||||
|
"hitresult" => {
|
||||||
|
let hit_result = bot.get_component::<HitResultComponent>();
|
||||||
|
bot.chat(&format!("hit_result: {hit_result:?}",));
|
||||||
|
}
|
||||||
|
"chest" => {
|
||||||
|
let target_pos = bot
|
||||||
|
.world()
|
||||||
|
.read()
|
||||||
|
.find_block(bot.position(), &azalea::Block::Chest.into());
|
||||||
|
let Some(target_pos) = target_pos else {
|
||||||
|
bot.chat("no chest found");
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
bot.look_at(target_pos.center());
|
||||||
|
let container = bot.open_container(target_pos).await;
|
||||||
|
println!("container: {:?}", container);
|
||||||
|
if let Some(container) = container {
|
||||||
|
if let Some(contents) = container.contents() {
|
||||||
|
for item in contents {
|
||||||
|
if let ItemSlot::Present(item) = item {
|
||||||
|
println!("item: {:?}", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("container was immediately closed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("no container found");
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +245,7 @@ async fn swarm_handle(
|
||||||
SwarmEvent::Chat(m) => {
|
SwarmEvent::Chat(m) => {
|
||||||
println!("swarm chat message: {}", m.message().to_ansi());
|
println!("swarm chat message: {}", m.message().to_ansi());
|
||||||
if m.message().to_string() == "<py5> world" {
|
if m.message().to_string() == "<py5> world" {
|
||||||
for (name, world) in &swarm.world_container.read().worlds {
|
for (name, world) in &swarm.instance_container.read().worlds {
|
||||||
println!("world name: {name}");
|
println!("world name: {name}");
|
||||||
if let Some(w) = world.upgrade() {
|
if let Some(w) = world.upgrade() {
|
||||||
for chunk_pos in w.read().chunks.chunks.values() {
|
for chunk_pos in w.read().chunks.chunks.values() {
|
||||||
|
|
|
@ -38,17 +38,15 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
|
||||||
bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)))
|
bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)))
|
||||||
.await;
|
.await;
|
||||||
let chest = bot
|
let chest = bot
|
||||||
.open_container(&bot.world().find_block(azalea_registry::Block::Chest))
|
.open_container(&bot.world().find_block(azalea::Block::Chest))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks")
|
bot.take_amount_from_container(&chest, 5, |i| i.id == "#minecraft:planks")
|
||||||
.await;
|
.await;
|
||||||
chest.close().await;
|
chest.close().await;
|
||||||
|
|
||||||
let crafting_table = bot
|
let crafting_table = bot
|
||||||
.open_crafting_table(
|
.open_crafting_table(&bot.world.find_block(azalea::Block::CraftingTable))
|
||||||
&bot.world.find_block(azalea_registry::Block::CraftingTable),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bot.craft(&crafting_table, &bot.recipe_for("minecraft:sticks"))
|
bot.craft(&crafting_table, &bot.recipe_for("minecraft:sticks"))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
|
use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
|
||||||
|
use crate::container::ContainerPlugin;
|
||||||
use crate::ecs::{
|
use crate::ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
@ -9,7 +10,8 @@ use crate::ecs::{
|
||||||
};
|
};
|
||||||
use azalea_core::Vec3;
|
use azalea_core::Vec3;
|
||||||
use azalea_physics::{force_jump_listener, PhysicsSet};
|
use azalea_physics::{force_jump_listener, PhysicsSet};
|
||||||
use azalea_world::entity::{metadata::Player, set_rotation, Jumping, Local, Physics, Position};
|
use azalea_world::entity::{clamp_look_direction, EyeHeight, LookDirection};
|
||||||
|
use azalea_world::entity::{metadata::Player, Jumping, Local, Position};
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
use crate::pathfinder::PathfinderPlugin;
|
use crate::pathfinder::PathfinderPlugin;
|
||||||
|
@ -22,7 +24,9 @@ impl Plugin for BotPlugin {
|
||||||
.add_event::<JumpEvent>()
|
.add_event::<JumpEvent>()
|
||||||
.add_systems((
|
.add_systems((
|
||||||
insert_bot,
|
insert_bot,
|
||||||
look_at_listener.before(force_jump_listener),
|
look_at_listener
|
||||||
|
.before(force_jump_listener)
|
||||||
|
.before(clamp_look_direction),
|
||||||
jump_listener,
|
jump_listener,
|
||||||
stop_jumping
|
stop_jumping
|
||||||
.in_schedule(CoreSchedule::FixedUpdate)
|
.in_schedule(CoreSchedule::FixedUpdate)
|
||||||
|
@ -99,12 +103,13 @@ pub struct LookAtEvent {
|
||||||
}
|
}
|
||||||
fn look_at_listener(
|
fn look_at_listener(
|
||||||
mut events: EventReader<LookAtEvent>,
|
mut events: EventReader<LookAtEvent>,
|
||||||
mut query: Query<(&Position, &mut Physics)>,
|
mut query: Query<(&Position, &EyeHeight, &mut LookDirection)>,
|
||||||
) {
|
) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
if let Ok((position, mut physics)) = query.get_mut(event.entity) {
|
if let Ok((position, eye_height, mut look_direction)) = query.get_mut(event.entity) {
|
||||||
let (y_rot, x_rot) = direction_looking_at(position, &event.position);
|
let (y_rot, x_rot) =
|
||||||
set_rotation(&mut physics, y_rot, x_rot);
|
direction_looking_at(&position.up(eye_height.into()), &event.position);
|
||||||
|
(look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,5 +134,6 @@ impl PluginGroup for DefaultBotPlugins {
|
||||||
PluginGroupBuilder::start::<Self>()
|
PluginGroupBuilder::start::<Self>()
|
||||||
.add(BotPlugin)
|
.add(BotPlugin)
|
||||||
.add(PathfinderPlugin)
|
.add(PathfinderPlugin)
|
||||||
|
.add(ContainerPlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
140
azalea/src/container.rs
Normal file
140
azalea/src/container.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
use azalea_client::{
|
||||||
|
inventory::{CloseContainerEvent, ContainerClickEvent, InventoryComponent},
|
||||||
|
packet_handling::PacketEvent,
|
||||||
|
Client, TickBroadcast,
|
||||||
|
};
|
||||||
|
use azalea_core::BlockPos;
|
||||||
|
use azalea_inventory::{operations::ClickOperation, ItemSlot, Menu};
|
||||||
|
use azalea_protocol::packets::game::ClientboundGamePacket;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_ecs::{component::Component, prelude::EventReader, system::Commands};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub struct ContainerPlugin;
|
||||||
|
impl Plugin for ContainerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_system(handle_menu_opened_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ContainerClientExt {
|
||||||
|
async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerClientExt for Client {
|
||||||
|
/// Open a container in the world, like a chest.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use azalea::prelude::*;
|
||||||
|
/// # async fn example(mut bot: azalea::Client) {
|
||||||
|
/// let target_pos = bot
|
||||||
|
/// .world()
|
||||||
|
/// .read()
|
||||||
|
/// .find_block(bot.position(), &azalea::Block::Chest.into());
|
||||||
|
/// let Some(target_pos) = target_pos else {
|
||||||
|
/// bot.chat("no chest found");
|
||||||
|
/// return;
|
||||||
|
/// };
|
||||||
|
/// let container = bot.open_container(target_pos).await;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle> {
|
||||||
|
self.ecs
|
||||||
|
.lock()
|
||||||
|
.entity_mut(self.entity)
|
||||||
|
.insert(WaitingForInventoryOpen);
|
||||||
|
self.block_interact(pos);
|
||||||
|
|
||||||
|
let mut receiver = {
|
||||||
|
let ecs = self.ecs.lock();
|
||||||
|
let tick_broadcast = ecs.resource::<TickBroadcast>();
|
||||||
|
tick_broadcast.subscribe()
|
||||||
|
};
|
||||||
|
while receiver.recv().await.is_ok() {
|
||||||
|
let ecs = self.ecs.lock();
|
||||||
|
if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ecs = self.ecs.lock();
|
||||||
|
let inventory = ecs
|
||||||
|
.get::<InventoryComponent>(self.entity)
|
||||||
|
.expect("no inventory");
|
||||||
|
if inventory.id == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ContainerHandle {
|
||||||
|
id: inventory.id,
|
||||||
|
client: self.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to the open container. The container will be closed once this is
|
||||||
|
/// dropped.
|
||||||
|
pub struct ContainerHandle {
|
||||||
|
pub id: u8,
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
impl Drop for ContainerHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.client.ecs.lock().send_event(CloseContainerEvent {
|
||||||
|
entity: self.client.entity,
|
||||||
|
id: self.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for ContainerHandle {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("ContainerHandle")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ContainerHandle {
|
||||||
|
/// Returns the menu of the container. If the container is closed, this
|
||||||
|
/// will return `None`.
|
||||||
|
pub fn menu(&self) -> Option<Menu> {
|
||||||
|
let ecs = self.client.ecs.lock();
|
||||||
|
let inventory = ecs
|
||||||
|
.get::<InventoryComponent>(self.client.entity)
|
||||||
|
.expect("no inventory");
|
||||||
|
if inventory.id == self.id {
|
||||||
|
Some(inventory.container_menu.clone().unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the item slots in the container, not including the player's
|
||||||
|
/// inventory. If the container is closed, this will return `None`.
|
||||||
|
pub fn contents(&self) -> Option<Vec<ItemSlot>> {
|
||||||
|
self.menu().map(|menu| menu.contents())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn click(&self, operation: impl Into<ClickOperation>) {
|
||||||
|
let operation = operation.into();
|
||||||
|
self.client.ecs.lock().send_event(ContainerClickEvent {
|
||||||
|
entity: self.client.entity,
|
||||||
|
window_id: self.id,
|
||||||
|
operation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct WaitingForInventoryOpen;
|
||||||
|
|
||||||
|
fn handle_menu_opened_event(mut commands: Commands, mut events: EventReader<PacketEvent>) {
|
||||||
|
for event in events.iter() {
|
||||||
|
if let ClientboundGamePacket::ContainerSetContent { .. } = event.packet {
|
||||||
|
commands
|
||||||
|
.entity(event.entity)
|
||||||
|
.remove::<WaitingForInventoryOpen>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![feature(async_closure)]
|
#![feature(async_closure)]
|
||||||
|
#![allow(incomplete_features)]
|
||||||
|
#![feature(async_fn_in_trait)]
|
||||||
|
|
||||||
mod bot;
|
mod bot;
|
||||||
|
mod container;
|
||||||
pub mod pathfinder;
|
pub mod pathfinder;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod swarm;
|
pub mod swarm;
|
||||||
|
@ -12,7 +15,7 @@ pub use azalea_block as blocks;
|
||||||
pub use azalea_client::*;
|
pub use azalea_client::*;
|
||||||
pub use azalea_core::{BlockPos, Vec3};
|
pub use azalea_core::{BlockPos, Vec3};
|
||||||
pub use azalea_protocol as protocol;
|
pub use azalea_protocol as protocol;
|
||||||
pub use azalea_registry::EntityKind;
|
pub use azalea_registry::{Block, EntityKind, Item};
|
||||||
pub use azalea_world::{entity, Instance};
|
pub use azalea_world::{entity, Instance};
|
||||||
use bot::DefaultBotPlugins;
|
use bot::DefaultBotPlugins;
|
||||||
use ecs::component::Component;
|
use ecs::component::Component;
|
||||||
|
|
|
@ -93,7 +93,7 @@ fn goto_listener(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut events: EventReader<GotoEvent>,
|
mut events: EventReader<GotoEvent>,
|
||||||
mut query: Query<(&Position, &WorldName)>,
|
mut query: Query<(&Position, &WorldName)>,
|
||||||
world_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
let thread_pool = AsyncComputeTaskPool::get();
|
let thread_pool = AsyncComputeTaskPool::get();
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ fn goto_listener(
|
||||||
vertical_vel: VerticalVel::None,
|
vertical_vel: VerticalVel::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let world_lock = world_container
|
let world_lock = instance_container
|
||||||
.get(world_name)
|
.get(world_name)
|
||||||
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
.expect("Entity tried to pathfind but the entity isn't in a valid world");
|
||||||
let end = event.goal.goal_node();
|
let end = event.goal.goal_node();
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
//! The Azalea prelude. Things that are necessary for a bare-bones bot are
|
//! The Azalea prelude. Things that are necessary for a bare-bones bot are
|
||||||
//! re-exported here.
|
//! re-exported here.
|
||||||
|
|
||||||
pub use crate::{bot::BotClientExt, pathfinder::PathfinderClientExt, ClientBuilder};
|
pub use crate::{
|
||||||
|
bot::BotClientExt, container::ContainerClientExt, pathfinder::PathfinderClientExt,
|
||||||
|
ClientBuilder,
|
||||||
|
};
|
||||||
pub use azalea_client::{Account, Client, Event};
|
pub use azalea_client::{Account, Client, Event};
|
||||||
// this is necessary to make the macros that reference bevy_ecs work
|
// this is necessary to make the macros that reference bevy_ecs work
|
||||||
pub use crate::ecs as bevy_ecs;
|
pub use crate::ecs as bevy_ecs;
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub struct Swarm {
|
||||||
// bot_datas: Arc<Mutex<Vec<(Client, S)>>>,
|
// bot_datas: Arc<Mutex<Vec<(Client, S)>>>,
|
||||||
resolved_address: SocketAddr,
|
resolved_address: SocketAddr,
|
||||||
address: ServerAddress,
|
address: ServerAddress,
|
||||||
pub world_container: Arc<RwLock<InstanceContainer>>,
|
pub instance_container: Arc<RwLock<InstanceContainer>>,
|
||||||
|
|
||||||
bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
|
bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
|
||||||
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
|
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
|
||||||
|
@ -248,7 +248,7 @@ where
|
||||||
// resolve the address
|
// resolve the address
|
||||||
let resolved_address = resolver::resolve_address(&address).await?;
|
let resolved_address = resolver::resolve_address(&address).await?;
|
||||||
|
|
||||||
let world_container = Arc::new(RwLock::new(InstanceContainer::default()));
|
let instance_container = Arc::new(RwLock::new(InstanceContainer::default()));
|
||||||
|
|
||||||
// we can't modify the swarm plugins after this
|
// we can't modify the swarm plugins after this
|
||||||
let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
|
let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
|
||||||
|
@ -263,7 +263,7 @@ where
|
||||||
|
|
||||||
resolved_address,
|
resolved_address,
|
||||||
address,
|
address,
|
||||||
world_container,
|
instance_container,
|
||||||
|
|
||||||
bots_tx,
|
bots_tx,
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import lib.code.inventory
|
||||||
import lib.code.registry
|
import lib.code.registry
|
||||||
import lib.code.version
|
import lib.code.version
|
||||||
import lib.code.packet
|
import lib.code.packet
|
||||||
|
@ -10,6 +11,7 @@ version_id = lib.code.version.get_version_id()
|
||||||
registries = lib.extract.get_registries_report(version_id)
|
registries = lib.extract.get_registries_report(version_id)
|
||||||
|
|
||||||
lib.code.registry.generate_registries(registries)
|
lib.code.registry.generate_registries(registries)
|
||||||
|
lib.code.inventory.update_menus(registries['minecraft:menu']['entries'])
|
||||||
|
|
||||||
lib.code.utils.fmt()
|
lib.code.utils.fmt()
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ DATA_RS_DIR = get_dir_location(
|
||||||
'../azalea-world/src/entity/data.rs')
|
'../azalea-world/src/entity/data.rs')
|
||||||
|
|
||||||
def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings):
|
def generate_metadata_names(burger_dataserializers: dict, mappings: Mappings):
|
||||||
serializer_names = [None] * len(burger_dataserializers)
|
serializer_names: list[Optional[str]] = [None] * len(burger_dataserializers)
|
||||||
for burger_serializer in burger_dataserializers.values():
|
for burger_serializer in burger_dataserializers.values():
|
||||||
print(burger_serializer)
|
print(burger_serializer)
|
||||||
|
|
||||||
|
@ -105,7 +105,8 @@ use super::{
|
||||||
SnifferState, VillagerData
|
SnifferState, VillagerData
|
||||||
};
|
};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{BlockPos, Direction, Particle, Slot, Vec3};
|
use azalea_core::{BlockPos, Direction, Particle, Vec3};
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -425,7 +426,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
|
||||||
elif type_name == 'OptionalUnsignedInt':
|
elif type_name == 'OptionalUnsignedInt':
|
||||||
default = f'OptionalUnsignedInt(Some({default}))' if default != 'Empty' else 'OptionalUnsignedInt(None)'
|
default = f'OptionalUnsignedInt(Some({default}))' if default != 'Empty' else 'OptionalUnsignedInt(None)'
|
||||||
elif type_name == 'ItemStack':
|
elif type_name == 'ItemStack':
|
||||||
default = f'Slot::Present({default})' if default != 'Empty' else 'Slot::Empty'
|
default = f'ItemSlot::Present({default})' if default != 'Empty' else 'ItemSlot::Empty'
|
||||||
elif type_name == 'BlockState':
|
elif type_name == 'BlockState':
|
||||||
default = f'{default}' if default != 'Empty' else 'azalea_block::BlockState::AIR'
|
default = f'{default}' if default != 'Empty' else 'azalea_block::BlockState::AIR'
|
||||||
elif type_name == 'OptionalBlockState':
|
elif type_name == 'OptionalBlockState':
|
||||||
|
|
108
codegen/lib/code/inventory.py
Normal file
108
codegen/lib/code/inventory.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location
|
||||||
|
from lib.code.utils import burger_type_to_rust_type, write_packet_file
|
||||||
|
from lib.mappings import Mappings
|
||||||
|
from typing import Any, Optional
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
# The directory where declare_menus! {} is done
|
||||||
|
inventory_menus_dir = get_dir_location(f'../azalea-inventory/src/lib.rs')
|
||||||
|
|
||||||
|
|
||||||
|
def update_menus(initial_menu_entries: dict[str, Any]):
|
||||||
|
# new_menus is a dict of { menu_id: { "protocol_id": protocol_id } }
|
||||||
|
# so convert that into an array where the protocol id is the index and the
|
||||||
|
# values are enum variant names
|
||||||
|
new_menus: list[str] = [''] * len(initial_menu_entries)
|
||||||
|
for menu_id, menu in initial_menu_entries.items():
|
||||||
|
new_menus[menu['protocol_id']] = menu_name_to_enum_name(menu_id)
|
||||||
|
|
||||||
|
new_menus.insert(0, 'Player')
|
||||||
|
|
||||||
|
with open(inventory_menus_dir, 'r') as f:
|
||||||
|
menus_rs = f.read().splitlines()
|
||||||
|
|
||||||
|
start_line_index = 0
|
||||||
|
|
||||||
|
current_menus = []
|
||||||
|
in_the_macro = False
|
||||||
|
for i, line in enumerate(menus_rs):
|
||||||
|
if line.startswith('declare_menus!'):
|
||||||
|
in_the_macro = True
|
||||||
|
start_line_index = i
|
||||||
|
if in_the_macro:
|
||||||
|
if line.startswith(' ') and line.endswith('{'):
|
||||||
|
# get the variant name for this menu
|
||||||
|
current_menu = line[:-1].strip()
|
||||||
|
current_menus.append(current_menu)
|
||||||
|
|
||||||
|
print('current_menus', current_menus)
|
||||||
|
print('new_menus', new_menus)
|
||||||
|
|
||||||
|
# now we have the current menus, so compare that with the expected
|
||||||
|
# menus and update the file if needed
|
||||||
|
if current_menus != new_menus:
|
||||||
|
# ok so insert the new menus with todo!() for the body
|
||||||
|
current_menus_list_index = 0
|
||||||
|
new_menus_list_index = 0
|
||||||
|
insert_line_index = start_line_index + 1
|
||||||
|
# figure out what menus need to be placed
|
||||||
|
while True:
|
||||||
|
# if the values at the indexes are the same, add to both and don't do anything
|
||||||
|
if (
|
||||||
|
current_menus_list_index < len(current_menus)
|
||||||
|
and new_menus_list_index < len(new_menus)
|
||||||
|
and current_menus[current_menus_list_index] == new_menus[new_menus_list_index]
|
||||||
|
):
|
||||||
|
current_menus_list_index += 1
|
||||||
|
new_menus_list_index += 1
|
||||||
|
# increase insert_line_index until we get a line that starts with }
|
||||||
|
while not menus_rs[insert_line_index].strip().startswith('}'):
|
||||||
|
insert_line_index += 1
|
||||||
|
insert_line_index += 1
|
||||||
|
# print('same', current_menus_list_index,
|
||||||
|
# new_menus_list_index, insert_line_index)
|
||||||
|
# something was added to new_menus but not current_menus
|
||||||
|
elif new_menus_list_index < len(new_menus) and new_menus[new_menus_list_index] not in current_menus:
|
||||||
|
# insert the new menu
|
||||||
|
menus_rs.insert(
|
||||||
|
insert_line_index, f' {new_menus[new_menus_list_index]} {{\n todo!()\n }},')
|
||||||
|
insert_line_index += 1
|
||||||
|
new_menus_list_index += 1
|
||||||
|
print('added', current_menus_list_index,
|
||||||
|
new_menus_list_index, insert_line_index)
|
||||||
|
# something was removed from new_menus but is still in current_menus
|
||||||
|
elif current_menus_list_index < len(current_menus) and current_menus[current_menus_list_index] not in new_menus:
|
||||||
|
# remove the current menu
|
||||||
|
while not menus_rs[insert_line_index].strip().startswith('}'):
|
||||||
|
menus_rs.pop(insert_line_index)
|
||||||
|
menus_rs.pop(insert_line_index)
|
||||||
|
current_menus_list_index += 1
|
||||||
|
print('removed', current_menus_list_index,
|
||||||
|
new_menus_list_index, insert_line_index)
|
||||||
|
|
||||||
|
# if current_menus_list_index overflowed, then add the rest of the new menus
|
||||||
|
elif current_menus_list_index >= len(current_menus):
|
||||||
|
for i in range(new_menus_list_index, len(new_menus)):
|
||||||
|
menus_rs.insert(
|
||||||
|
insert_line_index, f' {new_menus[i]} {{\n todo!()\n }},')
|
||||||
|
insert_line_index += 1
|
||||||
|
print('current_menus_list_index overflowed', current_menus_list_index,
|
||||||
|
new_menus_list_index, insert_line_index)
|
||||||
|
break
|
||||||
|
# if new_menus_list_index overflowed, then remove the rest of the current menus
|
||||||
|
elif new_menus_list_index >= len(new_menus):
|
||||||
|
for _ in range(current_menus_list_index, len(current_menus)):
|
||||||
|
while not menus_rs[insert_line_index].strip().startswith('}'):
|
||||||
|
menus_rs.pop(insert_line_index)
|
||||||
|
menus_rs.pop(insert_line_index)
|
||||||
|
# current_menus_list_index += 1
|
||||||
|
print('new_menus_list_index overflowed', current_menus_list_index,
|
||||||
|
new_menus_list_index, insert_line_index)
|
||||||
|
break
|
||||||
|
with open(inventory_menus_dir, 'w') as f:
|
||||||
|
f.write('\n'.join(menus_rs))
|
||||||
|
|
||||||
|
|
||||||
|
def menu_name_to_enum_name(menu_name: str) -> str:
|
||||||
|
return to_camel_case(menu_name.split(':')[-1])
|
|
@ -16,12 +16,16 @@ def generate_registries(registries: dict):
|
||||||
# Stone => "minecraft:stone"
|
# Stone => "minecraft:stone"
|
||||||
# });
|
# });
|
||||||
|
|
||||||
|
registry_name = registry_name.split(':')[1]
|
||||||
|
|
||||||
if registry_name.endswith('_type'):
|
if registry_name.endswith('_type'):
|
||||||
# change _type to _kind because that's Rustier (and because _type
|
# change _type to _kind because that's Rustier (and because _type
|
||||||
# is a reserved keyword)
|
# is a reserved keyword)
|
||||||
registry_name = registry_name[:-5] + '_kind'
|
registry_name = registry_name[:-5] + '_kind'
|
||||||
|
elif registry_name in {'menu'}:
|
||||||
|
registry_name += '_kind'
|
||||||
|
|
||||||
registry_struct_name = to_camel_case(registry_name.split(':')[1])
|
registry_struct_name = to_camel_case(registry_name)
|
||||||
|
|
||||||
registry_code = []
|
registry_code = []
|
||||||
registry_code.append(f'enum {registry_struct_name} {{')
|
registry_code.append(f'enum {registry_struct_name} {{')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from lib.code.packet import fix_state
|
from lib.code.packet import fix_state
|
||||||
from lib.utils import PacketIdentifier, group_packets
|
from lib.utils import PacketIdentifier, group_packets
|
||||||
|
import lib.code.inventory
|
||||||
import lib.code.language
|
import lib.code.language
|
||||||
import lib.code.registry
|
import lib.code.registry
|
||||||
import lib.code.version
|
import lib.code.version
|
||||||
|
@ -134,6 +135,7 @@ lib.code.language.write_language(language)
|
||||||
print('Generating registries...')
|
print('Generating registries...')
|
||||||
registries = lib.extract.get_registries_report(new_version_id)
|
registries = lib.extract.get_registries_report(new_version_id)
|
||||||
lib.code.registry.generate_registries(registries)
|
lib.code.registry.generate_registries(registries)
|
||||||
|
lib.code.inventory.update_menus(registries['minecraft:menu']['entries'])
|
||||||
|
|
||||||
print('Generating entity metadata...')
|
print('Generating entity metadata...')
|
||||||
burger_entities_data = new_burger_data[0]['entities']
|
burger_entities_data = new_burger_data[0]['entities']
|
||||||
|
|
Loading…
Add table
Reference in a new issue