mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Mining (#95)
* more mining stuff
* initialize azalea-tags crate
* more mining stuff 2
* mining in ecs
* well technically mining works but
no codegen for how long it takes to mine each block yet
* rename downloads to __cache__
it was bothering me since it's not *just* downloads
* codegen block behavior
* fix not sending packet to finish breaking block
* mining animation 🎉
* clippy
* cleanup, move Client::mine into a client extension
* add azalea/src/mining.rs
---------
Co-authored-by: mat <git@matdoes.dev>
This commit is contained in:
parent
d1afd02aa8
commit
7405427199
76 changed files with 7523 additions and 1603 deletions
129
Cargo.lock
generated
129
Cargo.lock
generated
|
@ -87,9 +87,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
|
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"event-listener",
|
"event-listener",
|
||||||
|
@ -98,9 +98,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b0122885821398cc923ece939e24d1056a2384ee719432397fa9db87230ff11"
|
checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -178,6 +178,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-client",
|
"azalea-client",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-entity",
|
||||||
"azalea-inventory",
|
"azalea-inventory",
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-protocol",
|
"azalea-protocol",
|
||||||
|
@ -293,6 +294,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-crypto",
|
"azalea-crypto",
|
||||||
|
"azalea-entity",
|
||||||
"azalea-inventory",
|
"azalea-inventory",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
|
@ -349,6 +351,29 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "azalea-entity"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"azalea-block",
|
||||||
|
"azalea-buf",
|
||||||
|
"azalea-chat",
|
||||||
|
"azalea-core",
|
||||||
|
"azalea-inventory",
|
||||||
|
"azalea-nbt",
|
||||||
|
"azalea-registry",
|
||||||
|
"azalea-world",
|
||||||
|
"bevy_app",
|
||||||
|
"bevy_ecs",
|
||||||
|
"derive_more",
|
||||||
|
"enum-as-inner 0.6.0",
|
||||||
|
"log",
|
||||||
|
"nohash-hasher",
|
||||||
|
"parking_lot",
|
||||||
|
"thiserror",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "azalea-inventory"
|
name = "azalea-inventory"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -401,6 +426,7 @@ version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azalea-block",
|
"azalea-block",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-entity",
|
||||||
"azalea-inventory",
|
"azalea-inventory",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
"azalea-world",
|
"azalea-world",
|
||||||
|
@ -426,6 +452,7 @@ dependencies = [
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-crypto",
|
"azalea-crypto",
|
||||||
|
"azalea-entity",
|
||||||
"azalea-inventory",
|
"azalea-inventory",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"azalea-protocol-macros",
|
"azalea-protocol-macros",
|
||||||
|
@ -465,6 +492,7 @@ version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"azalea-buf",
|
"azalea-buf",
|
||||||
"azalea-registry-macros",
|
"azalea-registry-macros",
|
||||||
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -933,9 +961,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.2"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
|
@ -945,9 +973,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
|
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -1058,9 +1086,9 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17"
|
checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid",
|
"const-oid",
|
||||||
"pem-rfc7468",
|
"pem-rfc7468",
|
||||||
|
@ -1151,15 +1179,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.0"
|
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 = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erased-serde"
|
name = "erased-serde"
|
||||||
version = "0.3.25"
|
version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
|
checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -1531,10 +1559,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.24.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
|
checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
"rustls",
|
"rustls",
|
||||||
|
@ -1612,9 +1641,9 @@ checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.8"
|
version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
|
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
@ -1632,9 +1661,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.6"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
@ -1783,9 +1812,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-complex",
|
"num-complex",
|
||||||
|
@ -1808,9 +1837,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.2"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905"
|
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -2153,7 +2182,7 @@ dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata 0.3.2",
|
"regex-automata 0.3.2",
|
||||||
"regex-syntax 0.7.3",
|
"regex-syntax 0.7.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2173,7 +2202,7 @@ checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.3",
|
"regex-syntax 0.7.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2184,9 +2213,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
|
@ -2299,9 +2328,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.1"
|
version = "0.38.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3"
|
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.3.3",
|
"bitflags 2.3.3",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -2312,9 +2341,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.21.2"
|
version = "0.21.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f"
|
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
|
@ -2333,9 +2362,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.100.1"
|
version = "0.101.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
|
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
@ -2343,15 +2372,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.12"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
|
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.13"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
|
@ -2386,27 +2415,27 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.170"
|
version = "1.0.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a56657f512baabca8f840542f9ca8152aecf182c473c26e46e58d6aab4f6e439"
|
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_bytes"
|
name = "serde_bytes"
|
||||||
version = "0.11.10"
|
version = "0.11.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3c5113243e4a3a1c96587342d067f3e6b0f50790b6cf40d2868eb647a3eef0e"
|
checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.170"
|
version = "1.0.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77d477848e6b23adba0db397777d5aad864555bc17fd9c89abb3b8009788b7b8"
|
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2415,9 +2444,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.100"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -2723,9 +2752,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.11"
|
version = "0.19.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
|
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
@ -3162,9 +3191,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.4.7"
|
version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,6 +16,7 @@ members = [
|
||||||
"azalea-physics",
|
"azalea-physics",
|
||||||
"azalea-registry",
|
"azalea-registry",
|
||||||
"azalea-inventory",
|
"azalea-inventory",
|
||||||
|
"azalea-entity",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
let mut from_registry_block_to_blockstate_match = quote! {};
|
let mut from_registry_block_to_blockstate_match = quote! {};
|
||||||
let mut from_registry_block_to_blockstates_match = quote! {};
|
let mut from_registry_block_to_blockstates_match = quote! {};
|
||||||
|
|
||||||
|
// a list of block state ids that are waterlogged
|
||||||
|
let mut waterlogged_state_ids: Vec<u32> = Vec::new();
|
||||||
|
|
||||||
for block in &input.block_definitions.blocks {
|
for block in &input.block_definitions.blocks {
|
||||||
let block_property_names = &block
|
let block_property_names = &block
|
||||||
.properties_and_defaults
|
.properties_and_defaults
|
||||||
|
@ -448,15 +451,21 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
is_default = false;
|
is_default = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let property_type = if property.is_enum {
|
let property_value = if property.is_enum {
|
||||||
quote! {properties::#property_struct_name_ident::#variant}
|
quote! {properties::#property_struct_name_ident::#variant}
|
||||||
} else {
|
} else {
|
||||||
quote! {#variant}
|
quote! {#variant}
|
||||||
};
|
};
|
||||||
|
|
||||||
from_block_to_state_combination_match_inner.extend(quote! {
|
from_block_to_state_combination_match_inner.extend(quote! {
|
||||||
#property_name: #property_type,
|
#property_name: #property_value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if "waterlogged" is a property and it's true for this state then add it to
|
||||||
|
// waterlogged_state_ids
|
||||||
|
if property_name == "waterlogged" && property_value.to_string() == "true" {
|
||||||
|
waterlogged_state_ids.push(state_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from_block_to_state_match_inner.extend(quote! {
|
from_block_to_state_match_inner.extend(quote! {
|
||||||
|
@ -473,7 +482,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(default_state_id) = default_state_id else {
|
let Some(default_state_id) = default_state_id else {
|
||||||
let defaults = properties_with_name.iter().map(|p| if let TokenTree::Ident(i) = p.default.clone().into_iter().last().unwrap() { i.to_string() } else { panic!() }).collect::<Vec<_>>();
|
let defaults = properties_with_name
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
if let TokenTree::Ident(i) = p.default.clone().into_iter().last().unwrap() {
|
||||||
|
i.to_string()
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
panic!("Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}")
|
panic!("Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -570,6 +588,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
fn as_block_state(&self) -> BlockState {
|
fn as_block_state(&self) -> BlockState {
|
||||||
#from_block_to_state_match
|
#from_block_to_state_match
|
||||||
}
|
}
|
||||||
|
fn as_registry_block(&self) -> azalea_registry::Block {
|
||||||
|
azalea_registry::Block::#block_name_pascal_case
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<#block_struct_name> for BlockState {
|
impl From<#block_struct_name> for BlockState {
|
||||||
|
@ -590,6 +611,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
block_structs.extend(block_struct);
|
block_structs.extend(block_struct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let waterlogged_state_ids_match = quote! { #(#waterlogged_state_ids)|* };
|
||||||
|
|
||||||
let last_state_id = state_id - 1;
|
let last_state_id = state_id - 1;
|
||||||
let mut generated = quote! {
|
let mut generated = quote! {
|
||||||
impl BlockState {
|
impl BlockState {
|
||||||
|
@ -598,6 +621,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||||
pub fn max_state() -> u32 {
|
pub fn max_state() -> u32 {
|
||||||
#last_state_id
|
#last_state_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the given block state is waterlogged. This checks for
|
||||||
|
/// the `waterlogged` field, so it'll return false for water.
|
||||||
|
pub fn waterlogged(&self) -> bool {
|
||||||
|
matches!(self.id, #waterlogged_state_ids_match)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod properties {
|
pub mod properties {
|
||||||
|
|
|
@ -1,35 +1,55 @@
|
||||||
pub struct BlockBehavior {
|
pub struct BlockBehavior {
|
||||||
pub has_collision: bool,
|
|
||||||
pub friction: f32,
|
pub friction: f32,
|
||||||
pub jump_factor: f32,
|
pub jump_factor: f32,
|
||||||
|
pub destroy_time: f32,
|
||||||
|
pub explosion_resistance: f32,
|
||||||
|
pub requires_correct_tool_for_drops: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlockBehavior {
|
impl Default for BlockBehavior {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
has_collision: true,
|
|
||||||
friction: 0.6,
|
friction: 0.6,
|
||||||
jump_factor: 1.0,
|
jump_factor: 1.0,
|
||||||
|
destroy_time: 0.,
|
||||||
|
explosion_resistance: 0.,
|
||||||
|
requires_correct_tool_for_drops: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockBehavior {
|
impl BlockBehavior {
|
||||||
#[inline]
|
pub fn new() -> Self {
|
||||||
pub fn no_collision(mut self) -> Self {
|
Self::default()
|
||||||
self.has_collision = false;
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn friction(mut self, friction: f32) -> Self {
|
pub fn friction(mut self, friction: f32) -> Self {
|
||||||
self.friction = friction;
|
self.friction = friction;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn jump_factor(mut self, jump_factor: f32) -> Self {
|
pub fn jump_factor(mut self, jump_factor: f32) -> Self {
|
||||||
self.jump_factor = jump_factor;
|
self.jump_factor = jump_factor;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn destroy_time(mut self, destroy_time: f32) -> Self {
|
||||||
|
self.destroy_time = destroy_time;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn explosion_resistance(mut self, explosion_resistance: f32) -> Self {
|
||||||
|
self.explosion_resistance = f32::max(0., explosion_resistance);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strength(self, destroy_time: f32, explosion_resistance: f32) -> Self {
|
||||||
|
self.destroy_time(destroy_time)
|
||||||
|
.explosion_resistance(explosion_resistance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requires_correct_tool_for_drops(mut self) -> Self {
|
||||||
|
self.requires_correct_tool_for_drops = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,6 +24,9 @@ pub trait Block: Debug + Any {
|
||||||
/// Convert the block to a block state. This is lossless, as the block
|
/// Convert the block to a block state. This is lossless, as the block
|
||||||
/// contains all the state data.
|
/// contains all the state data.
|
||||||
fn as_block_state(&self) -> BlockState;
|
fn as_block_state(&self) -> BlockState;
|
||||||
|
/// Convert the block to an [`azalea_registry::Block`]. This is lossy, as
|
||||||
|
/// `azalea_registry::Block` doesn't contain any state data.
|
||||||
|
fn as_registry_block(&self) -> azalea_registry::Block;
|
||||||
}
|
}
|
||||||
impl dyn Block {
|
impl dyn Block {
|
||||||
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
|
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
|
||||||
|
@ -45,19 +48,14 @@ pub struct BlockState {
|
||||||
impl BlockState {
|
impl BlockState {
|
||||||
pub const AIR: BlockState = BlockState { id: 0 };
|
pub const AIR: BlockState = BlockState { id: 0 };
|
||||||
|
|
||||||
/// Transmutes a u32 to a block state.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// The `state_id` should be a valid block state.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn from_u32_unchecked(state_id: u32) -> Self {
|
|
||||||
BlockState { id: state_id }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_valid_state(state_id: u32) -> bool {
|
pub fn is_valid_state(state_id: u32) -> bool {
|
||||||
state_id <= Self::max_state()
|
state_id <= Self::max_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_air(&self) -> bool {
|
||||||
|
self == &Self::AIR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for BlockState {
|
impl TryFrom<u32> for BlockState {
|
||||||
|
@ -66,7 +64,7 @@ impl TryFrom<u32> for BlockState {
|
||||||
/// Safely converts a state id to a block state.
|
/// Safely converts a state id to a block state.
|
||||||
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
||||||
if Self::is_valid_state(state_id) {
|
if Self::is_valid_state(state_id) {
|
||||||
Ok(unsafe { Self::from_u32_unchecked(state_id) })
|
Ok(BlockState { id: state_id })
|
||||||
} else {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
@ -98,6 +96,68 @@ impl std::fmt::Debug for BlockState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FluidState {
|
||||||
|
pub fluid: azalea_registry::Fluid,
|
||||||
|
pub height: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FluidState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
fluid: azalea_registry::Fluid::Empty,
|
||||||
|
height: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockState> for FluidState {
|
||||||
|
fn from(state: BlockState) -> Self {
|
||||||
|
if state.waterlogged() {
|
||||||
|
Self {
|
||||||
|
fluid: azalea_registry::Fluid::Water,
|
||||||
|
height: 15,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let block = Box::<dyn Block>::from(state);
|
||||||
|
if let Some(water) = block.downcast_ref::<crate::blocks::Water>() {
|
||||||
|
Self {
|
||||||
|
fluid: azalea_registry::Fluid::Water,
|
||||||
|
height: water.level as u8,
|
||||||
|
}
|
||||||
|
} else if let Some(lava) = block.downcast_ref::<crate::blocks::Lava>() {
|
||||||
|
Self {
|
||||||
|
fluid: azalea_registry::Fluid::Lava,
|
||||||
|
height: lava.level as u8,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
fluid: azalea_registry::Fluid::Empty,
|
||||||
|
height: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FluidState> for BlockState {
|
||||||
|
fn from(state: FluidState) -> Self {
|
||||||
|
match state.fluid {
|
||||||
|
azalea_registry::Fluid::Empty => BlockState::AIR,
|
||||||
|
azalea_registry::Fluid::Water | azalea_registry::Fluid::FlowingWater => {
|
||||||
|
BlockState::from(crate::blocks::Water {
|
||||||
|
level: crate::properties::WaterLevel::from(state.height as u32),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
azalea_registry::Fluid::Lava | azalea_registry::Fluid::FlowingLava => {
|
||||||
|
BlockState::from(crate::blocks::Lava {
|
||||||
|
level: crate::properties::LavaLevel::from(state.height as u32),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -96,17 +96,10 @@ fn read_utf_with_len(buf: &mut Cursor<&[u8]>, max_length: u32) -> Result<String,
|
||||||
/// number of bytes read
|
/// number of bytes read
|
||||||
// pub async fn read_varint_async(
|
// pub async fn read_varint_async(
|
||||||
// reader: &mut (dyn AsyncRead + Unpin + Send),
|
// reader: &mut (dyn AsyncRead + Unpin + Send),
|
||||||
// ) -> Result<i32, BufReadError> {
|
// ) -> Result<i32, BufReadError> { let mut buffer = [0]; let mut ans = 0; for i
|
||||||
// let mut buffer = [0];
|
// in 0..5 { reader.read_exact(&mut buffer).await?; ans |= ((buffer[0] &
|
||||||
// let mut ans = 0;
|
// 0b0111_1111) as i32) << (7 * i); if buffer[0] & 0b1000_0000 == 0 { break; }
|
||||||
// for i in 0..5 {
|
// } Ok(ans)
|
||||||
// reader.read_exact(&mut buffer).await?;
|
|
||||||
// ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i);
|
|
||||||
// if buffer[0] & 0b1000_0000 == 0 {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Ok(ans)
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub trait McBufReadable
|
pub trait McBufReadable
|
||||||
|
|
|
@ -38,6 +38,7 @@ regex = "1.9.1"
|
||||||
thiserror = "^1.0.43"
|
thiserror = "^1.0.43"
|
||||||
tokio = { version = "^1.29.1", features = ["sync"] }
|
tokio = { version = "^1.29.1", features = ["sync"] }
|
||||||
uuid = "^1.4.0"
|
uuid = "^1.4.0"
|
||||||
|
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["log"]
|
default = ["log"]
|
||||||
|
|
|
@ -187,7 +187,10 @@ impl Account {
|
||||||
|
|
||||||
*access_token_mutex.lock() = new_access_token;
|
*access_token_mutex.lock() = new_access_token;
|
||||||
let AccountOpts::MicrosoftWithAccessToken { msa: new_msa } =
|
let AccountOpts::MicrosoftWithAccessToken { msa: new_msa } =
|
||||||
new_account.account_opts else { unreachable!() };
|
new_account.account_opts
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
*msa.lock() = new_msa.lock().clone();
|
*msa.lock() = new_msa.lock().clone();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
mining::{self, MinePlugin},
|
||||||
movement::{LastSentLookDirection, 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,
|
||||||
|
@ -19,6 +20,7 @@ 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_core::Vec3;
|
||||||
|
use azalea_entity::{EntityPlugin, EntityUpdateSet, Local, Position};
|
||||||
use azalea_physics::{PhysicsPlugin, PhysicsSet};
|
use azalea_physics::{PhysicsPlugin, PhysicsSet};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
connect::{Connection, ConnectionError},
|
connect::{Connection, ConnectionError},
|
||||||
|
@ -41,10 +43,7 @@ use azalea_protocol::{
|
||||||
},
|
},
|
||||||
resolver, ServerAddress,
|
resolver, ServerAddress,
|
||||||
};
|
};
|
||||||
use azalea_world::{
|
use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance};
|
||||||
entity::{EntityPlugin, EntityUpdateSet, Local, Position, WorldName},
|
|
||||||
Instance, InstanceContainer, PartialInstance,
|
|
||||||
};
|
|
||||||
use bevy_app::{App, FixedUpdate, Main, Plugin, PluginGroup, PluginGroupBuilder, Update};
|
use bevy_app::{App, FixedUpdate, Main, Plugin, PluginGroup, PluginGroupBuilder, Update};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
|
@ -132,6 +131,10 @@ impl From<ClientboundPlayerAbilitiesPacket> for PlayerAbilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Level must be 0..=4
|
||||||
|
#[derive(Component, Clone, Default, Deref, DerefMut)]
|
||||||
|
pub struct PermissionLevel(pub u8);
|
||||||
|
|
||||||
/// A component that contains a map of player UUIDs to their information in the
|
/// A component that contains a map of player UUIDs to their information in the
|
||||||
/// tab list.
|
/// tab list.
|
||||||
///
|
///
|
||||||
|
@ -301,6 +304,8 @@ impl Client {
|
||||||
current_sequence_number: CurrentSequenceNumber::default(),
|
current_sequence_number: CurrentSequenceNumber::default(),
|
||||||
last_sent_direction: LastSentLookDirection::default(),
|
last_sent_direction: LastSentLookDirection::default(),
|
||||||
abilities: PlayerAbilities::default(),
|
abilities: PlayerAbilities::default(),
|
||||||
|
permission_level: PermissionLevel::default(),
|
||||||
|
mining: mining::MineBundle::default(),
|
||||||
_local: Local,
|
_local: Local,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -466,9 +471,9 @@ impl Client {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use azalea_world::entity::WorldName;
|
/// # use azalea_world::InstanceName;
|
||||||
/// # fn example(client: &azalea_client::Client) {
|
/// # fn example(client: &azalea_client::Client) {
|
||||||
/// let world_name = client.component::<WorldName>();
|
/// let world_name = client.component::<InstanceName>();
|
||||||
/// # }
|
/// # }
|
||||||
pub fn component<T: Component + Clone>(&self) -> T {
|
pub fn component<T: Component + Clone>(&self) -> T {
|
||||||
self.query::<&T>(&mut self.ecs.lock()).clone()
|
self.query::<&T>(&mut self.ecs.lock()).clone()
|
||||||
|
@ -486,7 +491,7 @@ impl Client {
|
||||||
/// If the client using a shared world, then the shared world will be a
|
/// If the client using a shared world, then the shared world will be a
|
||||||
/// superset of the client's world.
|
/// superset of the client's world.
|
||||||
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::<InstanceName>();
|
||||||
let ecs = self.ecs.lock();
|
let ecs = self.ecs.lock();
|
||||||
let instance_container = ecs.resource::<InstanceContainer>();
|
let instance_container = ecs.resource::<InstanceContainer>();
|
||||||
instance_container.get(&world_name).unwrap()
|
instance_container.get(&world_name).unwrap()
|
||||||
|
@ -495,7 +500,7 @@ impl Client {
|
||||||
/// Returns whether we have a received the login packet yet.
|
/// Returns whether we have a received the login packet yet.
|
||||||
pub fn logged_in(&self) -> bool {
|
pub fn logged_in(&self) -> bool {
|
||||||
// the login packet tells us the world name
|
// the login packet tells us the world name
|
||||||
self.query::<Option<&WorldName>>(&mut self.ecs.lock())
|
self.query::<Option<&InstanceName>>(&mut self.ecs.lock())
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,6 +565,10 @@ pub struct JoinedClientBundle {
|
||||||
pub current_sequence_number: CurrentSequenceNumber,
|
pub current_sequence_number: CurrentSequenceNumber,
|
||||||
pub last_sent_direction: LastSentLookDirection,
|
pub last_sent_direction: LastSentLookDirection,
|
||||||
pub abilities: PlayerAbilities,
|
pub abilities: PlayerAbilities,
|
||||||
|
pub permission_level: PermissionLevel,
|
||||||
|
|
||||||
|
pub mining: mining::MineBundle,
|
||||||
|
|
||||||
pub _local: Local,
|
pub _local: Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,7 +669,7 @@ pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::UnboundedSender<(
|
||||||
#[derive(Resource, Deref)]
|
#[derive(Resource, Deref)]
|
||||||
pub struct TickBroadcast(broadcast::Sender<()>);
|
pub struct TickBroadcast(broadcast::Sender<()>);
|
||||||
|
|
||||||
fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
|
pub fn send_tick_broadcast(tick_broadcast: ResMut<TickBroadcast>) {
|
||||||
let _ = tick_broadcast.0.send(());
|
let _ = tick_broadcast.0.send(());
|
||||||
}
|
}
|
||||||
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
||||||
|
@ -706,6 +715,7 @@ impl PluginGroup for DefaultPlugins {
|
||||||
.add(PlayerMovePlugin)
|
.add(PlayerMovePlugin)
|
||||||
.add(InteractPlugin)
|
.add(InteractPlugin)
|
||||||
.add(RespawnPlugin)
|
.add(RespawnPlugin)
|
||||||
|
.add(MinePlugin)
|
||||||
.add(TickBroadcastPlugin);
|
.add(TickBroadcastPlugin);
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,10 +15,10 @@ impl Client {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// # use azalea_world::entity::WorldName;
|
/// # use azalea_world::InstanceName;
|
||||||
/// # fn example(mut client: azalea_client::Client) {
|
/// # fn example(mut client: azalea_client::Client) {
|
||||||
/// let is_logged_in = client
|
/// let is_logged_in = client
|
||||||
/// .query::<Option<&WorldName>>(&mut client.ecs.lock())
|
/// .query::<Option<&InstanceName>>(&mut client.ecs.lock())
|
||||||
/// .is_some();
|
/// .is_some();
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -39,7 +39,7 @@ impl Client {
|
||||||
/// ```
|
/// ```
|
||||||
/// use azalea_client::{Client, GameProfileComponent};
|
/// use azalea_client::{Client, GameProfileComponent};
|
||||||
/// use bevy_ecs::query::With;
|
/// use bevy_ecs::query::With;
|
||||||
/// use azalea_world::entity::{Position, metadata::Player};
|
/// use azalea_entity::{Position, metadata::Player};
|
||||||
///
|
///
|
||||||
/// # fn example(mut bot: Client, sender_name: String) {
|
/// # fn example(mut bot: Client, sender_name: String) {
|
||||||
/// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
/// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||||
use azalea_protocol::packets::game::{
|
use azalea_protocol::packets::game::{
|
||||||
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, ClientboundGamePacket,
|
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, ClientboundGamePacket,
|
||||||
};
|
};
|
||||||
use azalea_world::entity::MinecraftEntityId;
|
use azalea_world::MinecraftEntityId;
|
||||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
use bevy_ecs::{component::Component, event::EventReader, query::Added, system::Query};
|
use bevy_ecs::{component::Component, event::EventReader, query::Added, system::Query};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
|
use std::ops::AddAssign;
|
||||||
|
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
use azalea_core::{BlockHitResult, BlockPos, Direction, GameMode, Vec3};
|
||||||
|
use azalea_entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position};
|
||||||
use azalea_inventory::{ItemSlot, ItemSlotData};
|
use azalea_inventory::{ItemSlot, ItemSlotData};
|
||||||
use azalea_nbt::NbtList;
|
use azalea_nbt::NbtList;
|
||||||
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||||
use azalea_protocol::packets::game::{
|
use azalea_protocol::packets::game::{
|
||||||
serverbound_interact_packet::InteractionHand,
|
serverbound_interact_packet::InteractionHand,
|
||||||
|
serverbound_swing_packet::ServerboundSwingPacket,
|
||||||
serverbound_use_item_on_packet::{BlockHit, ServerboundUseItemOnPacket},
|
serverbound_use_item_on_packet::{BlockHit, ServerboundUseItemOnPacket},
|
||||||
};
|
};
|
||||||
use azalea_world::{
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position, WorldName},
|
|
||||||
Instance, InstanceContainer,
|
|
||||||
};
|
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::{Event, EventReader, EventWriter},
|
||||||
prelude::Event,
|
|
||||||
schedule::IntoSystemConfigs,
|
schedule::IntoSystemConfigs,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
};
|
};
|
||||||
|
@ -24,8 +24,9 @@ use derive_more::{Deref, DerefMut};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
client::{PermissionLevel, PlayerAbilities},
|
||||||
inventory::InventoryComponent,
|
inventory::InventoryComponent,
|
||||||
local_player::{handle_send_packet_event, LocalGameMode},
|
local_player::{handle_send_packet_event, LocalGameMode, SendPacketEvent},
|
||||||
Client, LocalPlayer,
|
Client, LocalPlayer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,15 +34,18 @@ use crate::{
|
||||||
pub struct InteractPlugin;
|
pub struct InteractPlugin;
|
||||||
impl Plugin for InteractPlugin {
|
impl Plugin for InteractPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<BlockInteractEvent>().add_systems(
|
app.add_event::<BlockInteractEvent>()
|
||||||
Update,
|
.add_event::<SwingArmEvent>()
|
||||||
(
|
.add_systems(
|
||||||
update_hit_result_component.after(clamp_look_direction),
|
Update,
|
||||||
handle_block_interact_event,
|
(
|
||||||
)
|
update_hit_result_component.after(clamp_look_direction),
|
||||||
.before(handle_send_packet_event)
|
handle_block_interact_event,
|
||||||
.chain(),
|
handle_swing_arm_event,
|
||||||
);
|
)
|
||||||
|
.before(handle_send_packet_event)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,9 +77,15 @@ pub struct BlockInteractEvent {
|
||||||
|
|
||||||
/// A component that contains the number of changes this client has made to
|
/// A component that contains the number of changes this client has made to
|
||||||
/// blocks.
|
/// blocks.
|
||||||
#[derive(Component, Copy, Clone, Debug, Default, Deref, DerefMut)]
|
#[derive(Component, Copy, Clone, Debug, Default, Deref)]
|
||||||
pub struct CurrentSequenceNumber(u32);
|
pub struct CurrentSequenceNumber(u32);
|
||||||
|
|
||||||
|
impl AddAssign<u32> for CurrentSequenceNumber {
|
||||||
|
fn add_assign(&mut self, rhs: u32) {
|
||||||
|
self.0 += rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component that contains the block that the player is currently looking at.
|
/// A component that contains the block that the player is currently looking at.
|
||||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||||
pub struct HitResultComponent(BlockHitResult);
|
pub struct HitResultComponent(BlockHitResult);
|
||||||
|
@ -89,14 +99,15 @@ pub fn handle_block_interact_event(
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
let Ok((local_player, mut sequence_number, hit_result)) = query.get_mut(event.entity) else {
|
let Ok((local_player, mut sequence_number, hit_result)) = query.get_mut(event.entity)
|
||||||
|
else {
|
||||||
warn!("Sent BlockInteractEvent for entity that isn't LocalPlayer");
|
warn!("Sent BlockInteractEvent for entity that isn't LocalPlayer");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: check to make sure we're within the world border
|
// TODO: check to make sure we're within the world border
|
||||||
|
|
||||||
**sequence_number += 1;
|
*sequence_number += 1;
|
||||||
|
|
||||||
// minecraft also does the interaction client-side (so it looks like clicking a
|
// minecraft also does the interaction client-side (so it looks like clicking a
|
||||||
// button is instant) but we don't really need that
|
// button is instant) but we don't really need that
|
||||||
|
@ -143,7 +154,7 @@ fn update_hit_result_component(
|
||||||
&Position,
|
&Position,
|
||||||
&EyeHeight,
|
&EyeHeight,
|
||||||
&LookDirection,
|
&LookDirection,
|
||||||
&WorldName,
|
&InstanceName,
|
||||||
)>,
|
)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
|
@ -246,10 +257,11 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode(
|
||||||
.nbt
|
.nbt
|
||||||
.as_compound()
|
.as_compound()
|
||||||
.and_then(|nbt| nbt.get("tag").and_then(|nbt| nbt.as_compound()))
|
.and_then(|nbt| nbt.get("tag").and_then(|nbt| nbt.as_compound()))
|
||||||
.and_then(|nbt| nbt.get("CanDestroy").and_then(|nbt| nbt.as_list())) else {
|
.and_then(|nbt| nbt.get("CanDestroy").and_then(|nbt| nbt.as_list()))
|
||||||
// no CanDestroy tag
|
else {
|
||||||
return false;
|
// no CanDestroy tag
|
||||||
};
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
let NbtList::String(_can_destroy) = can_destroy else {
|
let NbtList::String(_can_destroy) = can_destroy else {
|
||||||
// CanDestroy tag must be a list of strings
|
// CanDestroy tag must be a list of strings
|
||||||
|
@ -265,3 +277,31 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode(
|
||||||
|
|
||||||
// true
|
// true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_use_game_master_blocks(
|
||||||
|
abilities: &PlayerAbilities,
|
||||||
|
permission_level: &PermissionLevel,
|
||||||
|
) -> bool {
|
||||||
|
abilities.instant_break && **permission_level >= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swing your arm. This is purely a visual effect and won't interact with
|
||||||
|
/// anything in the world.
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct SwingArmEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
}
|
||||||
|
fn handle_swing_arm_event(
|
||||||
|
mut events: EventReader<SwingArmEvent>,
|
||||||
|
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
packet: ServerboundSwingPacket {
|
||||||
|
hand: InteractionHand::MainHand,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ pub struct InventoryComponent {
|
||||||
/// the scroll wheel.
|
/// the scroll wheel.
|
||||||
pub selected_hotbar_slot: u8,
|
pub selected_hotbar_slot: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InventoryComponent {
|
impl InventoryComponent {
|
||||||
/// Returns a reference to the currently active menu. If a container is open
|
/// Returns a reference to the currently active menu. If a container is open
|
||||||
/// it'll return [`Self::container_menu`], otherwise
|
/// it'll return [`Self::container_menu`], otherwise
|
||||||
|
@ -220,10 +221,10 @@ impl InventoryComponent {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Some(&next_slot) = quick_craft_slots_iter.next() else {
|
let Some(&next_slot) = quick_craft_slots_iter.next() else {
|
||||||
carried.count = carried_count;
|
carried.count = carried_count;
|
||||||
self.carried = ItemSlot::Present(carried);
|
self.carried = ItemSlot::Present(carried);
|
||||||
return self.reset_quick_craft();
|
return self.reset_quick_craft();
|
||||||
};
|
};
|
||||||
|
|
||||||
slot = self.menu().slot(next_slot as usize).unwrap();
|
slot = self.menu().slot(next_slot as usize).unwrap();
|
||||||
slot_index = next_slot;
|
slot_index = next_slot;
|
||||||
|
@ -244,8 +245,8 @@ impl InventoryComponent {
|
||||||
|
|
||||||
// get the ItemSlotData for the slot
|
// get the ItemSlotData for the slot
|
||||||
let ItemSlot::Present(slot) = slot else {
|
let ItemSlot::Present(slot) = slot else {
|
||||||
unreachable!("the loop above requires the slot to be present to break")
|
unreachable!("the loop above requires the slot to be present to break")
|
||||||
};
|
};
|
||||||
|
|
||||||
// if self.can_drag_to(slot) {
|
// if self.can_drag_to(slot) {
|
||||||
let mut new_carried = carried.clone();
|
let mut new_carried = carried.clone();
|
||||||
|
@ -480,8 +481,8 @@ impl InventoryComponent {
|
||||||
// now extend the carried item
|
// now extend the carried item
|
||||||
let target_slot = &mut self.carried;
|
let target_slot = &mut self.carried;
|
||||||
let ItemSlot::Present(target_slot_item) = target_slot else {
|
let ItemSlot::Present(target_slot_item) = target_slot else {
|
||||||
unreachable!("target slot is not empty but is not present");
|
unreachable!("target slot is not empty but is not present");
|
||||||
};
|
};
|
||||||
target_slot_item.count += taken_item.count();
|
target_slot_item.count += taken_item.count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -512,13 +513,13 @@ fn can_item_quick_replace(
|
||||||
ignore_item_count: bool,
|
ignore_item_count: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let ItemSlot::Present(target_slot) = target_slot else {
|
let ItemSlot::Present(target_slot) = target_slot else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let ItemSlot::Present(item) = item else {
|
let ItemSlot::Present(item) = item else {
|
||||||
// i *think* this is what vanilla does
|
// i *think* this is what vanilla does
|
||||||
// not 100% sure lol probably doesn't matter though
|
// not 100% sure lol probably doesn't matter though
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !item.is_same_item_and_nbt(target_slot) {
|
if !item.is_same_item_and_nbt(target_slot) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -21,7 +21,7 @@ mod get_mc_dir;
|
||||||
pub mod interact;
|
pub mod interact;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
mod local_player;
|
mod local_player;
|
||||||
mod mining;
|
pub mod mining;
|
||||||
mod movement;
|
mod movement;
|
||||||
pub mod packet_handling;
|
pub mod packet_handling;
|
||||||
pub mod ping;
|
pub mod ping;
|
||||||
|
|
|
@ -2,11 +2,9 @@ use std::{io, sync::Arc};
|
||||||
|
|
||||||
use azalea_auth::game_profile::GameProfile;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
use azalea_core::{ChunkPos, GameMode};
|
use azalea_core::{ChunkPos, GameMode};
|
||||||
|
use azalea_entity::{Dead, Position};
|
||||||
use azalea_protocol::packets::game::ServerboundGamePacket;
|
use azalea_protocol::packets::game::ServerboundGamePacket;
|
||||||
use azalea_world::{
|
use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance};
|
||||||
entity::{self, Dead, WorldName},
|
|
||||||
Instance, InstanceContainer, PartialInstance,
|
|
||||||
};
|
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
@ -32,7 +30,7 @@ use crate::{
|
||||||
/// You can also use the [`Local`] marker component for queries if you're only
|
/// You can also use the [`Local`] marker component for queries if you're only
|
||||||
/// checking for a local player and don't need the contents of this component.
|
/// checking for a local player and don't need the contents of this component.
|
||||||
///
|
///
|
||||||
/// [`Local`]: azalea_world::entity::Local
|
/// [`Local`]: azalea_entity::Local
|
||||||
/// [`Client`]: crate::Client
|
/// [`Client`]: crate::Client
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct LocalPlayer {
|
pub struct LocalPlayer {
|
||||||
|
@ -135,7 +133,7 @@ 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, &WorldName, &entity::Position)>,
|
query: Query<(Entity, &InstanceName, &Position)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (entity, local_player, position) in &query {
|
for (entity, local_player, position) in &query {
|
||||||
|
|
|
@ -1,36 +1,543 @@
|
||||||
use azalea_core::BlockPos;
|
use azalea_block::{Block, BlockState, FluidState};
|
||||||
use bevy_app::{App, Plugin, Update};
|
use azalea_core::{BlockPos, Direction, GameMode};
|
||||||
|
use azalea_entity::{mining::get_mine_progress, FluidOnEyes, Physics};
|
||||||
|
use azalea_inventory::ItemSlot;
|
||||||
|
use azalea_protocol::packets::game::serverbound_player_action_packet::{
|
||||||
|
self, ServerboundPlayerActionPacket,
|
||||||
|
};
|
||||||
|
use azalea_world::{InstanceContainer, InstanceName};
|
||||||
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::Client;
|
use crate::{
|
||||||
|
client::{PermissionLevel, PlayerAbilities},
|
||||||
|
interact::{
|
||||||
|
can_use_game_master_blocks, check_is_interaction_restricted, CurrentSequenceNumber,
|
||||||
|
HitResultComponent, SwingArmEvent,
|
||||||
|
},
|
||||||
|
inventory::InventoryComponent,
|
||||||
|
local_player::{LocalGameMode, SendPacketEvent},
|
||||||
|
};
|
||||||
|
|
||||||
/// A plugin that allows clients to break blocks in the world.
|
/// A plugin that allows clients to break blocks in the world.
|
||||||
pub struct MinePlugin;
|
pub struct MinePlugin;
|
||||||
impl Plugin for MinePlugin {
|
impl Plugin for MinePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<StartMiningBlockEvent>()
|
app.add_event::<StartMiningBlockEvent>()
|
||||||
.add_systems(Update, handle_start_mining_block_event);
|
.add_event::<StartMiningBlockWithDirectionEvent>()
|
||||||
|
.add_event::<FinishMiningBlockEvent>()
|
||||||
|
.add_event::<StopMiningBlockEvent>()
|
||||||
|
.add_event::<MineBlockProgressEvent>()
|
||||||
|
.add_event::<AttackBlockEvent>()
|
||||||
|
.add_systems(FixedUpdate, continue_mining_block)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
handle_start_mining_block_event,
|
||||||
|
handle_start_mining_block_with_direction_event,
|
||||||
|
handle_finish_mining_block_event,
|
||||||
|
handle_stop_mining_block_event,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
/// Information about the block we're currently mining. This is only present if
|
||||||
/// Start mining a block.
|
/// we're currently mining a block.
|
||||||
pub fn start_mining_block(&self, position: BlockPos) {
|
#[derive(Component)]
|
||||||
self.ecs.lock().send_event(StartMiningBlockEvent {
|
pub struct Mining {
|
||||||
entity: self.entity,
|
pub pos: BlockPos,
|
||||||
position,
|
pub dir: Direction,
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start mining the block at the given position.
|
||||||
|
///
|
||||||
|
/// If we're looking at the block then the correct direction will be used,
|
||||||
|
/// otherwise it'll be [`Direction::Down`].
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
pub struct StartMiningBlockEvent {
|
pub struct StartMiningBlockEvent {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub position: BlockPos,
|
pub position: BlockPos,
|
||||||
}
|
}
|
||||||
|
fn handle_start_mining_block_event(
|
||||||
fn handle_start_mining_block_event(mut _events: EventReader<StartMiningBlockEvent>) {
|
mut events: EventReader<StartMiningBlockEvent>,
|
||||||
// for event in events.iter() {
|
mut start_mining_events: EventWriter<StartMiningBlockWithDirectionEvent>,
|
||||||
// //
|
mut query: Query<&HitResultComponent>,
|
||||||
// }
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let hit_result = query.get_mut(event.entity).unwrap();
|
||||||
|
let direction = if hit_result.block_pos == event.position {
|
||||||
|
// we're looking at the block
|
||||||
|
hit_result.direction
|
||||||
|
} else {
|
||||||
|
// we're not looking at the block, arbitrary direction
|
||||||
|
Direction::Down
|
||||||
|
};
|
||||||
|
start_mining_events.send(StartMiningBlockWithDirectionEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: event.position,
|
||||||
|
direction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct StartMiningBlockWithDirectionEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub position: BlockPos,
|
||||||
|
pub direction: Direction,
|
||||||
|
}
|
||||||
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||||
|
fn handle_start_mining_block_with_direction_event(
|
||||||
|
mut events: EventReader<StartMiningBlockWithDirectionEvent>,
|
||||||
|
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
|
||||||
|
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||||
|
mut attack_block_events: EventWriter<AttackBlockEvent>,
|
||||||
|
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
|
||||||
|
mut query: Query<(
|
||||||
|
&InstanceName,
|
||||||
|
&LocalGameMode,
|
||||||
|
&InventoryComponent,
|
||||||
|
&FluidOnEyes,
|
||||||
|
&Physics,
|
||||||
|
Option<&Mining>,
|
||||||
|
&mut CurrentSequenceNumber,
|
||||||
|
&mut MineDelay,
|
||||||
|
&mut MineProgress,
|
||||||
|
&mut MineTicks,
|
||||||
|
&mut MineItem,
|
||||||
|
&mut MineBlockPos,
|
||||||
|
)>,
|
||||||
|
instances: Res<InstanceContainer>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (
|
||||||
|
instance_name,
|
||||||
|
game_mode,
|
||||||
|
inventory,
|
||||||
|
fluid_on_eyes,
|
||||||
|
physics,
|
||||||
|
mining,
|
||||||
|
mut sequence_number,
|
||||||
|
mut mine_delay,
|
||||||
|
mut mine_progress,
|
||||||
|
mut mine_ticks,
|
||||||
|
mut current_mining_item,
|
||||||
|
mut current_mining_pos,
|
||||||
|
) = query.get_mut(event.entity).unwrap();
|
||||||
|
|
||||||
|
let instance_lock = instances.get(instance_name).unwrap();
|
||||||
|
let instance = instance_lock.read();
|
||||||
|
if check_is_interaction_restricted(
|
||||||
|
&instance,
|
||||||
|
&event.position,
|
||||||
|
&game_mode.current,
|
||||||
|
inventory,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO (when world border is implemented): vanilla ignores if the block
|
||||||
|
// is outside of the worldborder
|
||||||
|
|
||||||
|
if game_mode.current == GameMode::Creative {
|
||||||
|
*sequence_number += 1;
|
||||||
|
finish_mining_events.send(FinishMiningBlockEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: event.position,
|
||||||
|
});
|
||||||
|
**mine_delay = 5;
|
||||||
|
} else if mining.is_none()
|
||||||
|
|| !is_same_mining_target(
|
||||||
|
event.position,
|
||||||
|
inventory,
|
||||||
|
¤t_mining_pos,
|
||||||
|
¤t_mining_item,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if mining.is_some() {
|
||||||
|
// send a packet to stop mining since we just changed target
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
packet: ServerboundPlayerActionPacket {
|
||||||
|
action: serverbound_player_action_packet::Action::AbortDestroyBlock,
|
||||||
|
pos: current_mining_pos
|
||||||
|
.expect("IsMining is true so MineBlockPos must be present"),
|
||||||
|
direction: event.direction,
|
||||||
|
sequence: 0,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_block_state = instance
|
||||||
|
.get_block_state(&event.position)
|
||||||
|
.unwrap_or_default();
|
||||||
|
*sequence_number += 1;
|
||||||
|
let block_is_solid = !target_block_state.is_air();
|
||||||
|
if block_is_solid && **mine_progress == 0. {
|
||||||
|
// interact with the block (like note block left click) here
|
||||||
|
attack_block_events.send(AttackBlockEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: event.position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = Box::<dyn Block>::from(target_block_state);
|
||||||
|
|
||||||
|
let held_item = inventory.held_item();
|
||||||
|
|
||||||
|
if block_is_solid
|
||||||
|
&& get_mine_progress(
|
||||||
|
block.as_ref(),
|
||||||
|
held_item.kind(),
|
||||||
|
&inventory.inventory_menu,
|
||||||
|
fluid_on_eyes,
|
||||||
|
physics,
|
||||||
|
) >= 1.
|
||||||
|
{
|
||||||
|
// block was broken instantly
|
||||||
|
finish_mining_events.send(FinishMiningBlockEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: event.position,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
commands.entity(event.entity).insert(Mining {
|
||||||
|
pos: event.position,
|
||||||
|
dir: event.direction,
|
||||||
|
});
|
||||||
|
**current_mining_pos = Some(event.position);
|
||||||
|
**current_mining_item = held_item;
|
||||||
|
**mine_progress = 0.;
|
||||||
|
**mine_ticks = 0.;
|
||||||
|
mine_block_progress_events.send(MineBlockProgressEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: event.position,
|
||||||
|
destroy_stage: mine_progress.destroy_stage(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
packet: ServerboundPlayerActionPacket {
|
||||||
|
action: serverbound_player_action_packet::Action::StartDestroyBlock,
|
||||||
|
pos: event.position,
|
||||||
|
direction: event.direction,
|
||||||
|
sequence: **sequence_number,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct MineBlockProgressEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub position: BlockPos,
|
||||||
|
pub destroy_stage: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A player left clicked on a block, used for stuff like interacting with note
|
||||||
|
/// blocks.
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct AttackBlockEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub position: BlockPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the block and item are still the same as when we started
|
||||||
|
/// mining.
|
||||||
|
fn is_same_mining_target(
|
||||||
|
target_block: BlockPos,
|
||||||
|
inventory: &InventoryComponent,
|
||||||
|
current_mining_pos: &MineBlockPos,
|
||||||
|
current_mining_item: &MineItem,
|
||||||
|
) -> bool {
|
||||||
|
let held_item = inventory.held_item();
|
||||||
|
Some(target_block) == current_mining_pos.0 && held_item == current_mining_item.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component bundle for players that can mine blocks.
|
||||||
|
#[derive(Bundle, Default)]
|
||||||
|
pub struct MineBundle {
|
||||||
|
pub delay: MineDelay,
|
||||||
|
pub progress: MineProgress,
|
||||||
|
pub ticks: MineTicks,
|
||||||
|
pub mining_pos: MineBlockPos,
|
||||||
|
pub mine_item: MineItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that counts down until we start mining the next block.
|
||||||
|
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct MineDelay(pub u32);
|
||||||
|
|
||||||
|
/// A component that stores the progress of the current mining operation. This
|
||||||
|
/// is a value between 0 and 1.
|
||||||
|
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct MineProgress(pub f32);
|
||||||
|
|
||||||
|
impl MineProgress {
|
||||||
|
pub fn destroy_stage(&self) -> Option<u32> {
|
||||||
|
if self.0 > 0. {
|
||||||
|
Some((self.0 * 10.) as u32)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that stores the number of ticks that we've been mining the same
|
||||||
|
/// block for. This is a float even though it should only ever be a round
|
||||||
|
/// number.
|
||||||
|
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct MineTicks(pub f32);
|
||||||
|
|
||||||
|
/// A component that stores the position of the block we're currently mining.
|
||||||
|
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct MineBlockPos(pub Option<BlockPos>);
|
||||||
|
|
||||||
|
/// A component that contains the item we're currently using to mine. If we're
|
||||||
|
/// not mining anything, it'll be [`ItemSlot::Empty`].
|
||||||
|
#[derive(Component, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct MineItem(pub ItemSlot);
|
||||||
|
|
||||||
|
/// Sent when we completed mining a block.
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct FinishMiningBlockEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub position: BlockPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_finish_mining_block_event(
|
||||||
|
mut events: EventReader<FinishMiningBlockEvent>,
|
||||||
|
mut query: Query<(
|
||||||
|
&InstanceName,
|
||||||
|
&LocalGameMode,
|
||||||
|
&InventoryComponent,
|
||||||
|
&PlayerAbilities,
|
||||||
|
&PermissionLevel,
|
||||||
|
&mut CurrentSequenceNumber,
|
||||||
|
)>,
|
||||||
|
instances: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (instance_name, game_mode, inventory, abilities, permission_level, _sequence_number) =
|
||||||
|
query.get_mut(event.entity).unwrap();
|
||||||
|
let instance_lock = instances.get(instance_name).unwrap();
|
||||||
|
let instance = instance_lock.read();
|
||||||
|
if check_is_interaction_restricted(
|
||||||
|
&instance,
|
||||||
|
&event.position,
|
||||||
|
&game_mode.current,
|
||||||
|
inventory,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if game_mode.current == GameMode::Creative {
|
||||||
|
let held_item = inventory.held_item().kind();
|
||||||
|
if matches!(
|
||||||
|
held_item,
|
||||||
|
azalea_registry::Item::Trident | azalea_registry::Item::DebugStick
|
||||||
|
) || azalea_registry::tags::items::SWORDS.contains(&held_item)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(block_state) = instance.get_block_state(&event.position) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let registry_block = Box::<dyn Block>::from(block_state).as_registry_block();
|
||||||
|
if !can_use_game_master_blocks(abilities, permission_level)
|
||||||
|
&& matches!(
|
||||||
|
registry_block,
|
||||||
|
azalea_registry::Block::CommandBlock | azalea_registry::Block::StructureBlock
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if block_state == BlockState::AIR {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when we break a waterlogged block we want to keep the water there
|
||||||
|
let fluid_state = FluidState::from(block_state);
|
||||||
|
let block_state_for_fluid = BlockState::from(fluid_state);
|
||||||
|
instance.set_block_state(&event.position, block_state_for_fluid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abort mining a block.
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct StopMiningBlockEvent {
|
||||||
|
pub entity: Entity,
|
||||||
|
}
|
||||||
|
fn handle_stop_mining_block_event(
|
||||||
|
mut events: EventReader<StopMiningBlockEvent>,
|
||||||
|
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||||
|
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
|
||||||
|
mut query: Query<(&mut Mining, &MineBlockPos, &mut MineProgress)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
let (mut _mining, mine_block_pos, mut mine_progress) = query.get_mut(event.entity).unwrap();
|
||||||
|
|
||||||
|
let mine_block_pos =
|
||||||
|
mine_block_pos.expect("IsMining is true so MineBlockPos must be present");
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
packet: ServerboundPlayerActionPacket {
|
||||||
|
action: serverbound_player_action_packet::Action::AbortDestroyBlock,
|
||||||
|
pos: mine_block_pos,
|
||||||
|
direction: Direction::Down,
|
||||||
|
sequence: 0,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
commands.entity(event.entity).remove::<Mining>();
|
||||||
|
**mine_progress = 0.;
|
||||||
|
mine_block_progress_events.send(MineBlockProgressEvent {
|
||||||
|
entity: event.entity,
|
||||||
|
position: mine_block_pos,
|
||||||
|
destroy_stage: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||||
|
fn continue_mining_block(
|
||||||
|
mut query: Query<(
|
||||||
|
Entity,
|
||||||
|
&InstanceName,
|
||||||
|
&LocalGameMode,
|
||||||
|
&InventoryComponent,
|
||||||
|
&MineBlockPos,
|
||||||
|
&MineItem,
|
||||||
|
&FluidOnEyes,
|
||||||
|
&Physics,
|
||||||
|
&Mining,
|
||||||
|
&mut MineDelay,
|
||||||
|
&mut MineProgress,
|
||||||
|
&mut MineTicks,
|
||||||
|
&mut CurrentSequenceNumber,
|
||||||
|
)>,
|
||||||
|
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||||
|
mut mine_block_progress_events: EventWriter<MineBlockProgressEvent>,
|
||||||
|
mut finish_mining_events: EventWriter<FinishMiningBlockEvent>,
|
||||||
|
mut start_mining_events: EventWriter<StartMiningBlockWithDirectionEvent>,
|
||||||
|
mut swing_arm_events: EventWriter<SwingArmEvent>,
|
||||||
|
instances: Res<InstanceContainer>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
entity,
|
||||||
|
instance_name,
|
||||||
|
game_mode,
|
||||||
|
inventory,
|
||||||
|
current_mining_pos,
|
||||||
|
current_mining_item,
|
||||||
|
fluid_on_eyes,
|
||||||
|
physics,
|
||||||
|
mining,
|
||||||
|
mut mine_delay,
|
||||||
|
mut mine_progress,
|
||||||
|
mut mine_ticks,
|
||||||
|
mut sequence_number,
|
||||||
|
) in query.iter_mut()
|
||||||
|
{
|
||||||
|
if **mine_delay > 0 {
|
||||||
|
**mine_delay -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if game_mode.current == GameMode::Creative {
|
||||||
|
// TODO: worldborder check
|
||||||
|
**mine_delay = 5;
|
||||||
|
finish_mining_events.send(FinishMiningBlockEvent {
|
||||||
|
entity,
|
||||||
|
position: mining.pos,
|
||||||
|
});
|
||||||
|
*sequence_number += 1;
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity,
|
||||||
|
packet: ServerboundPlayerActionPacket {
|
||||||
|
action: serverbound_player_action_packet::Action::StartDestroyBlock,
|
||||||
|
pos: mining.pos,
|
||||||
|
direction: mining.dir,
|
||||||
|
sequence: **sequence_number,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
swing_arm_events.send(SwingArmEvent { entity });
|
||||||
|
} else if is_same_mining_target(
|
||||||
|
mining.pos,
|
||||||
|
inventory,
|
||||||
|
current_mining_pos,
|
||||||
|
current_mining_item,
|
||||||
|
) {
|
||||||
|
let instance_lock = instances.get(instance_name).unwrap();
|
||||||
|
let instance = instance_lock.read();
|
||||||
|
let target_block_state = instance.get_block_state(&mining.pos).unwrap_or_default();
|
||||||
|
|
||||||
|
if target_block_state.is_air() {
|
||||||
|
commands.entity(entity).remove::<Mining>();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let block = Box::<dyn Block>::from(target_block_state);
|
||||||
|
**mine_progress += get_mine_progress(
|
||||||
|
block.as_ref(),
|
||||||
|
current_mining_item.kind(),
|
||||||
|
&inventory.inventory_menu,
|
||||||
|
fluid_on_eyes,
|
||||||
|
physics,
|
||||||
|
);
|
||||||
|
|
||||||
|
if **mine_ticks % 4. == 0. {
|
||||||
|
// vanilla makes a mining sound here
|
||||||
|
}
|
||||||
|
**mine_ticks += 1.;
|
||||||
|
|
||||||
|
if **mine_progress >= 1. {
|
||||||
|
commands.entity(entity).remove::<Mining>();
|
||||||
|
*sequence_number += 1;
|
||||||
|
finish_mining_events.send(FinishMiningBlockEvent {
|
||||||
|
entity,
|
||||||
|
position: mining.pos,
|
||||||
|
});
|
||||||
|
send_packet_events.send(SendPacketEvent {
|
||||||
|
entity,
|
||||||
|
packet: ServerboundPlayerActionPacket {
|
||||||
|
action: serverbound_player_action_packet::Action::StopDestroyBlock,
|
||||||
|
pos: mining.pos,
|
||||||
|
direction: mining.dir,
|
||||||
|
sequence: **sequence_number,
|
||||||
|
}
|
||||||
|
.get(),
|
||||||
|
});
|
||||||
|
**mine_progress = 0.;
|
||||||
|
**mine_ticks = 0.;
|
||||||
|
**mine_delay = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mine_block_progress_events.send(MineBlockProgressEvent {
|
||||||
|
entity,
|
||||||
|
position: mining.pos,
|
||||||
|
destroy_stage: mine_progress.destroy_stage(),
|
||||||
|
});
|
||||||
|
swing_arm_events.send(SwingArmEvent { entity });
|
||||||
|
} else {
|
||||||
|
start_mining_events.send(StartMiningBlockWithDirectionEvent {
|
||||||
|
entity,
|
||||||
|
position: mining.pos,
|
||||||
|
direction: mining.dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
swing_arm_events.send(SwingArmEvent { entity });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ use crate::client::Client;
|
||||||
use crate::local_player::{
|
use crate::local_player::{
|
||||||
update_in_loaded_chunk, LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState,
|
update_in_loaded_chunk, LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState,
|
||||||
};
|
};
|
||||||
|
use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
|
||||||
|
use azalea_entity::{LastSentPosition, LookDirection, Physics, Position};
|
||||||
use azalea_physics::{force_jump_listener, PhysicsSet};
|
use azalea_physics::{force_jump_listener, PhysicsSet};
|
||||||
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
|
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
|
||||||
use azalea_protocol::packets::game::{
|
use azalea_protocol::packets::game::{
|
||||||
|
@ -10,10 +12,7 @@ use azalea_protocol::packets::game::{
|
||||||
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
||||||
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
||||||
};
|
};
|
||||||
use azalea_world::{
|
use azalea_world::{MinecraftEntityId, MoveEntityError};
|
||||||
entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId},
|
|
||||||
MoveEntityError,
|
|
||||||
};
|
|
||||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
use bevy_ecs::prelude::Event;
|
use bevy_ecs::prelude::Event;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
@ -89,7 +88,7 @@ impl Client {
|
||||||
/// `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_direction(&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 look_direction = self.query::<&mut entity::LookDirection>(&mut ecs);
|
let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
|
||||||
|
|
||||||
(look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
|
(look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
|
||||||
}
|
}
|
||||||
|
@ -110,12 +109,12 @@ pub(crate) fn send_position(
|
||||||
&MinecraftEntityId,
|
&MinecraftEntityId,
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
&mut PhysicsState,
|
&mut PhysicsState,
|
||||||
&entity::Position,
|
&Position,
|
||||||
&mut entity::LastSentPosition,
|
&mut LastSentPosition,
|
||||||
&mut entity::Physics,
|
&mut Physics,
|
||||||
&entity::LookDirection,
|
&LookDirection,
|
||||||
&mut LastSentLookDirection,
|
&mut LastSentLookDirection,
|
||||||
&entity::metadata::Sprinting,
|
&Sprinting,
|
||||||
),
|
),
|
||||||
&LocalPlayerInLoadedChunk,
|
&LocalPlayerInLoadedChunk,
|
||||||
>,
|
>,
|
||||||
|
@ -223,7 +222,7 @@ impl LocalPlayer {
|
||||||
fn send_sprinting_if_needed(
|
fn send_sprinting_if_needed(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &MinecraftEntityId,
|
id: &MinecraftEntityId,
|
||||||
sprinting: &entity::metadata::Sprinting,
|
sprinting: &Sprinting,
|
||||||
physics_state: &mut PhysicsState,
|
physics_state: &mut PhysicsState,
|
||||||
) {
|
) {
|
||||||
let was_sprinting = physics_state.was_sprinting;
|
let was_sprinting = physics_state.was_sprinting;
|
||||||
|
@ -287,9 +286,9 @@ pub fn local_player_ai_step(
|
||||||
mut query: Query<
|
mut query: Query<
|
||||||
(
|
(
|
||||||
&mut PhysicsState,
|
&mut PhysicsState,
|
||||||
&mut entity::Physics,
|
&mut Physics,
|
||||||
&mut entity::metadata::Sprinting,
|
&mut Sprinting,
|
||||||
&mut entity::Attributes,
|
&mut Attributes,
|
||||||
),
|
),
|
||||||
With<LocalPlayerInLoadedChunk>,
|
With<LocalPlayerInLoadedChunk>,
|
||||||
>,
|
>,
|
||||||
|
@ -431,12 +430,12 @@ fn set_sprinting(
|
||||||
if sprinting {
|
if sprinting {
|
||||||
attributes
|
attributes
|
||||||
.speed
|
.speed
|
||||||
.insert(entity::attributes::sprinting_modifier())
|
.insert(azalea_entity::attributes::sprinting_modifier())
|
||||||
.is_ok()
|
.is_ok()
|
||||||
} else {
|
} else {
|
||||||
attributes
|
attributes
|
||||||
.speed
|
.speed
|
||||||
.remove(&entity::attributes::sprinting_modifier().uuid)
|
.remove(&azalea_entity::attributes::sprinting_modifier().uuid)
|
||||||
.is_none()
|
.is_none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
||||||
|
|
||||||
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
|
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
|
||||||
|
use azalea_entity::{
|
||||||
|
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
||||||
|
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy, LookDirection,
|
||||||
|
Physics, PlayerBundle, Position, RelativeEntityUpdate,
|
||||||
|
};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
connect::{ReadConnection, WriteConnection},
|
connect::{ReadConnection, WriteConnection},
|
||||||
packets::game::{
|
packets::game::{
|
||||||
|
@ -15,15 +20,7 @@ use azalea_protocol::{
|
||||||
},
|
},
|
||||||
read::ReadPacketError,
|
read::ReadPacketError,
|
||||||
};
|
};
|
||||||
use azalea_world::{
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
||||||
entity::{
|
|
||||||
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
|
||||||
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LookDirection,
|
|
||||||
MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
|
|
||||||
},
|
|
||||||
entity::{LoadedBy, RelativeEntityUpdate},
|
|
||||||
InstanceContainer, PartialInstance,
|
|
||||||
};
|
|
||||||
use bevy_app::{App, First, Plugin, PreUpdate};
|
use bevy_app::{App, First, Plugin, PreUpdate};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
|
@ -195,7 +192,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
Commands,
|
Commands,
|
||||||
Query<(
|
Query<(
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
Option<&mut WorldName>,
|
Option<&mut InstanceName>,
|
||||||
&GameProfileComponent,
|
&GameProfileComponent,
|
||||||
&ClientInformation,
|
&ClientInformation,
|
||||||
)>,
|
)>,
|
||||||
|
@ -225,7 +222,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
} else {
|
} else {
|
||||||
commands
|
commands
|
||||||
.entity(player_entity)
|
.entity(player_entity)
|
||||||
.insert(WorldName(new_world_name.clone()));
|
.insert(InstanceName(new_world_name.clone()));
|
||||||
}
|
}
|
||||||
// add this world to the instance_container (or don't if it's already
|
// add this world to the instance_container (or don't if it's already
|
||||||
// there)
|
// there)
|
||||||
|
@ -348,10 +345,16 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
)>,
|
)>,
|
||||||
> = SystemState::new(ecs);
|
> = SystemState::new(ecs);
|
||||||
let mut query = system_state.get_mut(ecs);
|
let mut query = system_state.get_mut(ecs);
|
||||||
let Ok((local_player, mut physics, mut direction, mut position, mut last_sent_position)) =
|
let Ok((
|
||||||
query.get_mut(player_entity) else {
|
local_player,
|
||||||
continue;
|
mut physics,
|
||||||
};
|
mut direction,
|
||||||
|
mut position,
|
||||||
|
mut last_sent_position,
|
||||||
|
)) = query.get_mut(player_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let delta_movement = physics.delta;
|
let delta_movement = physics.delta;
|
||||||
|
|
||||||
|
@ -555,12 +558,12 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
ClientboundGamePacket::AddEntity(p) => {
|
ClientboundGamePacket::AddEntity(p) => {
|
||||||
debug!("Got add entity packet {:?}", p);
|
debug!("Got add entity packet {:?}", p);
|
||||||
|
|
||||||
let mut system_state: SystemState<(Commands, Query<Option<&WorldName>>)> =
|
let mut system_state: SystemState<(Commands, Query<Option<&InstanceName>>)> =
|
||||||
SystemState::new(ecs);
|
SystemState::new(ecs);
|
||||||
let (mut commands, mut query) = system_state.get_mut(ecs);
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
||||||
let world_name = query.get_mut(player_entity).unwrap();
|
let world_name = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
if let Some(WorldName(world_name)) = world_name {
|
if let Some(InstanceName(world_name)) = world_name {
|
||||||
let bundle = p.as_entity_bundle(world_name.clone());
|
let bundle = p.as_entity_bundle(world_name.clone());
|
||||||
let mut entity_commands = commands.spawn((
|
let mut entity_commands = commands.spawn((
|
||||||
MinecraftEntityId(p.id),
|
MinecraftEntityId(p.id),
|
||||||
|
@ -622,12 +625,12 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
let mut system_state: SystemState<(
|
let mut system_state: SystemState<(
|
||||||
Commands,
|
Commands,
|
||||||
Query<(&TabList, Option<&WorldName>)>,
|
Query<(&TabList, Option<&InstanceName>)>,
|
||||||
)> = SystemState::new(ecs);
|
)> = SystemState::new(ecs);
|
||||||
let (mut commands, mut query) = system_state.get_mut(ecs);
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
||||||
let (tab_list, world_name) = query.get_mut(player_entity).unwrap();
|
let (tab_list, world_name) = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
if let Some(WorldName(world_name)) = world_name {
|
if let Some(InstanceName(world_name)) = world_name {
|
||||||
let bundle = p.as_player_bundle(world_name.clone());
|
let bundle = p.as_player_bundle(world_name.clone());
|
||||||
let mut spawned = commands.spawn((
|
let mut spawned = commands.spawn((
|
||||||
MinecraftEntityId(p.id),
|
MinecraftEntityId(p.id),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use azalea_auth::game_profile::GameProfile;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::GameMode;
|
use azalea_core::GameMode;
|
||||||
use azalea_world::entity::EntityInfos;
|
use azalea_entity::EntityInfos;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
system::{Commands, Res},
|
system::{Commands, Res},
|
||||||
|
|
|
@ -22,8 +22,7 @@ pub use direction::*;
|
||||||
mod delta;
|
mod delta;
|
||||||
pub use delta::*;
|
pub use delta::*;
|
||||||
|
|
||||||
mod particle;
|
pub mod particle;
|
||||||
pub use particle::*;
|
|
||||||
|
|
||||||
mod cursor3d;
|
mod cursor3d;
|
||||||
pub use cursor3d::*;
|
pub use cursor3d::*;
|
||||||
|
@ -37,61 +36,5 @@ 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
|
pub mod math;
|
||||||
|
pub mod tier;
|
||||||
// TODO: make this generic
|
|
||||||
pub fn binary_search(mut min: i32, max: i32, predicate: &dyn Fn(i32) -> bool) -> i32 {
|
|
||||||
let mut diff = max - min;
|
|
||||||
while diff > 0 {
|
|
||||||
let diff_mid = diff / 2;
|
|
||||||
let mid = min + diff_mid;
|
|
||||||
if predicate(mid) {
|
|
||||||
diff = diff_mid;
|
|
||||||
} else {
|
|
||||||
min = mid + 1;
|
|
||||||
diff -= diff_mid + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
min
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lcm(a: u32, b: u32) -> u64 {
|
|
||||||
let gcd = gcd(a, b);
|
|
||||||
(a as u64) * (b / gcd) as u64
|
|
||||||
}
|
|
||||||
pub fn gcd(mut a: u32, mut b: u32) -> u32 {
|
|
||||||
while b != 0 {
|
|
||||||
let t = b;
|
|
||||||
b = a % b;
|
|
||||||
a = t;
|
|
||||||
}
|
|
||||||
a
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp<T: num_traits::Float>(amount: T, a: T, b: T) -> T {
|
|
||||||
a + amount * (b - a)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gcd() {
|
|
||||||
assert_eq!(gcd(0, 0), 0);
|
|
||||||
assert_eq!(gcd(1, 1), 1);
|
|
||||||
|
|
||||||
assert_eq!(gcd(0, 1), 1);
|
|
||||||
assert_eq!(gcd(1, 0), 1);
|
|
||||||
|
|
||||||
assert_eq!(gcd(12, 8), 4);
|
|
||||||
assert_eq!(gcd(8, 12), 4);
|
|
||||||
|
|
||||||
assert_eq!(gcd(12, 9), 3);
|
|
||||||
assert_eq!(gcd(9, 12), 3);
|
|
||||||
|
|
||||||
assert_eq!(gcd(12, 7), 1);
|
|
||||||
assert_eq!(gcd(7, 12), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
56
azalea-core/src/math.rs
Normal file
56
azalea-core/src/math.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// TODO: make this generic
|
||||||
|
pub fn binary_search(mut min: i32, max: i32, predicate: &dyn Fn(i32) -> bool) -> i32 {
|
||||||
|
let mut diff = max - min;
|
||||||
|
while diff > 0 {
|
||||||
|
let diff_mid = diff / 2;
|
||||||
|
let mid = min + diff_mid;
|
||||||
|
if predicate(mid) {
|
||||||
|
diff = diff_mid;
|
||||||
|
} else {
|
||||||
|
min = mid + 1;
|
||||||
|
diff -= diff_mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
min
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lcm(a: u32, b: u32) -> u64 {
|
||||||
|
let gcd = gcd(a, b);
|
||||||
|
(a as u64) * (b / gcd) as u64
|
||||||
|
}
|
||||||
|
pub fn gcd(mut a: u32, mut b: u32) -> u32 {
|
||||||
|
while b != 0 {
|
||||||
|
let t = b;
|
||||||
|
b = a % b;
|
||||||
|
a = t;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerp<T: num_traits::Float>(amount: T, a: T, b: T) -> T {
|
||||||
|
a + amount * (b - a)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gcd() {
|
||||||
|
assert_eq!(gcd(0, 0), 0);
|
||||||
|
assert_eq!(gcd(1, 1), 1);
|
||||||
|
|
||||||
|
assert_eq!(gcd(0, 1), 1);
|
||||||
|
assert_eq!(gcd(1, 0), 1);
|
||||||
|
|
||||||
|
assert_eq!(gcd(12, 8), 4);
|
||||||
|
assert_eq!(gcd(8, 12), 4);
|
||||||
|
|
||||||
|
assert_eq!(gcd(12, 9), 3);
|
||||||
|
assert_eq!(gcd(9, 12), 3);
|
||||||
|
|
||||||
|
assert_eq!(gcd(12, 7), 1);
|
||||||
|
assert_eq!(gcd(7, 12), 1);
|
||||||
|
}
|
||||||
|
}
|
46
azalea-core/src/tier.rs
Normal file
46
azalea-core/src/tier.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
pub fn get_item_tier(item: azalea_registry::Item) -> Option<Tier> {
|
||||||
|
use azalea_registry::Item::*;
|
||||||
|
Some(match item {
|
||||||
|
WoodenPickaxe | WoodenShovel | WoodenAxe | WoodenHoe | WoodenSword => Tier::Wood,
|
||||||
|
StonePickaxe | StoneShovel | StoneAxe | StoneHoe | StoneSword => Tier::Stone,
|
||||||
|
IronPickaxe | IronShovel | IronAxe | IronHoe | IronSword => Tier::Iron,
|
||||||
|
DiamondPickaxe | DiamondShovel | DiamondAxe | DiamondHoe | DiamondSword => Tier::Diamond,
|
||||||
|
GoldenPickaxe | GoldenShovel | GoldenAxe | GoldenHoe | GoldenSword => Tier::Gold,
|
||||||
|
NetheritePickaxe | NetheriteShovel | NetheriteAxe | NetheriteHoe | NetheriteSword => {
|
||||||
|
Tier::Netherite
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Tier {
|
||||||
|
Wood,
|
||||||
|
Stone,
|
||||||
|
Iron,
|
||||||
|
Diamond,
|
||||||
|
Gold,
|
||||||
|
Netherite,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tier {
|
||||||
|
pub fn level(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Tier::Wood => 0,
|
||||||
|
Tier::Stone => 1,
|
||||||
|
Tier::Iron => 2,
|
||||||
|
Tier::Diamond => 3,
|
||||||
|
Tier::Gold => 0, // gold is the same tier as wood
|
||||||
|
Tier::Netherite => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn speed(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Tier::Wood => 2.,
|
||||||
|
Tier::Stone => 4.,
|
||||||
|
Tier::Iron => 6.,
|
||||||
|
Tier::Diamond => 8.,
|
||||||
|
Tier::Gold => 12.,
|
||||||
|
Tier::Netherite => 9.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
azalea-entity/Cargo.toml
Normal file
25
azalea-entity/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "azalea-entity"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
azalea-block = { version = "0.7.0", path = "../azalea-block" }
|
||||||
|
azalea-buf = { version = "0.7.0", path = "../azalea-buf" }
|
||||||
|
azalea-chat = { version = "0.7.0", path = "../azalea-chat" }
|
||||||
|
azalea-core = { version = "0.7.0", path = "../azalea-core" }
|
||||||
|
azalea-inventory = { version = "0.7.0", path = "../azalea-inventory" }
|
||||||
|
azalea-nbt = { version = "0.7.0", path = "../azalea-nbt" }
|
||||||
|
azalea-registry = { version = "0.7.0", path = "../azalea-registry" }
|
||||||
|
azalea-world = { version = "0.7.0", path = "../azalea-world" }
|
||||||
|
bevy_app = "0.11.0"
|
||||||
|
bevy_ecs = "0.11.0"
|
||||||
|
derive_more = "0.99.17"
|
||||||
|
enum-as-inner = "0.6.0"
|
||||||
|
log = "0.4.19"
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
thiserror = "1.0.43"
|
||||||
|
uuid = "1.4.0"
|
|
@ -4,7 +4,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::{BlockPos, Direction, GlobalPos, Particle, Vec3};
|
use azalea_core::{particle::Particle, BlockPos, Direction, GlobalPos, Vec3};
|
||||||
use azalea_inventory::ItemSlot;
|
use azalea_inventory::ItemSlot;
|
||||||
use bevy_ecs::component::Component;
|
use bevy_ecs::component::Component;
|
||||||
use derive_more::Deref;
|
use derive_more::Deref;
|
26
azalea-entity/src/effects.rs
Normal file
26
azalea-entity/src/effects.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// pub struct ActiveEffects(HashMap<azalea_registry::MobEffect, MobEffectData>);
|
||||||
|
|
||||||
|
/// Returns the level of the given effect, or `None` if the effect is not
|
||||||
|
/// active. The lowest level is 0.
|
||||||
|
pub fn get_effect(_effect: azalea_registry::MobEffect) -> Option<u32> {
|
||||||
|
// TODO
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dig_speed_amplifier() -> Option<u32> {
|
||||||
|
let effect_plus_one = u32::max(
|
||||||
|
get_effect(azalea_registry::MobEffect::Haste)
|
||||||
|
.map(|x| x + 1)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
get_effect(azalea_registry::MobEffect::ConduitPower)
|
||||||
|
.map(|x| x + 1)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
if effect_plus_one > 0 {
|
||||||
|
Some(effect_plus_one - 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
8
azalea-entity/src/enchantments.rs
Normal file
8
azalea-entity/src/enchantments.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
pub fn get_enchant_level(
|
||||||
|
_enchantment: azalea_registry::Enchantment,
|
||||||
|
_player_inventory: &azalea_inventory::Menu,
|
||||||
|
) -> u32 {
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
|
@ -1,14 +1,8 @@
|
||||||
//! Implement things relating to entity datas, like an index of uuids to
|
//! Implement things relating to entity datas, like an index of uuids to
|
||||||
//! entities.
|
//! entities.
|
||||||
|
|
||||||
use crate::{
|
|
||||||
deduplicate_entities, deduplicate_local_entities,
|
|
||||||
entity::{
|
|
||||||
self, add_dead, update_bounding_box, EntityUuid, MinecraftEntityId, Position, WorldName,
|
|
||||||
},
|
|
||||||
update_entity_by_id_index, update_uuid_index, InstanceContainer, PartialInstance,
|
|
||||||
};
|
|
||||||
use azalea_core::ChunkPos;
|
use azalea_core::ChunkPos;
|
||||||
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
||||||
use bevy_app::{App, Plugin, PostUpdate, PreUpdate, Update};
|
use bevy_app::{App, Plugin, PostUpdate, PreUpdate, Update};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
|
@ -20,7 +14,6 @@ use bevy_ecs::{
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use nohash_hasher::IntMap;
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
@ -29,6 +22,15 @@ use std::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
add_dead,
|
||||||
|
systems::{
|
||||||
|
deduplicate_entities, deduplicate_local_entities, update_entity_by_id_index,
|
||||||
|
update_fluid_on_eyes, update_uuid_index,
|
||||||
|
},
|
||||||
|
update_bounding_box, EntityUuid, LastSentPosition, Position,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{Local, LookDirection};
|
use super::{Local, LookDirection};
|
||||||
|
|
||||||
/// A Bevy [`SystemSet`] for various types of entity updates.
|
/// A Bevy [`SystemSet`] for various types of entity updates.
|
||||||
|
@ -75,6 +77,7 @@ impl Plugin for EntityPlugin {
|
||||||
add_dead,
|
add_dead,
|
||||||
update_bounding_box,
|
update_bounding_box,
|
||||||
clamp_look_direction,
|
clamp_look_direction,
|
||||||
|
update_fluid_on_eyes,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -109,33 +112,6 @@ fn debug_new_entity(query: Query<(Entity, Option<&Local>), Added<MinecraftEntity
|
||||||
// "updates received" if not, then we simply increment our local "updates
|
// "updates received" if not, then we simply increment our local "updates
|
||||||
// received" and do nothing else
|
// received" and do nothing else
|
||||||
|
|
||||||
/// Keep track of certain metadatas that are only relevant for this partial
|
|
||||||
/// world.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct PartialEntityInfos {
|
|
||||||
// note: using MinecraftEntityId for entity ids is acceptable here since
|
|
||||||
// there's no chance of collisions here
|
|
||||||
/// The entity id of the player that owns this partial world. This will
|
|
||||||
/// make [`RelativeEntityUpdate`] pretend the entity doesn't exist so
|
|
||||||
/// it doesn't get modified from outside sources.
|
|
||||||
pub owner_entity: Option<Entity>,
|
|
||||||
/// A counter for each entity that tracks how many updates we've observed
|
|
||||||
/// for it.
|
|
||||||
///
|
|
||||||
/// This is used for shared worlds (i.e. swarms), to make sure we don't
|
|
||||||
/// update entities twice on accident.
|
|
||||||
pub updates_received: IntMap<MinecraftEntityId, u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEntityInfos {
|
|
||||||
pub fn new(owner_entity: Option<Entity>) -> Self {
|
|
||||||
Self {
|
|
||||||
owner_entity,
|
|
||||||
updates_received: IntMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An [`EntityCommand`] that applies a "relative update" to an entity, which
|
/// An [`EntityCommand`] that applies a "relative update" to an entity, which
|
||||||
/// means this update won't be run multiple times by different clients in the
|
/// means this update won't be run multiple times by different clients in the
|
||||||
/// same world.
|
/// same world.
|
||||||
|
@ -211,15 +187,7 @@ impl EntityInfos {
|
||||||
|
|
||||||
/// Update the chunk position indexes in [`EntityInfos`].
|
/// Update the chunk position indexes in [`EntityInfos`].
|
||||||
fn update_entity_chunk_positions(
|
fn update_entity_chunk_positions(
|
||||||
mut query: Query<
|
mut query: Query<(Entity, &Position, &mut LastSentPosition, &InstanceName), Changed<Position>>,
|
||||||
(
|
|
||||||
Entity,
|
|
||||||
&entity::Position,
|
|
||||||
&mut entity::LastSentPosition,
|
|
||||||
&entity::WorldName,
|
|
||||||
),
|
|
||||||
Changed<entity::Position>,
|
|
||||||
>,
|
|
||||||
instance_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() {
|
||||||
|
@ -288,7 +256,7 @@ fn remove_despawned_entities_from_indexes(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut entity_infos: ResMut<EntityInfos>,
|
mut entity_infos: ResMut<EntityInfos>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed<LoadedBy>>,
|
query: Query<(Entity, &EntityUuid, &Position, &InstanceName, &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 = instance_container.get(world_name).unwrap();
|
let world_lock = instance_container.get(world_name).unwrap();
|
|
@ -3,15 +3,18 @@
|
||||||
pub mod attributes;
|
pub mod attributes;
|
||||||
mod data;
|
mod data;
|
||||||
mod dimensions;
|
mod dimensions;
|
||||||
|
mod effects;
|
||||||
|
mod enchantments;
|
||||||
mod info;
|
mod info;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
|
pub mod mining;
|
||||||
use crate::ChunkStorage;
|
mod systems;
|
||||||
|
|
||||||
use self::{attributes::AttributeInstance, metadata::Health};
|
use self::{attributes::AttributeInstance, metadata::Health};
|
||||||
pub use attributes::Attributes;
|
pub use attributes::Attributes;
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
||||||
|
use azalea_world::{ChunkStorage, InstanceName};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
component::Component,
|
component::Component,
|
||||||
|
@ -23,23 +26,12 @@ 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::{
|
||||||
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy, PartialEntityInfos,
|
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy,
|
||||||
RelativeEntityUpdate,
|
RelativeEntityUpdate,
|
||||||
};
|
};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// An entity ID used by Minecraft. These are not guaranteed to be unique in
|
|
||||||
/// shared worlds, that's what [`Entity`] is for.
|
|
||||||
#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)]
|
|
||||||
pub struct MinecraftEntityId(pub u32);
|
|
||||||
impl std::hash::Hash for MinecraftEntityId {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
|
||||||
hasher.write_u32(self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
|
||||||
|
|
||||||
pub fn move_relative(
|
pub fn move_relative(
|
||||||
physics: &mut Physics,
|
physics: &mut Physics,
|
||||||
direction: &LookDirection,
|
direction: &LookDirection,
|
||||||
|
@ -197,11 +189,6 @@ impl From<&LastSentPosition> for BlockPos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the world the entity is in. If two entities share the same world
|
|
||||||
/// name, we assume they're in the same world.
|
|
||||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
|
||||||
pub struct WorldName(pub ResourceLocation);
|
|
||||||
|
|
||||||
/// A component for entities that can jump.
|
/// A component for entities that can jump.
|
||||||
///
|
///
|
||||||
/// 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
|
||||||
|
@ -303,7 +290,7 @@ pub struct EntityKind(pub azalea_registry::EntityKind);
|
||||||
pub struct EntityBundle {
|
pub struct EntityBundle {
|
||||||
pub kind: EntityKind,
|
pub kind: EntityKind,
|
||||||
pub uuid: EntityUuid,
|
pub uuid: EntityUuid,
|
||||||
pub world_name: WorldName,
|
pub world_name: InstanceName,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub last_sent_position: LastSentPosition,
|
pub last_sent_position: LastSentPosition,
|
||||||
pub physics: Physics,
|
pub physics: Physics,
|
||||||
|
@ -311,6 +298,7 @@ pub struct EntityBundle {
|
||||||
pub eye_height: EyeHeight,
|
pub eye_height: EyeHeight,
|
||||||
pub attributes: Attributes,
|
pub attributes: Attributes,
|
||||||
pub jumping: Jumping,
|
pub jumping: Jumping,
|
||||||
|
pub fluid_on_eyes: FluidOnEyes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBundle {
|
impl EntityBundle {
|
||||||
|
@ -330,7 +318,7 @@ impl EntityBundle {
|
||||||
Self {
|
Self {
|
||||||
kind: EntityKind(kind),
|
kind: EntityKind(kind),
|
||||||
uuid: EntityUuid(uuid),
|
uuid: EntityUuid(uuid),
|
||||||
world_name: WorldName(world_name),
|
world_name: InstanceName(world_name),
|
||||||
position: Position(pos),
|
position: Position(pos),
|
||||||
last_sent_position: LastSentPosition(pos),
|
last_sent_position: LastSentPosition(pos),
|
||||||
physics: Physics {
|
physics: Physics {
|
||||||
|
@ -359,6 +347,7 @@ impl EntityBundle {
|
||||||
},
|
},
|
||||||
|
|
||||||
jumping: Jumping(false),
|
jumping: Jumping(false),
|
||||||
|
fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,6 +364,9 @@ pub struct PlayerBundle {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Local;
|
pub struct Local;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||||
|
pub struct FluidOnEyes(azalea_registry::Fluid);
|
||||||
|
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod tests {
|
// mod tests {
|
||||||
// use super::*;
|
// use super::*;
|
|
@ -8,7 +8,7 @@ use super::{
|
||||||
SnifferState, VillagerData,
|
SnifferState, VillagerData,
|
||||||
};
|
};
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::{BlockPos, Direction, Particle, Vec3};
|
use azalea_core::{particle::Particle, BlockPos, Direction, Vec3};
|
||||||
use azalea_inventory::ItemSlot;
|
use azalea_inventory::ItemSlot;
|
||||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
170
azalea-entity/src/mining.rs
Normal file
170
azalea-entity/src/mining.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use azalea_block::{Block, BlockBehavior};
|
||||||
|
use azalea_core::tier::get_item_tier;
|
||||||
|
use azalea_registry as registry;
|
||||||
|
|
||||||
|
use crate::{effects, enchantments, FluidOnEyes, Physics};
|
||||||
|
|
||||||
|
pub fn get_mine_progress(
|
||||||
|
block: &dyn Block,
|
||||||
|
held_item: registry::Item,
|
||||||
|
player_inventory: &azalea_inventory::Menu,
|
||||||
|
fluid_on_eyes: &FluidOnEyes,
|
||||||
|
physics: &Physics,
|
||||||
|
) -> f32 {
|
||||||
|
// public float getDestroyProgress(BlockState blockState, Player player,
|
||||||
|
// BlockGetter world, BlockPos blockPos) { float destroySpeed =
|
||||||
|
// blockState.getDestroySpeed(world, blockPos); if (destroySpeed ==
|
||||||
|
// -1.0F) { return 0.0F;
|
||||||
|
// } else {
|
||||||
|
// int divider = player.hasCorrectToolForDrops(blockState) ? 30 : 100;
|
||||||
|
// return player.getDestroySpeed(blockState) / destroySpeed /
|
||||||
|
// (float)divider; }
|
||||||
|
// }
|
||||||
|
|
||||||
|
let block_behavior: BlockBehavior = block.behavior();
|
||||||
|
|
||||||
|
let destroy_time = block_behavior.destroy_time;
|
||||||
|
if destroy_time == -1. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
let divider = if has_correct_tool_for_drops(block, held_item) {
|
||||||
|
30
|
||||||
|
} else {
|
||||||
|
100
|
||||||
|
};
|
||||||
|
|
||||||
|
(destroy_speed(
|
||||||
|
block.as_registry_block(),
|
||||||
|
held_item,
|
||||||
|
player_inventory,
|
||||||
|
fluid_on_eyes,
|
||||||
|
physics,
|
||||||
|
) / destroy_time)
|
||||||
|
/ divider as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_correct_tool_for_drops(block: &dyn Block, tool: registry::Item) -> bool {
|
||||||
|
if !block.behavior().requires_correct_tool_for_drops {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let registry_block = block.as_registry_block();
|
||||||
|
if tool == registry::Item::Shears {
|
||||||
|
matches!(
|
||||||
|
registry_block,
|
||||||
|
registry::Block::Cobweb | registry::Block::RedstoneWire | registry::Block::Tripwire
|
||||||
|
)
|
||||||
|
} else if registry::tags::items::SWORDS.contains(&tool) {
|
||||||
|
registry_block == registry::Block::Cobweb
|
||||||
|
} else if registry::tags::items::PICKAXES.contains(&tool)
|
||||||
|
|| registry::tags::items::SHOVELS.contains(&tool)
|
||||||
|
|| registry::tags::items::HOES.contains(&tool)
|
||||||
|
|| registry::tags::items::AXES.contains(&tool)
|
||||||
|
{
|
||||||
|
let tier = get_item_tier(tool).expect("all pickaxes and shovels should be matched");
|
||||||
|
let tier_level = tier.level();
|
||||||
|
!((tier_level < 3 && registry::tags::blocks::NEEDS_DIAMOND_TOOL.contains(®istry_block))
|
||||||
|
|| (tier_level < 2
|
||||||
|
&& registry::tags::blocks::NEEDS_IRON_TOOL.contains(®istry_block))
|
||||||
|
|| (tier_level < 1
|
||||||
|
&& registry::tags::blocks::NEEDS_STONE_TOOL.contains(®istry_block)))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the destroy speed of the given block with the given tool, taking
|
||||||
|
/// into account enchantments and effects. If the player is not holding anything
|
||||||
|
/// then `tool` should be `Item::Air`.
|
||||||
|
fn destroy_speed(
|
||||||
|
block: registry::Block,
|
||||||
|
tool: registry::Item,
|
||||||
|
player_inventory: &azalea_inventory::Menu,
|
||||||
|
fluid_on_eyes: &FluidOnEyes,
|
||||||
|
physics: &Physics,
|
||||||
|
) -> f32 {
|
||||||
|
let mut base_destroy_speed = base_destroy_speed(block, tool);
|
||||||
|
|
||||||
|
// add efficiency enchantment
|
||||||
|
if base_destroy_speed > 1. {
|
||||||
|
let efficiency_level =
|
||||||
|
enchantments::get_enchant_level(registry::Enchantment::Efficiency, player_inventory);
|
||||||
|
if efficiency_level > 0 && tool != registry::Item::Air {
|
||||||
|
base_destroy_speed += (efficiency_level * efficiency_level + 1) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dig_speed_amplifier) = effects::get_dig_speed_amplifier() {
|
||||||
|
base_destroy_speed *= 1. + (dig_speed_amplifier + 1) as f32 * 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dig_slowdown) = effects::get_effect(registry::MobEffect::MiningFatigue) {
|
||||||
|
let multiplier = match dig_slowdown {
|
||||||
|
0 => 0.3,
|
||||||
|
1 => 0.09,
|
||||||
|
2 => 0.0027,
|
||||||
|
_ => 8.1E-4,
|
||||||
|
};
|
||||||
|
base_destroy_speed *= multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if registry::tags::fluids::WATER.contains(fluid_on_eyes)
|
||||||
|
&& enchantments::get_enchant_level(registry::Enchantment::AquaAffinity, player_inventory)
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
base_destroy_speed /= 5.;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !physics.on_ground {
|
||||||
|
base_destroy_speed /= 5.;
|
||||||
|
}
|
||||||
|
|
||||||
|
base_destroy_speed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_destroy_speed(block: registry::Block, tool: registry::Item) -> f32 {
|
||||||
|
if tool == registry::Item::Shears {
|
||||||
|
if block == registry::Block::Cobweb || registry::tags::blocks::LEAVES.contains(&block) {
|
||||||
|
15.
|
||||||
|
} else if registry::tags::blocks::WOOL.contains(&block) {
|
||||||
|
5.
|
||||||
|
} else if matches!(block, registry::Block::Vine | registry::Block::GlowLichen) {
|
||||||
|
2.
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else if registry::tags::items::SWORDS.contains(&tool) {
|
||||||
|
if block == registry::Block::Cobweb {
|
||||||
|
15.
|
||||||
|
} else if registry::tags::blocks::SWORD_EFFICIENT.contains(&block) {
|
||||||
|
1.5
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else if registry::tags::items::PICKAXES.contains(&tool) {
|
||||||
|
if registry::tags::blocks::MINEABLE_PICKAXE.contains(&block) {
|
||||||
|
get_item_tier(tool).unwrap().speed()
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else if registry::tags::items::SHOVELS.contains(&tool) {
|
||||||
|
if registry::tags::blocks::MINEABLE_SHOVEL.contains(&block) {
|
||||||
|
get_item_tier(tool).unwrap().speed()
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else if registry::tags::items::HOES.contains(&tool) {
|
||||||
|
if registry::tags::blocks::MINEABLE_HOE.contains(&block) {
|
||||||
|
get_item_tier(tool).unwrap().speed()
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else if registry::tags::items::AXES.contains(&tool) {
|
||||||
|
if registry::tags::blocks::MINEABLE_AXE.contains(&block) {
|
||||||
|
get_item_tier(tool).unwrap().speed()
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
}
|
||||||
|
}
|
181
azalea-entity/src/systems.rs
Normal file
181
azalea-entity/src/systems.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use azalea_core::{BlockPos, Vec3};
|
||||||
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use log::{debug, error, info};
|
||||||
|
|
||||||
|
use crate::{EntityInfos, EntityUuid, EyeHeight, FluidOnEyes, LoadedBy, Local, Position};
|
||||||
|
|
||||||
|
/// Remove new entities that have the same id as an existing entity, and
|
||||||
|
/// increase the reference counts.
|
||||||
|
///
|
||||||
|
/// This is the reason why spawning entities into the ECS when you get a spawn
|
||||||
|
/// entity packet is okay. This system will make sure the new entity gets
|
||||||
|
/// combined into the old one.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn deduplicate_entities(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<
|
||||||
|
(Entity, &MinecraftEntityId, &InstanceName),
|
||||||
|
(Changed<MinecraftEntityId>, Without<Local>),
|
||||||
|
>,
|
||||||
|
mut loaded_by_query: Query<&mut LoadedBy>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
// if this entity already exists, remove it
|
||||||
|
for (new_entity, id, world_name) in query.iter_mut() {
|
||||||
|
if let Some(world_lock) = instance_container.get(world_name) {
|
||||||
|
let world = world_lock.write();
|
||||||
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
|
if old_entity == &new_entity {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this entity already exists!!! remove the one we just added but increase
|
||||||
|
// the reference count
|
||||||
|
let new_loaded_by = loaded_by_query
|
||||||
|
.get(new_entity)
|
||||||
|
.unwrap_or_else(|_| panic!(
|
||||||
|
"Entities should always have the LoadedBy component ({new_entity:?} did not)"
|
||||||
|
))
|
||||||
|
.clone();
|
||||||
|
let old_loaded_by = loaded_by_query.get_mut(*old_entity);
|
||||||
|
// merge them if possible
|
||||||
|
if let Ok(mut old_loaded_by) = old_loaded_by {
|
||||||
|
old_loaded_by.extend(new_loaded_by.iter());
|
||||||
|
}
|
||||||
|
commands.entity(new_entity).despawn();
|
||||||
|
info!(
|
||||||
|
"Entity with id {id:?} / {new_entity:?} already existed in the world, merging it with {old_entity:?}"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Entity was inserted into a world that doesn't exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when a local entity is added, if there was already an entity with the same id
|
||||||
|
// then delete the old entity
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn deduplicate_local_entities(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<
|
||||||
|
(Entity, &MinecraftEntityId, &InstanceName),
|
||||||
|
(Changed<MinecraftEntityId>, With<Local>),
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
// if this entity already exists, remove the old one
|
||||||
|
for (new_entity, id, world_name) in query.iter_mut() {
|
||||||
|
if let Some(world_lock) = instance_container.get(world_name) {
|
||||||
|
let world = world_lock.write();
|
||||||
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
|
if old_entity == &new_entity {
|
||||||
|
// lol
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.entity(*old_entity).despawn();
|
||||||
|
debug!(
|
||||||
|
"Added local entity {id:?} / {new_entity:?} but already existed in world as {old_entity:?}, despawning {old_entity:?}"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Entity was inserted into a world that doesn't exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_uuid_index(
|
||||||
|
mut entity_infos: ResMut<EntityInfos>,
|
||||||
|
query: Query<(Entity, &EntityUuid, Option<&Local>), Changed<EntityUuid>>,
|
||||||
|
) {
|
||||||
|
for (entity, &uuid, local) in query.iter() {
|
||||||
|
// only add it if it doesn't already exist in
|
||||||
|
// entity_infos.entity_by_uuid
|
||||||
|
if local.is_none() {
|
||||||
|
if let Some(old_entity) = entity_infos.entity_by_uuid.get(&uuid) {
|
||||||
|
debug!(
|
||||||
|
"Entity with UUID {uuid:?} already existed in the world, not adding to
|
||||||
|
index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entity_infos.entity_by_uuid.insert(*uuid, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Clear all entities in a chunk. This will not clear them from the
|
||||||
|
// /// shared storage unless there are no other references to them.
|
||||||
|
// pub fn clear_entities_in_chunk(
|
||||||
|
// mut commands: Commands,
|
||||||
|
// partial_entity_infos: &mut PartialEntityInfos,
|
||||||
|
// chunk: &ChunkPos,
|
||||||
|
// instance_container: &WorldContainer,
|
||||||
|
// world_name: &InstanceName,
|
||||||
|
// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>,
|
||||||
|
// ) { let world_lock = instance_container.get(world_name).unwrap(); let world =
|
||||||
|
// world_lock.read();
|
||||||
|
|
||||||
|
// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
||||||
|
// for &entity in &entities {
|
||||||
|
// let (id, mut reference_count) = query.get_mut(entity).unwrap();
|
||||||
|
// if partial_entity_infos.loaded_entity_ids.remove(id) {
|
||||||
|
// // decrease the reference count
|
||||||
|
// **reference_count -= 1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// System to keep the entity_by_id index up-to-date.
|
||||||
|
pub fn update_entity_by_id_index(
|
||||||
|
mut query: Query<
|
||||||
|
(Entity, &MinecraftEntityId, &InstanceName, Option<&Local>),
|
||||||
|
Changed<MinecraftEntityId>,
|
||||||
|
>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (entity, id, world_name, local) in query.iter_mut() {
|
||||||
|
let world_lock = instance_container.get(world_name).unwrap();
|
||||||
|
let mut world = world_lock.write();
|
||||||
|
if local.is_none() {
|
||||||
|
if let Some(old_entity) = world.entity_by_id.get(id) {
|
||||||
|
debug!(
|
||||||
|
"Entity with ID {id:?} already existed in the world, not adding to
|
||||||
|
index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
world.entity_by_id.insert(*id, entity);
|
||||||
|
debug!("Added {entity:?} to {world_name:?} with {id:?}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_fluid_on_eyes(
|
||||||
|
mut query: Query<(&mut FluidOnEyes, &Position, &EyeHeight, &InstanceName)>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (mut fluid_on_eyes, position, eye_height, instance_name) in query.iter_mut() {
|
||||||
|
let Some(instance) = instance_container.get(instance_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let adjusted_eye_y = position.y + (**eye_height as f64) - 0.1111111119389534;
|
||||||
|
let eye_block_pos = BlockPos::from(Vec3::new(position.x, adjusted_eye_y, position.z));
|
||||||
|
let fluid_at_eye = instance
|
||||||
|
.read()
|
||||||
|
.get_fluid_state(&eye_block_pos)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64);
|
||||||
|
if fluid_cutoff_y > adjusted_eye_y {
|
||||||
|
**fluid_on_eyes = fluid_at_eye.fluid;
|
||||||
|
} else {
|
||||||
|
**fluid_on_eyes = azalea_registry::Fluid::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,8 @@ use azalea_inventory_macros::declare_menus;
|
||||||
pub use slot::{ItemSlot, ItemSlotData};
|
pub use slot::{ItemSlot, ItemSlotData};
|
||||||
|
|
||||||
// TODO: remove this here and in azalea-inventory-macros when rust makes
|
// 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
|
// Default be implemented for all array sizes
|
||||||
// 32)
|
// https://github.com/rust-lang/rust/issues/61415
|
||||||
|
|
||||||
/// A fixed-size list of [`ItemSlot`]s.
|
/// A fixed-size list of [`ItemSlot`]s.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -11,6 +11,7 @@ version = "0.7.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
azalea-block = { path = "../azalea-block", version = "^0.7.0" }
|
azalea-block = { path = "../azalea-block", version = "^0.7.0" }
|
||||||
azalea-core = { path = "../azalea-core", version = "^0.7.0" }
|
azalea-core = { path = "../azalea-core", version = "^0.7.0" }
|
||||||
|
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||||
azalea-inventory = { version = "0.7.0", path = "../azalea-inventory" }
|
azalea-inventory = { version = "0.7.0", path = "../azalea-inventory" }
|
||||||
azalea-registry = { path = "../azalea-registry", version = "^0.7.0" }
|
azalea-registry = { path = "../azalea-registry", version = "^0.7.0" }
|
||||||
azalea-world = { path = "../azalea-world", version = "^0.7.0" }
|
azalea-world = { path = "../azalea-world", version = "^0.7.0" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{lerp, BlockHitResult, BlockPos, Direction, Vec3, EPSILON};
|
use azalea_core::{math::lerp, BlockHitResult, BlockPos, Direction, Vec3, EPSILON};
|
||||||
use azalea_inventory::ItemSlot;
|
use azalea_inventory::ItemSlot;
|
||||||
use azalea_world::ChunkStorage;
|
use azalea_world::ChunkStorage;
|
||||||
use bevy_ecs::entity::Entity;
|
use bevy_ecs::entity::Entity;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use std::{cmp::Ordering, convert::TryInto};
|
use std::{cmp::Ordering, convert::TryInto};
|
||||||
|
|
||||||
use super::CubePointRange;
|
use super::CubePointRange;
|
||||||
use azalea_core::{gcd, lcm, EPSILON};
|
use azalea_core::{
|
||||||
|
math::{gcd, lcm},
|
||||||
|
EPSILON,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IndexMerger {
|
pub enum IndexMerger {
|
||||||
|
|
|
@ -5,7 +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::{entity, Instance, MoveEntityError};
|
use azalea_world::{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::*;
|
||||||
|
@ -49,7 +49,7 @@ pub enum MoverType {
|
||||||
|
|
||||||
// return var4;
|
// return var4;
|
||||||
// }
|
// }
|
||||||
fn collide(movement: &Vec3, world: &Instance, physics: &entity::Physics) -> Vec3 {
|
fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics) -> Vec3 {
|
||||||
let entity_bounding_box = physics.bounding_box;
|
let entity_bounding_box = physics.bounding_box;
|
||||||
// TODO: get_entity_collisions
|
// TODO: get_entity_collisions
|
||||||
// let entity_collisions = world.get_entity_collisions(self,
|
// let entity_collisions = world.get_entity_collisions(self,
|
||||||
|
@ -71,8 +71,8 @@ pub fn move_colliding(
|
||||||
_mover_type: &MoverType,
|
_mover_type: &MoverType,
|
||||||
movement: &Vec3,
|
movement: &Vec3,
|
||||||
world: &Instance,
|
world: &Instance,
|
||||||
position: &mut entity::Position,
|
position: &mut azalea_entity::Position,
|
||||||
physics: &mut entity::Physics,
|
physics: &mut azalea_entity::Physics,
|
||||||
) -> Result<(), MoveEntityError> {
|
) -> Result<(), MoveEntityError> {
|
||||||
// TODO: do all these
|
// TODO: do all these
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ pub fn move_colliding(
|
||||||
|
|
||||||
// TODO: minecraft checks for a "minor" horizontal collision here
|
// TODO: minecraft checks for a "minor" horizontal collision here
|
||||||
|
|
||||||
let _block_pos_below = entity::on_pos_legacy(&world.chunks, position);
|
let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, position);
|
||||||
// let _block_state_below = self
|
// let _block_state_below = self
|
||||||
// .world
|
// .world
|
||||||
// .get_block_state(&block_pos_below)
|
// .get_block_state(&block_pos_below)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::mergers::IndexMerger;
|
use super::mergers::IndexMerger;
|
||||||
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
binary_search, Axis, AxisCycle, BlockHitResult, BlockPos, Direction, Vec3, EPSILON,
|
math::binary_search, Axis, AxisCycle, BlockHitResult, BlockPos, Direction, Vec3, EPSILON,
|
||||||
};
|
};
|
||||||
use std::{cmp, num::NonZeroU32};
|
use std::{cmp, num::NonZeroU32};
|
||||||
|
|
||||||
|
|
|
@ -71,9 +71,7 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk = self.get_chunk(item.pos.x, item.pos.z);
|
let chunk = self.get_chunk(item.pos.x, item.pos.z);
|
||||||
let Some(chunk) = chunk else {
|
let Some(chunk) = chunk else { continue };
|
||||||
continue
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos = item.pos;
|
let pos = item.pos;
|
||||||
let block_state: BlockState = chunk
|
let block_state: BlockState = chunk
|
||||||
|
|
|
@ -6,13 +6,12 @@ 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_entity::update_bounding_box;
|
||||||
entity::{
|
use azalea_entity::{
|
||||||
clamp_look_direction, metadata::Sprinting, move_relative, Attributes, Jumping, Local,
|
clamp_look_direction, metadata::Sprinting, move_relative, Attributes, Jumping, Local,
|
||||||
LookDirection, Physics, Position, WorldName,
|
LookDirection, Physics, Position,
|
||||||
},
|
|
||||||
Instance, InstanceContainer,
|
|
||||||
};
|
};
|
||||||
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
@ -35,7 +34,7 @@ impl Plugin for PhysicsPlugin {
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
force_jump_listener
|
force_jump_listener
|
||||||
.before(azalea_world::entity::update_bounding_box)
|
.before(update_bounding_box)
|
||||||
.after(clamp_look_direction),
|
.after(clamp_look_direction),
|
||||||
)
|
)
|
||||||
.add_systems(FixedUpdate, (ai_step, travel).chain().in_set(PhysicsSet));
|
.add_systems(FixedUpdate, (ai_step, travel).chain().in_set(PhysicsSet));
|
||||||
|
@ -51,7 +50,7 @@ fn travel(
|
||||||
&mut LookDirection,
|
&mut LookDirection,
|
||||||
&mut Position,
|
&mut Position,
|
||||||
&Attributes,
|
&Attributes,
|
||||||
&WorldName,
|
&InstanceName,
|
||||||
),
|
),
|
||||||
With<Local>,
|
With<Local>,
|
||||||
>,
|
>,
|
||||||
|
@ -176,7 +175,7 @@ pub fn force_jump_listener(
|
||||||
&Position,
|
&Position,
|
||||||
&LookDirection,
|
&LookDirection,
|
||||||
&Sprinting,
|
&Sprinting,
|
||||||
&WorldName,
|
&InstanceName,
|
||||||
)>,
|
)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
mut events: EventReader<ForceJumpEvent>,
|
mut events: EventReader<ForceJumpEvent>,
|
||||||
|
@ -327,10 +326,8 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use azalea_core::{ChunkPos, ResourceLocation};
|
use azalea_core::{ChunkPos, ResourceLocation};
|
||||||
use azalea_world::{
|
use azalea_entity::{EntityBundle, EntityPlugin};
|
||||||
entity::{EntityBundle, EntityPlugin, MinecraftEntityId},
|
use azalea_world::{Chunk, MinecraftEntityId, PartialInstance};
|
||||||
Chunk, PartialInstance,
|
|
||||||
};
|
|
||||||
use bevy_app::App;
|
use bevy_app::App;
|
||||||
use bevy_time::fixed_timestep::FixedTime;
|
use bevy_time::fixed_timestep::FixedTime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
|
@ -25,6 +25,7 @@ azalea-core = { path = "../azalea-core", optional = true, version = "^0.7.0", fe
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
azalea-crypto = { path = "../azalea-crypto", version = "^0.7.0" }
|
azalea-crypto = { path = "../azalea-crypto", version = "^0.7.0" }
|
||||||
|
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||||
azalea-inventory = { version = "0.7.0", path = "../azalea-inventory" }
|
azalea-inventory = { version = "0.7.0", path = "../azalea-inventory" }
|
||||||
azalea-nbt = { path = "../azalea-nbt", version = "^0.7.0", features = [
|
azalea-nbt = { path = "../azalea-nbt", version = "^0.7.0", features = [
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::{ResourceLocation, Vec3};
|
use azalea_core::{ResourceLocation, Vec3};
|
||||||
|
use azalea_entity::{metadata::apply_default_metadata, EntityBundle};
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use azalea_world::entity::{metadata::apply_default_metadata, EntityBundle};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::{ResourceLocation, Vec3};
|
use azalea_core::{ResourceLocation, Vec3};
|
||||||
|
use azalea_entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle};
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use azalea_registry::EntityKind;
|
use azalea_registry::EntityKind;
|
||||||
use azalea_world::entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// This packet is sent by the server when a player comes into visible range,
|
/// This packet is sent by the server when a player comes into visible range,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
|
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
|
||||||
use azalea_core::ParticleData;
|
use azalea_core::particle::ParticleData;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
|
use azalea_entity::EntityMetadataItems;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use azalea_world::entity::EntityMetadataItems;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
pub struct ClientboundSetEntityDataPacket {
|
pub struct ClientboundSetEntityDataPacket {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use azalea_buf::McBuf;
|
use azalea_buf::McBuf;
|
||||||
use azalea_core::ResourceLocation;
|
use azalea_core::ResourceLocation;
|
||||||
|
use azalea_entity::attributes::AttributeModifier;
|
||||||
use azalea_protocol_macros::ClientboundGamePacket;
|
use azalea_protocol_macros::ClientboundGamePacket;
|
||||||
use azalea_world::entity::attributes::AttributeModifier;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||||
pub struct ClientboundUpdateAttributesPacket {
|
pub struct ClientboundUpdateAttributesPacket {
|
||||||
|
|
|
@ -221,10 +221,12 @@ impl McBufWritable for Recipe {
|
||||||
impl McBufReadable for Recipe {
|
impl McBufReadable for Recipe {
|
||||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||||
let recipe_serializer_name = ResourceLocation::read_from(buf)?;
|
let recipe_serializer_name = ResourceLocation::read_from(buf)?;
|
||||||
let Ok(recipe_serializer) =
|
let Ok(recipe_serializer) = RecipeSerializer::from_str(&recipe_serializer_name.to_string())
|
||||||
RecipeSerializer::from_str(&recipe_serializer_name.to_string()) else {
|
else {
|
||||||
return Err(BufReadError::UnexpectedStringEnumVariant { id: recipe_serializer_name.to_string() });
|
return Err(BufReadError::UnexpectedStringEnumVariant {
|
||||||
};
|
id: recipe_serializer_name.to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
let identifier = ResourceLocation::read_from(buf)?;
|
let identifier = ResourceLocation::read_from(buf)?;
|
||||||
|
|
||||||
// rust doesn't let us match ResourceLocation so we have to do a big
|
// rust doesn't let us match ResourceLocation so we have to do a big
|
||||||
|
|
|
@ -11,6 +11,7 @@ version = "0.7.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
azalea-buf = { path = "../azalea-buf", version = "^0.7.0" }
|
azalea-buf = { path = "../azalea-buf", version = "^0.7.0" }
|
||||||
azalea-registry-macros = { path = "./azalea-registry-macros", version = "^0.7.0" }
|
azalea-registry-macros = { path = "./azalea-registry-macros", version = "^0.7.0" }
|
||||||
|
once_cell = "1.18.0"
|
||||||
serde = { version = "^1.0", optional = true }
|
serde = { version = "^1.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
// auto-generated (so you can add doc comments to the registry enums if you
|
// auto-generated (so you can add doc comments to the registry enums if you
|
||||||
// want)
|
// want)
|
||||||
|
|
||||||
|
pub mod tags;
|
||||||
|
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
|
|
||||||
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
|
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
|
||||||
|
|
3374
azalea-registry/src/tags/blocks.rs
Normal file
3374
azalea-registry/src/tags/blocks.rs
Normal file
File diff suppressed because it is too large
Load diff
12
azalea-registry/src/tags/fluids.rs
Normal file
12
azalea-registry/src/tags/fluids.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// This file was generated by codegen/lib/code/tags.py, don't edit it manually!
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::Fluid;
|
||||||
|
|
||||||
|
pub static LAVA: Lazy<HashSet<Fluid>> =
|
||||||
|
Lazy::new(|| HashSet::from_iter(vec![Fluid::Lava, Fluid::FlowingLava]));
|
||||||
|
pub static WATER: Lazy<HashSet<Fluid>> =
|
||||||
|
Lazy::new(|| HashSet::from_iter(vec![Fluid::Water, Fluid::FlowingWater]));
|
1379
azalea-registry/src/tags/items.rs
Normal file
1379
azalea-registry/src/tags/items.rs
Normal file
File diff suppressed because it is too large
Load diff
3
azalea-registry/src/tags/mod.rs
Normal file
3
azalea-registry/src/tags/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod blocks;
|
||||||
|
pub mod fluids;
|
||||||
|
pub mod items;
|
|
@ -1,5 +1,6 @@
|
||||||
use azalea_core::ResourceLocation;
|
use azalea_core::ResourceLocation;
|
||||||
use bevy_ecs::system::Resource;
|
use bevy_ecs::{component::Component, system::Resource};
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
use log::error;
|
use log::error;
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -8,7 +9,7 @@ use std::{
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{entity::WorldName, ChunkStorage, Instance};
|
use crate::{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 +38,7 @@ impl InstanceContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a world from the container.
|
/// Get a world from the container.
|
||||||
pub fn get(&self, name: &WorldName) -> Option<Arc<RwLock<Instance>>> {
|
pub fn get(&self, name: &InstanceName) -> Option<Arc<RwLock<Instance>>> {
|
||||||
self.worlds.get(name).and_then(|world| world.upgrade())
|
self.worlds.get(name).and_then(|world| world.upgrade())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,3 +77,9 @@ impl InstanceContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name of the [`Instance`](crate::Instance) (world) the entity is
|
||||||
|
/// in. If two entities share the same world name, we assume they're in the same
|
||||||
|
/// instance.
|
||||||
|
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||||
|
pub struct InstanceName(pub ResourceLocation);
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
mod bit_storage;
|
mod bit_storage;
|
||||||
mod chunk_storage;
|
mod chunk_storage;
|
||||||
mod container;
|
mod container;
|
||||||
pub mod entity;
|
|
||||||
pub mod iterators;
|
pub mod iterators;
|
||||||
pub mod palette;
|
pub mod palette;
|
||||||
mod world;
|
mod world;
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
use crate::{
|
use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, PartialChunkStorage};
|
||||||
entity::{
|
use azalea_block::{BlockState, BlockStates, FluidState};
|
||||||
EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName,
|
|
||||||
},
|
|
||||||
iterators::ChunkIterator,
|
|
||||||
palette::Palette,
|
|
||||||
ChunkStorage, InstanceContainer, PartialChunkStorage,
|
|
||||||
};
|
|
||||||
use azalea_block::{BlockState, BlockStates};
|
|
||||||
use azalea_core::{BlockPos, ChunkPos};
|
use azalea_core::{BlockPos, ChunkPos};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{component::Component, entity::Entity};
|
||||||
entity::Entity,
|
use derive_more::{Deref, DerefMut};
|
||||||
query::{Changed, With, Without},
|
|
||||||
system::{Commands, Query, Res, ResMut},
|
|
||||||
};
|
|
||||||
use log::{debug, error, info};
|
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -45,133 +34,45 @@ impl PartialInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove new entities that have the same id as an existing entity, and
|
/// An entity ID used by Minecraft. These are not guaranteed to be unique in
|
||||||
/// increase the reference counts.
|
/// shared worlds, that's what [`Entity`] is for.
|
||||||
///
|
#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)]
|
||||||
/// This is the reason why spawning entities into the ECS when you get a spawn
|
pub struct MinecraftEntityId(pub u32);
|
||||||
/// entity packet is okay. This system will make sure the new entity gets
|
|
||||||
/// combined into the old one.
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn deduplicate_entities(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut query: Query<
|
|
||||||
(Entity, &MinecraftEntityId, &WorldName),
|
|
||||||
(Changed<MinecraftEntityId>, Without<Local>),
|
|
||||||
>,
|
|
||||||
mut loaded_by_query: Query<&mut LoadedBy>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
) {
|
|
||||||
// if this entity already exists, remove it
|
|
||||||
for (new_entity, id, world_name) in query.iter_mut() {
|
|
||||||
if let Some(world_lock) = instance_container.get(world_name) {
|
|
||||||
let world = world_lock.write();
|
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
|
||||||
if old_entity == &new_entity {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this entity already exists!!! remove the one we just added but increase
|
impl std::hash::Hash for MinecraftEntityId {
|
||||||
// the reference count
|
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
||||||
let new_loaded_by = loaded_by_query
|
hasher.write_u32(self.0);
|
||||||
.get(new_entity)
|
}
|
||||||
.unwrap_or_else(|_| panic!(
|
}
|
||||||
"Entities should always have the LoadedBy component ({new_entity:?} did not)"
|
impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
||||||
))
|
|
||||||
.clone();
|
/// Keep track of certain metadatas that are only relevant for this partial
|
||||||
let old_loaded_by = loaded_by_query.get_mut(*old_entity);
|
/// world.
|
||||||
// merge them if possible
|
#[derive(Debug, Default)]
|
||||||
if let Ok(mut old_loaded_by) = old_loaded_by {
|
pub struct PartialEntityInfos {
|
||||||
old_loaded_by.extend(new_loaded_by.iter());
|
// note: using MinecraftEntityId for entity ids is acceptable here since
|
||||||
}
|
// there's no chance of collisions here
|
||||||
commands.entity(new_entity).despawn();
|
/// The entity id of the player that owns this partial world. This will
|
||||||
info!(
|
/// make `RelativeEntityUpdate` pretend the entity doesn't exist so
|
||||||
"Entity with id {id:?} / {new_entity:?} already existed in the world, merging it with {old_entity:?}"
|
/// it doesn't get modified from outside sources.
|
||||||
);
|
pub owner_entity: Option<Entity>,
|
||||||
break;
|
/// A counter for each entity that tracks how many updates we've observed
|
||||||
}
|
/// for it.
|
||||||
} else {
|
///
|
||||||
error!("Entity was inserted into a world that doesn't exist.");
|
/// This is used for shared worlds (i.e. swarms), to make sure we don't
|
||||||
|
/// update entities twice on accident.
|
||||||
|
pub updates_received: IntMap<MinecraftEntityId, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEntityInfos {
|
||||||
|
pub fn new(owner_entity: Option<Entity>) -> Self {
|
||||||
|
Self {
|
||||||
|
owner_entity,
|
||||||
|
updates_received: IntMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when a local entity is added, if there was already an entity with the same id
|
|
||||||
// then delete the old entity
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn deduplicate_local_entities(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut query: Query<
|
|
||||||
(Entity, &MinecraftEntityId, &WorldName),
|
|
||||||
(Changed<MinecraftEntityId>, With<Local>),
|
|
||||||
>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
) {
|
|
||||||
// if this entity already exists, remove the old one
|
|
||||||
for (new_entity, id, world_name) in query.iter_mut() {
|
|
||||||
if let Some(world_lock) = instance_container.get(world_name) {
|
|
||||||
let world = world_lock.write();
|
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
|
||||||
if old_entity == &new_entity {
|
|
||||||
// lol
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.entity(*old_entity).despawn();
|
|
||||||
debug!(
|
|
||||||
"Added local entity {id:?} / {new_entity:?} but already existed in world as {old_entity:?}, despawning {old_entity:?}"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("Entity was inserted into a world that doesn't exist.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_uuid_index(
|
|
||||||
mut entity_infos: ResMut<EntityInfos>,
|
|
||||||
query: Query<(Entity, &EntityUuid, Option<&Local>), Changed<EntityUuid>>,
|
|
||||||
) {
|
|
||||||
for (entity, &uuid, local) in query.iter() {
|
|
||||||
// only add it if it doesn't already exist in
|
|
||||||
// entity_infos.entity_by_uuid
|
|
||||||
if local.is_none() {
|
|
||||||
if let Some(old_entity) = entity_infos.entity_by_uuid.get(&uuid) {
|
|
||||||
debug!(
|
|
||||||
"Entity with UUID {uuid:?} already existed in the world, not adding to
|
|
||||||
index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entity_infos.entity_by_uuid.insert(*uuid, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// Clear all entities in a chunk. This will not clear them from the
|
|
||||||
// /// shared storage unless there are no other references to them.
|
|
||||||
// pub fn clear_entities_in_chunk(
|
|
||||||
// mut commands: Commands,
|
|
||||||
// partial_entity_infos: &mut PartialEntityInfos,
|
|
||||||
// chunk: &ChunkPos,
|
|
||||||
// instance_container: &WorldContainer,
|
|
||||||
// world_name: &WorldName,
|
|
||||||
// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>,
|
|
||||||
// ) {
|
|
||||||
// let world_lock = instance_container.get(world_name).unwrap();
|
|
||||||
// let world = world_lock.read();
|
|
||||||
|
|
||||||
// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
|
||||||
// for &entity in &entities {
|
|
||||||
// let (id, mut reference_count) = query.get_mut(entity).unwrap();
|
|
||||||
// if partial_entity_infos.loaded_entity_ids.remove(id) {
|
|
||||||
// // decrease the reference count
|
|
||||||
// **reference_count -= 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// A world where the chunks are stored as weak pointers. This is used for
|
/// A world where the chunks are stored as weak pointers. This is used for
|
||||||
/// shared worlds.
|
/// shared worlds.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
@ -191,6 +92,18 @@ impl Instance {
|
||||||
self.entity_by_id.get(entity_id).copied()
|
self.entity_by_id.get(entity_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
|
||||||
|
self.chunks.get_block_state(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fluid_state(&self, pos: &BlockPos) -> Option<FluidState> {
|
||||||
|
self.chunks.get_block_state(pos).map(FluidState::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> {
|
||||||
|
self.chunks.set_block_state(pos, state)
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the coordinates of a block in the world.
|
/// Find the coordinates of a block in the world.
|
||||||
///
|
///
|
||||||
/// 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
|
||||||
|
@ -290,31 +203,6 @@ impl Default for PartialInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// System to keep the entity_by_id index up-to-date.
|
|
||||||
pub fn update_entity_by_id_index(
|
|
||||||
mut query: Query<
|
|
||||||
(Entity, &MinecraftEntityId, &WorldName, Option<&Local>),
|
|
||||||
Changed<MinecraftEntityId>,
|
|
||||||
>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
) {
|
|
||||||
for (entity, id, world_name, local) in query.iter_mut() {
|
|
||||||
let world_lock = instance_container.get(world_name).unwrap();
|
|
||||||
let mut world = world_lock.write();
|
|
||||||
if local.is_none() {
|
|
||||||
if let Some(old_entity) = world.entity_by_id.get(id) {
|
|
||||||
debug!(
|
|
||||||
"Entity with ID {id:?} already existed in the world, not adding to
|
|
||||||
index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
world.entity_by_id.insert(*id, entity);
|
|
||||||
debug!("Added {entity:?} to {world_name:?} with {id:?}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ChunkStorage> for Instance {
|
impl From<ChunkStorage> for Instance {
|
||||||
/// Make an empty world from this `ChunkStorage`. This is meant to be a
|
/// Make an empty world from this `ChunkStorage`. This is meant to be a
|
||||||
/// convenience function for tests.
|
/// convenience function for tests.
|
||||||
|
|
|
@ -40,6 +40,7 @@ thiserror = "^1.0.43"
|
||||||
tokio = "^1.29.1"
|
tokio = "^1.29.1"
|
||||||
uuid = "1.4.0"
|
uuid = "1.4.0"
|
||||||
bevy_log = "0.11.0"
|
bevy_log = "0.11.0"
|
||||||
|
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["log"]
|
default = ["log"]
|
||||||
|
|
|
@ -89,7 +89,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
bot.disconnect();
|
bot.disconnect();
|
||||||
}
|
}
|
||||||
let Some(sender) = m.username() else {
|
let Some(sender) = m.username() else {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
};
|
};
|
||||||
// let mut ecs = bot.ecs.lock();
|
// let mut ecs = bot.ecs.lock();
|
||||||
// let entity = bot
|
// let entity = bot
|
||||||
|
@ -164,6 +164,21 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
bot.chat("no diamond block found");
|
bot.chat("no diamond block found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"mineblock" => {
|
||||||
|
let target_pos = bot
|
||||||
|
.world()
|
||||||
|
.read()
|
||||||
|
.find_block(bot.position(), &azalea::Block::DiamondBlock.into());
|
||||||
|
if let Some(target_pos) = target_pos {
|
||||||
|
// +1 to stand on top of the block
|
||||||
|
bot.chat("ok mining diamond block");
|
||||||
|
bot.look_at(target_pos.center());
|
||||||
|
bot.mine(target_pos).await;
|
||||||
|
bot.chat("finished mining");
|
||||||
|
} else {
|
||||||
|
bot.chat("no diamond block found");
|
||||||
|
}
|
||||||
|
}
|
||||||
"lever" => {
|
"lever" => {
|
||||||
let target_pos = bot
|
let target_pos = bot
|
||||||
.world()
|
.world()
|
||||||
|
@ -171,7 +186,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
.find_block(bot.position(), &azalea::Block::Lever.into());
|
.find_block(bot.position(), &azalea::Block::Lever.into());
|
||||||
let Some(target_pos) = target_pos else {
|
let Some(target_pos) = target_pos else {
|
||||||
bot.chat("no lever found");
|
bot.chat("no lever found");
|
||||||
return Ok(())
|
return Ok(());
|
||||||
};
|
};
|
||||||
bot.goto(BlockPosGoal::from(target_pos));
|
bot.goto(BlockPosGoal::from(target_pos));
|
||||||
bot.look_at(target_pos.center());
|
bot.look_at(target_pos.center());
|
||||||
|
@ -188,7 +203,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
||||||
.find_block(bot.position(), &azalea::Block::Chest.into());
|
.find_block(bot.position(), &azalea::Block::Chest.into());
|
||||||
let Some(target_pos) = target_pos else {
|
let Some(target_pos) = target_pos else {
|
||||||
bot.chat("no chest found");
|
bot.chat("no chest found");
|
||||||
return Ok(())
|
return Ok(());
|
||||||
};
|
};
|
||||||
bot.look_at(target_pos.center());
|
bot.look_at(target_pos.center());
|
||||||
let container = bot.open_container(target_pos).await;
|
let container = bot.open_container(target_pos).await;
|
||||||
|
|
|
@ -9,9 +9,10 @@ use crate::ecs::{
|
||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
};
|
};
|
||||||
use azalea_core::Vec3;
|
use azalea_core::Vec3;
|
||||||
|
use azalea_entity::{
|
||||||
|
clamp_look_direction, metadata::Player, EyeHeight, Jumping, Local, LookDirection, Position,
|
||||||
|
};
|
||||||
use azalea_physics::{force_jump_listener, PhysicsSet};
|
use azalea_physics::{force_jump_listener, PhysicsSet};
|
||||||
use azalea_world::entity::{clamp_look_direction, EyeHeight, LookDirection};
|
|
||||||
use azalea_world::entity::{metadata::Player, Jumping, Local, Position};
|
|
||||||
use bevy_app::{FixedUpdate, Update};
|
use bevy_app::{FixedUpdate, Update};
|
||||||
use bevy_ecs::prelude::Event;
|
use bevy_ecs::prelude::Event;
|
||||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
mod auto_respawn;
|
mod auto_respawn;
|
||||||
mod bot;
|
mod bot;
|
||||||
mod container;
|
mod container;
|
||||||
|
pub mod mining;
|
||||||
pub mod pathfinder;
|
pub mod pathfinder;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod swarm;
|
pub mod swarm;
|
||||||
|
@ -17,9 +18,10 @@ pub use azalea_brigadier as brigadier;
|
||||||
pub use azalea_chat::FormattedText;
|
pub use azalea_chat::FormattedText;
|
||||||
pub use azalea_client::*;
|
pub use azalea_client::*;
|
||||||
pub use azalea_core::{BlockPos, Vec3};
|
pub use azalea_core::{BlockPos, Vec3};
|
||||||
|
pub use azalea_entity as entity;
|
||||||
pub use azalea_protocol as protocol;
|
pub use azalea_protocol as protocol;
|
||||||
pub use azalea_registry::{Block, EntityKind, Item};
|
pub use azalea_registry::{Block, EntityKind, Item};
|
||||||
pub use azalea_world::{entity, Instance};
|
pub use azalea_world::Instance;
|
||||||
pub use bot::DefaultBotPlugins;
|
pub use bot::DefaultBotPlugins;
|
||||||
use ecs::component::Component;
|
use ecs::component::Component;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
|
40
azalea/src/mining.rs
Normal file
40
azalea/src/mining.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use azalea_client::{
|
||||||
|
interact::SwingArmEvent,
|
||||||
|
mining::{Mining, StartMiningBlockEvent},
|
||||||
|
Client, TickBroadcast,
|
||||||
|
};
|
||||||
|
use azalea_core::BlockPos;
|
||||||
|
|
||||||
|
pub trait MiningExt {
|
||||||
|
/// Start mining a block.
|
||||||
|
async fn mine(&mut self, position: BlockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MiningExt for Client {
|
||||||
|
/// Start mining a block. This won't turn the bot's head towards the block,
|
||||||
|
/// so you'll have to do that yourself with [`look_at`].
|
||||||
|
///
|
||||||
|
/// [`look_at`]: crate::prelude::BotClientExt::look_at
|
||||||
|
async fn mine(&mut self, position: BlockPos) {
|
||||||
|
self.ecs.lock().send_event(StartMiningBlockEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
// vanilla sends an extra swing arm packet when we start mining
|
||||||
|
self.ecs.lock().send_event(SwingArmEvent {
|
||||||
|
entity: self.entity,
|
||||||
|
});
|
||||||
|
|
||||||
|
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::<Mining>(self.entity).is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,11 @@ use crate::ecs::{
|
||||||
use astar::Edge;
|
use astar::Edge;
|
||||||
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
use azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||||
use azalea_core::{BlockPos, CardinalDirection};
|
use azalea_core::{BlockPos, CardinalDirection};
|
||||||
|
use azalea_entity::metadata::Player;
|
||||||
|
use azalea_entity::Local;
|
||||||
|
use azalea_entity::{Physics, Position};
|
||||||
use azalea_physics::PhysicsSet;
|
use azalea_physics::PhysicsSet;
|
||||||
use azalea_world::entity::metadata::Player;
|
use azalea_world::{InstanceContainer, InstanceName};
|
||||||
use azalea_world::entity::Local;
|
|
||||||
use azalea_world::{
|
|
||||||
entity::{Physics, Position, WorldName},
|
|
||||||
InstanceContainer,
|
|
||||||
};
|
|
||||||
use bevy_app::{FixedUpdate, Update};
|
use bevy_app::{FixedUpdate, Update};
|
||||||
use bevy_ecs::prelude::Event;
|
use bevy_ecs::prelude::Event;
|
||||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||||
|
@ -106,7 +104,7 @@ pub struct ComputePath(Task<Option<PathFoundEvent>>);
|
||||||
fn goto_listener(
|
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, &InstanceName)>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
let thread_pool = AsyncComputeTaskPool::get();
|
let thread_pool = AsyncComputeTaskPool::get();
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
//! re-exported here.
|
//! re-exported here.
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
bot::BotClientExt, container::ContainerClientExt, pathfinder::PathfinderClientExt,
|
bot::BotClientExt, container::ContainerClientExt, mining::MiningExt,
|
||||||
ClientBuilder,
|
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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use azalea_client::LocalPlayer;
|
use azalea_client::LocalPlayer;
|
||||||
use azalea_world::entity::MinecraftEntityId;
|
use azalea_world::MinecraftEntityId;
|
||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, Update};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
4
codegen/.gitignore
vendored
4
codegen/.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
downloads
|
|
||||||
__pycache__
|
__pycache__
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
|
downloads
|
||||||
|
__cache__
|
||||||
|
|
|
@ -20,7 +20,7 @@ ordered_blocks = lib.extract.get_ordered_blocks_burger(version_id)
|
||||||
block_states_report = lib.extract.get_block_states_report(version_id)
|
block_states_report = lib.extract.get_block_states_report(version_id)
|
||||||
|
|
||||||
lib.code.blocks.generate_blocks(
|
lib.code.blocks.generate_blocks(
|
||||||
block_states_burger, block_states_report, ordered_blocks, mappings)
|
block_states_burger, block_states_report, pixlyzer_block_datas, ordered_blocks, mappings)
|
||||||
|
|
||||||
lib.code.shapes.generate_block_shapes(
|
lib.code.shapes.generate_block_shapes(
|
||||||
pixlyzer_block_datas, shape_datas['shapes'], shape_datas['aabbs'], block_states_report, block_states_burger, mappings)
|
pixlyzer_block_datas, shape_datas['shapes'], shape_datas['aabbs'], block_states_report, block_states_burger, mappings)
|
||||||
|
|
|
@ -3,16 +3,30 @@ import lib.code.registry
|
||||||
import lib.code.version
|
import lib.code.version
|
||||||
import lib.code.packet
|
import lib.code.packet
|
||||||
import lib.code.utils
|
import lib.code.utils
|
||||||
|
import lib.code.tags
|
||||||
import lib.download
|
import lib.download
|
||||||
import lib.extract
|
import lib.extract
|
||||||
import lib.utils
|
import lib.utils
|
||||||
|
|
||||||
version_id = lib.code.version.get_version_id()
|
def generate(version_id: str):
|
||||||
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.inventory.update_menus(registries['minecraft:menu']['entries'])
|
||||||
|
|
||||||
lib.code.utils.fmt()
|
|
||||||
|
|
||||||
print('Done!')
|
block_tags = lib.extract.get_registry_tags(version_id, 'blocks')
|
||||||
|
item_tags = lib.extract.get_registry_tags(version_id, 'items')
|
||||||
|
fluid_tags = lib.extract.get_registry_tags(version_id, 'fluids')
|
||||||
|
|
||||||
|
lib.code.tags.generate_tags(block_tags, 'blocks', 'Block')
|
||||||
|
lib.code.tags.generate_tags(item_tags, 'items', 'Item')
|
||||||
|
lib.code.tags.generate_tags(fluid_tags, 'fluids', 'Fluid')
|
||||||
|
|
||||||
|
lib.code.utils.fmt()
|
||||||
|
|
||||||
|
print('Done!')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
version_id = lib.code.version.get_version_id()
|
||||||
|
generate(version_id)
|
||||||
|
|
|
@ -13,7 +13,7 @@ BLOCKS_RS_DIR = get_dir_location('../azalea-block/src/generated.rs')
|
||||||
# - Block: Has properties and states.
|
# - Block: Has properties and states.
|
||||||
|
|
||||||
|
|
||||||
def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: list[str], mappings: Mappings):
|
def generate_blocks(blocks_burger: dict, blocks_report: dict, pixlyzer_block_datas: dict, ordered_blocks: list[str], mappings: Mappings):
|
||||||
with open(BLOCKS_RS_DIR, 'r') as f:
|
with open(BLOCKS_RS_DIR, 'r') as f:
|
||||||
existing_code = f.read().splitlines()
|
existing_code = f.read().splitlines()
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
||||||
for block_id in ordered_blocks:
|
for block_id in ordered_blocks:
|
||||||
block_data_burger = blocks_burger[block_id]
|
block_data_burger = blocks_burger[block_id]
|
||||||
block_data_report = blocks_report['minecraft:' + block_id]
|
block_data_report = blocks_report['minecraft:' + block_id]
|
||||||
|
block_data_pixlyzer = pixlyzer_block_datas[f'minecraft:{block_id}']
|
||||||
|
|
||||||
block_properties = block_data_burger.get('states', [])
|
block_properties = block_data_burger.get('states', [])
|
||||||
block_properties_burger = block_data_burger.get('states', [])
|
block_properties_burger = block_data_burger.get('states', [])
|
||||||
|
@ -134,9 +135,28 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
||||||
else:
|
else:
|
||||||
properties_code += '\n }'
|
properties_code += '\n }'
|
||||||
|
|
||||||
|
# make the block behavior
|
||||||
|
behavior_constructor = 'BlockBehavior::new()'
|
||||||
|
# requires tool
|
||||||
|
if block_data_pixlyzer.get('requires_tool'):
|
||||||
|
behavior_constructor += '.requires_correct_tool_for_drops()'
|
||||||
|
# strength
|
||||||
|
destroy_time = block_data_pixlyzer.get('hardness')
|
||||||
|
explosion_resistance = block_data_pixlyzer.get('explosion_resistance')
|
||||||
|
if destroy_time and explosion_resistance:
|
||||||
|
behavior_constructor += f'.strength({destroy_time}, {explosion_resistance})'
|
||||||
|
elif destroy_time:
|
||||||
|
behavior_constructor += f'.destroy_time({destroy_time})'
|
||||||
|
elif explosion_resistance:
|
||||||
|
behavior_constructor += f'.explosion_resistance({explosion_resistance})'
|
||||||
|
# friction
|
||||||
|
friction = block_data_pixlyzer.get('friction')
|
||||||
|
if friction != None:
|
||||||
|
behavior_constructor += f'.friction({friction})'
|
||||||
|
|
||||||
# TODO: use burger to generate the blockbehavior
|
# TODO: use burger to generate the blockbehavior
|
||||||
new_make_block_states_macro_code.append(
|
new_make_block_states_macro_code.append(
|
||||||
f' {block_id} => BlockBehavior::default(), {properties_code},')
|
f' {block_id} => {behavior_constructor}, {properties_code},')
|
||||||
|
|
||||||
new_make_block_states_macro_code.append(' }')
|
new_make_block_states_macro_code.append(' }')
|
||||||
new_make_block_states_macro_code.append('}')
|
new_make_block_states_macro_code.append('}')
|
||||||
|
|
|
@ -66,7 +66,7 @@ def simplify_shapes(blocks: dict, shapes: dict, aabbs: dict):
|
||||||
|
|
||||||
|
|
||||||
def generate_block_shapes_code(blocks: dict, shapes: dict, block_states_report, block_datas_burger, mappings: Mappings):
|
def generate_block_shapes_code(blocks: dict, shapes: dict, block_states_report, block_datas_burger, mappings: Mappings):
|
||||||
# look at downloads/generator-mod-*/blockCollisionShapes.json for format of blocks and shapes
|
# look at __cache__/generator-mod-*/blockCollisionShapes.json for format of blocks and shapes
|
||||||
|
|
||||||
generated_shape_code = ''
|
generated_shape_code = ''
|
||||||
for (shape_id, shape) in sorted(shapes.items(), key=lambda shape: int(shape[0])):
|
for (shape_id, shape) in sorted(shapes.items(), key=lambda shape: int(shape[0])):
|
||||||
|
|
35
codegen/lib/code/tags.py
Normal file
35
codegen/lib/code/tags.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from lib.utils import to_snake_case, upper_first_letter, get_dir_location, to_camel_case
|
||||||
|
|
||||||
|
REGISTRIES_DIR = get_dir_location('../azalea-registry/src/tags')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_tags(registries: dict, file_name: str, struct_name: str):
|
||||||
|
tags_dir = f'{REGISTRIES_DIR}/{file_name}.rs'
|
||||||
|
|
||||||
|
generated = f'''// This file was generated by codegen/lib/code/tags.py, don't edit it manually!
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::{struct_name};
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
for tag_name, tag in registries.items():
|
||||||
|
tag_name = tag_name.replace('/', '_')
|
||||||
|
static_set_name = to_snake_case(tag_name).upper()
|
||||||
|
generated += f'pub static {static_set_name}: Lazy<HashSet<{struct_name}>> = Lazy::new(|| HashSet::from_iter(vec!['
|
||||||
|
|
||||||
|
queue = tag['values'].copy()
|
||||||
|
while queue != []:
|
||||||
|
item = queue.pop(0)
|
||||||
|
namespace, item_name = item.split(':')
|
||||||
|
if namespace[0] == '#':
|
||||||
|
queue += registries[item_name]['values']
|
||||||
|
continue
|
||||||
|
generated += f'{struct_name}::{upper_first_letter(to_camel_case(item_name))},\n'
|
||||||
|
generated += ']));\n'
|
||||||
|
|
||||||
|
with open(tags_dir, 'w') as f:
|
||||||
|
f.write(generated)
|
|
@ -5,47 +5,47 @@ import requests
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# make sure the downloads directory exists
|
# make sure the cache directory exists
|
||||||
print('Making downloads')
|
print('Making __cache__')
|
||||||
if not os.path.exists(get_dir_location('downloads')):
|
if not os.path.exists(get_dir_location('__cache__')):
|
||||||
print('Made downloads directory.', get_dir_location('downloads'))
|
print('Made __cache__ directory.', get_dir_location('__cache__'))
|
||||||
os.mkdir(get_dir_location('downloads'))
|
os.mkdir(get_dir_location('__cache__'))
|
||||||
|
|
||||||
|
|
||||||
def get_burger():
|
def get_burger():
|
||||||
if not os.path.exists(get_dir_location('downloads/Burger')):
|
if not os.path.exists(get_dir_location('__cache__/Burger')):
|
||||||
print('\033[92mDownloading Burger...\033[m')
|
print('\033[92mDownloading Burger...\033[m')
|
||||||
os.system(
|
os.system(
|
||||||
f'cd {get_dir_location("downloads")} && git clone https://github.com/pokechu22/Burger && cd Burger && git pull')
|
f'cd {get_dir_location("__cache__")} && git clone https://github.com/pokechu22/Burger && cd Burger && git pull')
|
||||||
|
|
||||||
print('\033[92mInstalling dependencies...\033[m')
|
print('\033[92mInstalling dependencies...\033[m')
|
||||||
os.system(f'cd {get_dir_location("downloads")}/Burger && pip install six jawa')
|
os.system(f'cd {get_dir_location("__cache__")}/Burger && pip install six jawa')
|
||||||
|
|
||||||
|
|
||||||
def get_pixlyzer():
|
def get_pixlyzer():
|
||||||
if not os.path.exists(get_dir_location('downloads/pixlyzer')):
|
if not os.path.exists(get_dir_location('__cache__/pixlyzer')):
|
||||||
print('\033[92mDownloading bixilon/pixlyzer...\033[m')
|
print('\033[92mDownloading bixilon/pixlyzer...\033[m')
|
||||||
os.system(
|
os.system(
|
||||||
f'cd {get_dir_location("downloads")} && git clone https://gitlab.bixilon.de/bixilon/pixlyzer.git && cd pixlyzer && git pull')
|
f'cd {get_dir_location("__cache__")} && git clone https://gitlab.bixilon.de/bixilon/pixlyzer.git && cd pixlyzer && git pull')
|
||||||
return get_dir_location('downloads/pixlyzer')
|
return get_dir_location('__cache__/pixlyzer')
|
||||||
|
|
||||||
|
|
||||||
def get_version_manifest():
|
def get_version_manifest():
|
||||||
if not os.path.exists(get_dir_location(f'downloads/version_manifest.json')):
|
if not os.path.exists(get_dir_location(f'__cache__/version_manifest.json')):
|
||||||
print(
|
print(
|
||||||
f'\033[92mDownloading version manifest...\033[m')
|
f'\033[92mDownloading version manifest...\033[m')
|
||||||
version_manifest_data = requests.get(
|
version_manifest_data = requests.get(
|
||||||
'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json').json()
|
'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json').json()
|
||||||
with open(get_dir_location(f'downloads/version_manifest.json'), 'w') as f:
|
with open(get_dir_location(f'__cache__/version_manifest.json'), 'w') as f:
|
||||||
json.dump(version_manifest_data, f)
|
json.dump(version_manifest_data, f)
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location(f'downloads/version_manifest.json'), 'r') as f:
|
with open(get_dir_location(f'__cache__/version_manifest.json'), 'r') as f:
|
||||||
version_manifest_data = json.load(f)
|
version_manifest_data = json.load(f)
|
||||||
return version_manifest_data
|
return version_manifest_data
|
||||||
|
|
||||||
|
|
||||||
def get_version_data(version_id: str):
|
def get_version_data(version_id: str):
|
||||||
if not os.path.exists(get_dir_location(f'downloads/{version_id}.json')):
|
if not os.path.exists(get_dir_location(f'__cache__/{version_id}.json')):
|
||||||
version_manifest_data = get_version_manifest()
|
version_manifest_data = get_version_manifest()
|
||||||
|
|
||||||
print(
|
print(
|
||||||
|
@ -55,60 +55,60 @@ def get_version_data(version_id: str):
|
||||||
filter(lambda v: v['id'] == version_id, version_manifest_data['versions']))['url']
|
filter(lambda v: v['id'] == version_id, version_manifest_data['versions']))['url']
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'No version with id {version_id} found. Maybe delete downloads/version_manifest.json and try again?')
|
f'No version with id {version_id} found. Maybe delete __cache__/version_manifest.json and try again?')
|
||||||
package_data = requests.get(package_url).json()
|
package_data = requests.get(package_url).json()
|
||||||
with open(get_dir_location(f'downloads/{version_id}.json'), 'w') as f:
|
with open(get_dir_location(f'__cache__/{version_id}.json'), 'w') as f:
|
||||||
json.dump(package_data, f)
|
json.dump(package_data, f)
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location(f'downloads/{version_id}.json'), 'r') as f:
|
with open(get_dir_location(f'__cache__/{version_id}.json'), 'r') as f:
|
||||||
package_data = json.load(f)
|
package_data = json.load(f)
|
||||||
return package_data
|
return package_data
|
||||||
|
|
||||||
|
|
||||||
def get_client_jar(version_id: str):
|
def get_client_jar(version_id: str):
|
||||||
if not os.path.exists(get_dir_location(f'downloads/client-{version_id}.jar')):
|
if not os.path.exists(get_dir_location(f'__cache__/client-{version_id}.jar')):
|
||||||
package_data = get_version_data(version_id)
|
package_data = get_version_data(version_id)
|
||||||
print('\033[92mDownloading client jar...\033[m')
|
print('\033[92mDownloading client jar...\033[m')
|
||||||
client_jar_url = package_data['downloads']['client']['url']
|
client_jar_url = package_data['downloads']['client']['url']
|
||||||
with open(get_dir_location(f'downloads/client-{version_id}.jar'), 'wb') as f:
|
with open(get_dir_location(f'__cache__/client-{version_id}.jar'), 'wb') as f:
|
||||||
f.write(requests.get(client_jar_url).content)
|
f.write(requests.get(client_jar_url).content)
|
||||||
|
|
||||||
|
|
||||||
def get_server_jar(version_id: str):
|
def get_server_jar(version_id: str):
|
||||||
if not os.path.exists(get_dir_location(f'downloads/server-{version_id}.jar')):
|
if not os.path.exists(get_dir_location(f'__cache__/server-{version_id}.jar')):
|
||||||
package_data = get_version_data(version_id)
|
package_data = get_version_data(version_id)
|
||||||
print('\033[92mDownloading server jar...\033[m')
|
print('\033[92mDownloading server jar...\033[m')
|
||||||
server_jar_url = package_data['downloads']['server']['url']
|
server_jar_url = package_data['downloads']['server']['url']
|
||||||
with open(get_dir_location(f'downloads/server-{version_id}.jar'), 'wb') as f:
|
with open(get_dir_location(f'__cache__/server-{version_id}.jar'), 'wb') as f:
|
||||||
f.write(requests.get(server_jar_url).content)
|
f.write(requests.get(server_jar_url).content)
|
||||||
|
|
||||||
|
|
||||||
def get_mappings_for_version(version_id: str):
|
def get_mappings_for_version(version_id: str):
|
||||||
if not os.path.exists(get_dir_location(f'downloads/mappings-{version_id}.txt')):
|
if not os.path.exists(get_dir_location(f'__cache__/mappings-{version_id}.txt')):
|
||||||
package_data = get_version_data(version_id)
|
package_data = get_version_data(version_id)
|
||||||
|
|
||||||
client_mappings_url = package_data['downloads']['client_mappings']['url']
|
client_mappings_url = package_data['downloads']['client_mappings']['url']
|
||||||
|
|
||||||
mappings_text = requests.get(client_mappings_url).text
|
mappings_text = requests.get(client_mappings_url).text
|
||||||
|
|
||||||
with open(get_dir_location(f'downloads/mappings-{version_id}.txt'), 'w') as f:
|
with open(get_dir_location(f'__cache__/mappings-{version_id}.txt'), 'w') as f:
|
||||||
f.write(mappings_text)
|
f.write(mappings_text)
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location(f'downloads/mappings-{version_id}.txt'), 'r') as f:
|
with open(get_dir_location(f'__cache__/mappings-{version_id}.txt'), 'r') as f:
|
||||||
mappings_text = f.read()
|
mappings_text = f.read()
|
||||||
return Mappings.parse(mappings_text)
|
return Mappings.parse(mappings_text)
|
||||||
|
|
||||||
|
|
||||||
def get_yarn_versions():
|
def get_yarn_versions():
|
||||||
# https://meta.fabricmc.net/v2/versions/yarn
|
# https://meta.fabricmc.net/v2/versions/yarn
|
||||||
if not os.path.exists(get_dir_location('downloads/yarn_versions.json')):
|
if not os.path.exists(get_dir_location('__cache__/yarn_versions.json')):
|
||||||
print('\033[92mDownloading yarn versions...\033[m')
|
print('\033[92mDownloading yarn versions...\033[m')
|
||||||
yarn_versions_data = requests.get(
|
yarn_versions_data = requests.get(
|
||||||
'https://meta.fabricmc.net/v2/versions/yarn').json()
|
'https://meta.fabricmc.net/v2/versions/yarn').json()
|
||||||
with open(get_dir_location('downloads/yarn_versions.json'), 'w') as f:
|
with open(get_dir_location('__cache__/yarn_versions.json'), 'w') as f:
|
||||||
json.dump(yarn_versions_data, f)
|
json.dump(yarn_versions_data, f)
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location('downloads/yarn_versions.json'), 'r') as f:
|
with open(get_dir_location('__cache__/yarn_versions.json'), 'r') as f:
|
||||||
yarn_versions_data = json.load(f)
|
yarn_versions_data = json.load(f)
|
||||||
return yarn_versions_data
|
return yarn_versions_data
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ def get_yarn_data(version_id: str):
|
||||||
|
|
||||||
def get_fabric_api_versions():
|
def get_fabric_api_versions():
|
||||||
# https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml
|
# https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml
|
||||||
if not os.path.exists(get_dir_location('downloads/fabric_api_versions.json')):
|
if not os.path.exists(get_dir_location('__cache__/fabric_api_versions.json')):
|
||||||
print('\033[92mDownloading Fabric API versions...\033[m')
|
print('\033[92mDownloading Fabric API versions...\033[m')
|
||||||
fabric_api_versions_xml_text = requests.get(
|
fabric_api_versions_xml_text = requests.get(
|
||||||
'https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml').text
|
'https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml').text
|
||||||
|
@ -138,17 +138,17 @@ def get_fabric_api_versions():
|
||||||
for version_el in versions_el.findall('version'):
|
for version_el in versions_el.findall('version'):
|
||||||
fabric_api_versions.append(version_el.text)
|
fabric_api_versions.append(version_el.text)
|
||||||
|
|
||||||
with open(get_dir_location('downloads/fabric_api_versions.json'), 'w') as f:
|
with open(get_dir_location('__cache__/fabric_api_versions.json'), 'w') as f:
|
||||||
f.write(json.dumps(fabric_api_versions))
|
f.write(json.dumps(fabric_api_versions))
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location('downloads/fabric_api_versions.json'), 'r') as f:
|
with open(get_dir_location('__cache__/fabric_api_versions.json'), 'r') as f:
|
||||||
fabric_api_versions = json.loads(f.read())
|
fabric_api_versions = json.loads(f.read())
|
||||||
return fabric_api_versions
|
return fabric_api_versions
|
||||||
|
|
||||||
|
|
||||||
def get_fabric_loader_versions():
|
def get_fabric_loader_versions():
|
||||||
# https://meta.fabricmc.net/v2/versions/loader
|
# https://meta.fabricmc.net/v2/versions/loader
|
||||||
if not os.path.exists(get_dir_location('downloads/fabric_loader_versions.json')):
|
if not os.path.exists(get_dir_location('__cache__/fabric_loader_versions.json')):
|
||||||
print('\033[92mDownloading Fabric loader versions...\033[m')
|
print('\033[92mDownloading Fabric loader versions...\033[m')
|
||||||
fabric_api_versions_json = requests.get(
|
fabric_api_versions_json = requests.get(
|
||||||
'https://meta.fabricmc.net/v2/versions/loader').json()
|
'https://meta.fabricmc.net/v2/versions/loader').json()
|
||||||
|
@ -157,10 +157,10 @@ def get_fabric_loader_versions():
|
||||||
for version in fabric_api_versions_json:
|
for version in fabric_api_versions_json:
|
||||||
fabric_api_versions.append(version['version'])
|
fabric_api_versions.append(version['version'])
|
||||||
|
|
||||||
with open(get_dir_location('downloads/fabric_loader_versions.json'), 'w') as f:
|
with open(get_dir_location('__cache__/fabric_loader_versions.json'), 'w') as f:
|
||||||
f.write(json.dumps(fabric_api_versions))
|
f.write(json.dumps(fabric_api_versions))
|
||||||
else:
|
else:
|
||||||
with open(get_dir_location('downloads/fabric_loader_versions.json'), 'r') as f:
|
with open(get_dir_location('__cache__/fabric_loader_versions.json'), 'r') as f:
|
||||||
fabric_api_versions = json.loads(f.read())
|
fabric_api_versions = json.loads(f.read())
|
||||||
return fabric_api_versions
|
return fabric_api_versions
|
||||||
|
|
||||||
|
@ -174,14 +174,14 @@ def clear_version_cache():
|
||||||
'fabric_loader_versions.json'
|
'fabric_loader_versions.json'
|
||||||
]
|
]
|
||||||
for file in files:
|
for file in files:
|
||||||
if os.path.exists(get_dir_location(f'downloads/{file}')):
|
if os.path.exists(get_dir_location(f'__cache__/{file}')):
|
||||||
os.remove(get_dir_location(f'downloads/{file}'))
|
os.remove(get_dir_location(f'__cache__/{file}'))
|
||||||
|
|
||||||
burger_path = get_dir_location("downloads/Burger")
|
burger_path = get_dir_location("__cache__/Burger")
|
||||||
if os.path.exists(burger_path):
|
if os.path.exists(burger_path):
|
||||||
os.system(
|
os.system(
|
||||||
f'cd {burger_path} && git pull')
|
f'cd {burger_path} && git pull')
|
||||||
pixlyzer_path = get_dir_location('downloads/pixlyzer')
|
pixlyzer_path = get_dir_location('__cache__/pixlyzer')
|
||||||
if os.path.exists(pixlyzer_path):
|
if os.path.exists(pixlyzer_path):
|
||||||
os.system(
|
os.system(
|
||||||
f'cd {pixlyzer_path} && git pull')
|
f'cd {pixlyzer_path} && git pull')
|
|
@ -12,27 +12,44 @@ import os
|
||||||
|
|
||||||
|
|
||||||
def generate_data_from_server_jar(version_id: str):
|
def generate_data_from_server_jar(version_id: str):
|
||||||
if os.path.exists(get_dir_location(f'downloads/generated-{version_id}')):
|
if os.path.exists(get_dir_location(f'__cache__/generated-{version_id}')):
|
||||||
return
|
return
|
||||||
|
|
||||||
get_server_jar(version_id)
|
get_server_jar(version_id)
|
||||||
os.system(
|
os.system(
|
||||||
f'cd {get_dir_location(f"downloads")} && java -DbundlerMainClass=net.minecraft.data.Main -jar {get_dir_location(f"downloads/server-{version_id}.jar")} --all --output \"{get_dir_location(f"downloads/generated-{version_id}")}\"'
|
f'cd {get_dir_location(f"__cache__")} && java -DbundlerMainClass=net.minecraft.data.Main -jar {get_dir_location(f"__cache__/server-{version_id}.jar")} --all --output \"{get_dir_location(f"__cache__/generated-{version_id}")}\"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_block_states_report(version_id: str):
|
def get_block_states_report(version_id: str):
|
||||||
generate_data_from_server_jar(version_id)
|
generate_data_from_server_jar(version_id)
|
||||||
with open(get_dir_location(f'downloads/generated-{version_id}/reports/blocks.json'), 'r') as f:
|
with open(get_dir_location(f'__cache__/generated-{version_id}/reports/blocks.json'), 'r') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
def get_registries_report(version_id: str):
|
def get_registries_report(version_id: str):
|
||||||
generate_data_from_server_jar(version_id)
|
generate_data_from_server_jar(version_id)
|
||||||
with open(get_dir_location(f'downloads/generated-{version_id}/reports/registries.json'), 'r') as f:
|
with open(get_dir_location(f'__cache__/generated-{version_id}/reports/registries.json'), 'r') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_registry_tags(version_id: str, name: str):
|
||||||
|
generate_data_from_server_jar(version_id)
|
||||||
|
tags_directory = get_dir_location(f'__cache__/generated-{version_id}/data/minecraft/tags/{name}')
|
||||||
|
if not os.path.exists(tags_directory):
|
||||||
|
return {}
|
||||||
|
tags = {}
|
||||||
|
for root, dirs, files in os.walk(tags_directory, topdown=False):
|
||||||
|
for name in files:
|
||||||
|
file = os.path.join(root, name)
|
||||||
|
relative_path = file.replace(tags_directory, '')[1:]
|
||||||
|
if not file.endswith('.json'):
|
||||||
|
continue
|
||||||
|
with open(file, 'r') as f:
|
||||||
|
tags[relative_path[:-5]] = json.load(f)
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
def get_block_states_burger(version_id: str):
|
def get_block_states_burger(version_id: str):
|
||||||
burger_data = get_burger_data_for_version(version_id)
|
burger_data = get_burger_data_for_version(version_id)
|
||||||
return burger_data[0]['blocks']['block']
|
return burger_data[0]['blocks']['block']
|
||||||
|
@ -96,15 +113,15 @@ def run_python_command_and_download_deps(command):
|
||||||
|
|
||||||
|
|
||||||
def get_burger_data_for_version(version_id: str):
|
def get_burger_data_for_version(version_id: str):
|
||||||
if not os.path.exists(get_dir_location(f'downloads/burger-{version_id}.json')):
|
if not os.path.exists(get_dir_location(f'__cache__/burger-{version_id}.json')):
|
||||||
get_burger()
|
get_burger()
|
||||||
get_client_jar(version_id)
|
get_client_jar(version_id)
|
||||||
|
|
||||||
print('\033[92mRunning Burger...\033[m')
|
print('\033[92mRunning Burger...\033[m')
|
||||||
run_python_command_and_download_deps(
|
run_python_command_and_download_deps(
|
||||||
f'cd {get_dir_location("downloads/Burger")} && {determine_python_command()} munch.py {get_dir_location("downloads")}/client-{version_id}.jar --output {get_dir_location("downloads")}/burger-{version_id}.json'
|
f'cd {get_dir_location("__cache__/Burger")} && {determine_python_command()} munch.py {get_dir_location("__cache__")}/client-{version_id}.jar --output {get_dir_location("__cache__")}/burger-{version_id}.json'
|
||||||
)
|
)
|
||||||
with open(get_dir_location(f'downloads/burger-{version_id}.json'), 'r') as f:
|
with open(get_dir_location(f'__cache__/burger-{version_id}.json'), 'r') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +130,7 @@ def get_pixlyzer_data(version_id: str, category: str):
|
||||||
Gets data from Pixlyzer. Note that this requires Yarn to release updates first.
|
Gets data from Pixlyzer. Note that this requires Yarn to release updates first.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
target_dir = get_dir_location(f'downloads/pixlyzer-{version_id}')
|
target_dir = get_dir_location(f'__cache__/pixlyzer-{version_id}')
|
||||||
|
|
||||||
# TODO: right now this False is hard-coded, it should retry with this
|
# TODO: right now this False is hard-coded, it should retry with this
|
||||||
# enabled if # initially getting the data fails
|
# enabled if # initially getting the data fails
|
||||||
|
@ -249,7 +266,7 @@ def get_pixlyzer_data(version_id: str, category: str):
|
||||||
|
|
||||||
def get_file_from_jar(version_id: str, file_dir: str):
|
def get_file_from_jar(version_id: str, file_dir: str):
|
||||||
get_client_jar(version_id)
|
get_client_jar(version_id)
|
||||||
with ZipFile(get_dir_location(f'downloads/client-{version_id}.jar')) as z:
|
with ZipFile(get_dir_location(f'__cache__/client-{version_id}.jar')) as z:
|
||||||
with z.open(file_dir) as f:
|
with z.open(file_dir) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
|
@ -133,9 +133,8 @@ language = lib.extract.get_en_us_lang(new_version_id)
|
||||||
lib.code.language.write_language(language)
|
lib.code.language.write_language(language)
|
||||||
|
|
||||||
print('Generating registries...')
|
print('Generating registries...')
|
||||||
registries = lib.extract.get_registries_report(new_version_id)
|
import genregistries
|
||||||
lib.code.registry.generate_registries(registries)
|
genregistries.generate(new_version_id)
|
||||||
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