mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 14:42:32 +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]]
|
||||
name = "async-channel"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
|
||||
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
|
@ -98,9 +98,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0122885821398cc923ece939e24d1056a2384ee719432397fa9db87230ff11"
|
||||
checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
|
@ -178,6 +178,7 @@ dependencies = [
|
|||
"azalea-chat",
|
||||
"azalea-client",
|
||||
"azalea-core",
|
||||
"azalea-entity",
|
||||
"azalea-inventory",
|
||||
"azalea-physics",
|
||||
"azalea-protocol",
|
||||
|
@ -293,6 +294,7 @@ dependencies = [
|
|||
"azalea-chat",
|
||||
"azalea-core",
|
||||
"azalea-crypto",
|
||||
"azalea-entity",
|
||||
"azalea-inventory",
|
||||
"azalea-nbt",
|
||||
"azalea-physics",
|
||||
|
@ -349,6 +351,29 @@ dependencies = [
|
|||
"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]]
|
||||
name = "azalea-inventory"
|
||||
version = "0.7.0"
|
||||
|
@ -401,6 +426,7 @@ version = "0.7.0"
|
|||
dependencies = [
|
||||
"azalea-block",
|
||||
"azalea-core",
|
||||
"azalea-entity",
|
||||
"azalea-inventory",
|
||||
"azalea-registry",
|
||||
"azalea-world",
|
||||
|
@ -426,6 +452,7 @@ dependencies = [
|
|||
"azalea-chat",
|
||||
"azalea-core",
|
||||
"azalea-crypto",
|
||||
"azalea-entity",
|
||||
"azalea-inventory",
|
||||
"azalea-nbt",
|
||||
"azalea-protocol-macros",
|
||||
|
@ -465,6 +492,7 @@ version = "0.7.0"
|
|||
dependencies = [
|
||||
"azalea-buf",
|
||||
"azalea-registry-macros",
|
||||
"once_cell",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -933,9 +961,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
|
@ -945,9 +973,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1058,9 +1086,9 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
|||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17"
|
||||
checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
|
@ -1151,15 +1179,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.25"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
|
||||
checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1531,10 +1559,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
|
||||
checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"rustls",
|
||||
|
@ -1612,9 +1641,9 @@ checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
|||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
|
@ -1632,9 +1661,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
|
@ -1783,9 +1812,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
|
@ -1808,9 +1837,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.2"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905"
|
||||
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
|
@ -2153,7 +2182,7 @@ dependencies = [
|
|||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.2",
|
||||
"regex-syntax 0.7.3",
|
||||
"regex-syntax 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2173,7 +2202,7 @@ checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
|
|||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.3",
|
||||
"regex-syntax 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2184,9 +2213,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
|
@ -2299,9 +2328,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.1"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3"
|
||||
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"errno",
|
||||
|
@ -2312,9 +2341,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.2"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f"
|
||||
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
|
@ -2333,9 +2362,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.1"
|
||||
version = "0.101.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
|
||||
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
|
@ -2343,15 +2372,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.12"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
|
||||
checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
|
@ -2386,27 +2415,27 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.170"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56657f512baabca8f840542f9ca8152aecf182c473c26e46e58d6aab4f6e439"
|
||||
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.10"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c5113243e4a3a1c96587342d067f3e6b0f50790b6cf40d2868eb647a3eef0e"
|
||||
checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.170"
|
||||
version = "1.0.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77d477848e6b23adba0db397777d5aad864555bc17fd9c89abb3b8009788b7b8"
|
||||
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2415,9 +2444,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.100"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
||||
checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -2723,9 +2752,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.11"
|
||||
version = "0.19.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
|
||||
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"toml_datetime",
|
||||
|
@ -3162,9 +3191,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.7"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
|
||||
checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -16,6 +16,7 @@ members = [
|
|||
"azalea-physics",
|
||||
"azalea-registry",
|
||||
"azalea-inventory",
|
||||
"azalea-entity",
|
||||
]
|
||||
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_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 {
|
||||
let block_property_names = &block
|
||||
.properties_and_defaults
|
||||
|
@ -448,15 +451,21 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
is_default = false;
|
||||
}
|
||||
|
||||
let property_type = if property.is_enum {
|
||||
let property_value = if property.is_enum {
|
||||
quote! {properties::#property_struct_name_ident::#variant}
|
||||
} else {
|
||||
quote! {#variant}
|
||||
};
|
||||
|
||||
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! {
|
||||
|
@ -473,7 +482,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
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:?}")
|
||||
};
|
||||
|
||||
|
@ -570,6 +588,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
fn as_block_state(&self) -> BlockState {
|
||||
#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 {
|
||||
|
@ -590,6 +611,8 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
block_structs.extend(block_struct);
|
||||
}
|
||||
|
||||
let waterlogged_state_ids_match = quote! { #(#waterlogged_state_ids)|* };
|
||||
|
||||
let last_state_id = state_id - 1;
|
||||
let mut generated = quote! {
|
||||
impl BlockState {
|
||||
|
@ -598,6 +621,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
|
|||
pub fn max_state() -> u32 {
|
||||
#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 {
|
||||
|
|
|
@ -1,35 +1,55 @@
|
|||
pub struct BlockBehavior {
|
||||
pub has_collision: bool,
|
||||
pub friction: f32,
|
||||
pub jump_factor: f32,
|
||||
pub destroy_time: f32,
|
||||
pub explosion_resistance: f32,
|
||||
pub requires_correct_tool_for_drops: bool,
|
||||
}
|
||||
|
||||
impl Default for BlockBehavior {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
has_collision: true,
|
||||
friction: 0.6,
|
||||
jump_factor: 1.0,
|
||||
destroy_time: 0.,
|
||||
explosion_resistance: 0.,
|
||||
requires_correct_tool_for_drops: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBehavior {
|
||||
#[inline]
|
||||
pub fn no_collision(mut self) -> Self {
|
||||
self.has_collision = false;
|
||||
self
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn friction(mut self, friction: f32) -> Self {
|
||||
self.friction = friction;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn jump_factor(mut self, jump_factor: f32) -> Self {
|
||||
self.jump_factor = jump_factor;
|
||||
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
|
||||
/// contains all the state data.
|
||||
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 {
|
||||
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
|
||||
|
@ -45,19 +48,14 @@ pub struct BlockState {
|
|||
impl BlockState {
|
||||
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]
|
||||
pub fn is_valid_state(state_id: u32) -> bool {
|
||||
state_id <= Self::max_state()
|
||||
}
|
||||
|
||||
pub fn is_air(&self) -> bool {
|
||||
self == &Self::AIR
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for BlockState {
|
||||
|
@ -66,7 +64,7 @@ impl TryFrom<u32> for BlockState {
|
|||
/// Safely converts a state id to a block state.
|
||||
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
||||
if Self::is_valid_state(state_id) {
|
||||
Ok(unsafe { Self::from_u32_unchecked(state_id) })
|
||||
Ok(BlockState { id: state_id })
|
||||
} else {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -96,17 +96,10 @@ fn read_utf_with_len(buf: &mut Cursor<&[u8]>, max_length: u32) -> Result<String,
|
|||
/// number of bytes read
|
||||
// pub async fn read_varint_async(
|
||||
// reader: &mut (dyn AsyncRead + Unpin + Send),
|
||||
// ) -> Result<i32, BufReadError> {
|
||||
// let mut buffer = [0];
|
||||
// let mut ans = 0;
|
||||
// for i in 0..5 {
|
||||
// reader.read_exact(&mut buffer).await?;
|
||||
// ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i);
|
||||
// if buffer[0] & 0b1000_0000 == 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// Ok(ans)
|
||||
// ) -> Result<i32, BufReadError> { let mut buffer = [0]; let mut ans = 0; for i
|
||||
// in 0..5 { 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
|
||||
|
|
|
@ -38,6 +38,7 @@ regex = "1.9.1"
|
|||
thiserror = "^1.0.43"
|
||||
tokio = { version = "^1.29.1", features = ["sync"] }
|
||||
uuid = "^1.4.0"
|
||||
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||
|
||||
[features]
|
||||
default = ["log"]
|
||||
|
|
|
@ -187,7 +187,10 @@ impl Account {
|
|||
|
||||
*access_token_mutex.lock() = new_access_token;
|
||||
let AccountOpts::MicrosoftWithAccessToken { msa: new_msa } =
|
||||
new_account.account_opts else { unreachable!() };
|
||||
new_account.account_opts
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
*msa.lock() = new_msa.lock().clone();
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
|
||||
LocalPlayer, PhysicsState, SendPacketEvent,
|
||||
},
|
||||
mining::{self, MinePlugin},
|
||||
movement::{LastSentLookDirection, PlayerMovePlugin},
|
||||
packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
|
||||
player::retroactively_add_game_profile_component,
|
||||
|
@ -19,6 +20,7 @@ use crate::{
|
|||
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_core::Vec3;
|
||||
use azalea_entity::{EntityPlugin, EntityUpdateSet, Local, Position};
|
||||
use azalea_physics::{PhysicsPlugin, PhysicsSet};
|
||||
use azalea_protocol::{
|
||||
connect::{Connection, ConnectionError},
|
||||
|
@ -41,10 +43,7 @@ use azalea_protocol::{
|
|||
},
|
||||
resolver, ServerAddress,
|
||||
};
|
||||
use azalea_world::{
|
||||
entity::{EntityPlugin, EntityUpdateSet, Local, Position, WorldName},
|
||||
Instance, InstanceContainer, PartialInstance,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance};
|
||||
use bevy_app::{App, FixedUpdate, Main, Plugin, PluginGroup, PluginGroupBuilder, Update};
|
||||
use bevy_ecs::{
|
||||
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
|
||||
/// tab list.
|
||||
///
|
||||
|
@ -301,6 +304,8 @@ impl Client {
|
|||
current_sequence_number: CurrentSequenceNumber::default(),
|
||||
last_sent_direction: LastSentLookDirection::default(),
|
||||
abilities: PlayerAbilities::default(),
|
||||
permission_level: PermissionLevel::default(),
|
||||
mining: mining::MineBundle::default(),
|
||||
_local: Local,
|
||||
});
|
||||
|
||||
|
@ -466,9 +471,9 @@ impl Client {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use azalea_world::entity::WorldName;
|
||||
/// # use azalea_world::InstanceName;
|
||||
/// # 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 {
|
||||
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
|
||||
/// superset of the client's world.
|
||||
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 instance_container = ecs.resource::<InstanceContainer>();
|
||||
instance_container.get(&world_name).unwrap()
|
||||
|
@ -495,7 +500,7 @@ impl Client {
|
|||
/// Returns whether we have a received the login packet yet.
|
||||
pub fn logged_in(&self) -> bool {
|
||||
// the login packet tells us the world name
|
||||
self.query::<Option<&WorldName>>(&mut self.ecs.lock())
|
||||
self.query::<Option<&InstanceName>>(&mut self.ecs.lock())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
|
@ -560,6 +565,10 @@ pub struct JoinedClientBundle {
|
|||
pub current_sequence_number: CurrentSequenceNumber,
|
||||
pub last_sent_direction: LastSentLookDirection,
|
||||
pub abilities: PlayerAbilities,
|
||||
pub permission_level: PermissionLevel,
|
||||
|
||||
pub mining: mining::MineBundle,
|
||||
|
||||
pub _local: Local,
|
||||
}
|
||||
|
||||
|
@ -660,7 +669,7 @@ pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::UnboundedSender<(
|
|||
#[derive(Resource, Deref)]
|
||||
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(());
|
||||
}
|
||||
/// A plugin that makes the [`RanScheduleBroadcast`] resource available.
|
||||
|
@ -706,6 +715,7 @@ impl PluginGroup for DefaultPlugins {
|
|||
.add(PlayerMovePlugin)
|
||||
.add(InteractPlugin)
|
||||
.add(RespawnPlugin)
|
||||
.add(MinePlugin)
|
||||
.add(TickBroadcastPlugin);
|
||||
#[cfg(feature = "log")]
|
||||
{
|
||||
|
|
|
@ -15,10 +15,10 @@ impl Client {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use azalea_world::entity::WorldName;
|
||||
/// # use azalea_world::InstanceName;
|
||||
/// # fn example(mut client: azalea_client::Client) {
|
||||
/// let is_logged_in = client
|
||||
/// .query::<Option<&WorldName>>(&mut client.ecs.lock())
|
||||
/// .query::<Option<&InstanceName>>(&mut client.ecs.lock())
|
||||
/// .is_some();
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -39,7 +39,7 @@ impl Client {
|
|||
/// ```
|
||||
/// use azalea_client::{Client, GameProfileComponent};
|
||||
/// 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) {
|
||||
/// let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
use azalea_protocol::packets::game::{
|
||||
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_ecs::{component::Component, event::EventReader, query::Added, system::Query};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
use std::ops::AddAssign;
|
||||
|
||||
use azalea_block::BlockState;
|
||||
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_nbt::NbtList;
|
||||
use azalea_physics::clip::{BlockShapeType, ClipContext, FluidPickType};
|
||||
use azalea_protocol::packets::game::{
|
||||
serverbound_interact_packet::InteractionHand,
|
||||
serverbound_swing_packet::ServerboundSwingPacket,
|
||||
serverbound_use_item_on_packet::{BlockHit, ServerboundUseItemOnPacket},
|
||||
};
|
||||
use azalea_world::{
|
||||
entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position, WorldName},
|
||||
Instance, InstanceContainer,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::EventReader,
|
||||
prelude::Event,
|
||||
event::{Event, EventReader, EventWriter},
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res},
|
||||
};
|
||||
|
@ -24,8 +24,9 @@ use derive_more::{Deref, DerefMut};
|
|||
use log::warn;
|
||||
|
||||
use crate::{
|
||||
client::{PermissionLevel, PlayerAbilities},
|
||||
inventory::InventoryComponent,
|
||||
local_player::{handle_send_packet_event, LocalGameMode},
|
||||
local_player::{handle_send_packet_event, LocalGameMode, SendPacketEvent},
|
||||
Client, LocalPlayer,
|
||||
};
|
||||
|
||||
|
@ -33,11 +34,14 @@ use crate::{
|
|||
pub struct InteractPlugin;
|
||||
impl Plugin for InteractPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<BlockInteractEvent>().add_systems(
|
||||
app.add_event::<BlockInteractEvent>()
|
||||
.add_event::<SwingArmEvent>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_hit_result_component.after(clamp_look_direction),
|
||||
handle_block_interact_event,
|
||||
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
|
||||
/// blocks.
|
||||
#[derive(Component, Copy, Clone, Debug, Default, Deref, DerefMut)]
|
||||
#[derive(Component, Copy, Clone, Debug, Default, Deref)]
|
||||
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.
|
||||
#[derive(Component, Clone, Debug, Deref, DerefMut)]
|
||||
pub struct HitResultComponent(BlockHitResult);
|
||||
|
@ -89,14 +99,15 @@ pub fn handle_block_interact_event(
|
|||
)>,
|
||||
) {
|
||||
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");
|
||||
continue;
|
||||
};
|
||||
|
||||
// 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
|
||||
// button is instant) but we don't really need that
|
||||
|
@ -143,7 +154,7 @@ fn update_hit_result_component(
|
|||
&Position,
|
||||
&EyeHeight,
|
||||
&LookDirection,
|
||||
&WorldName,
|
||||
&InstanceName,
|
||||
)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
|
@ -246,7 +257,8 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode(
|
|||
.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()))
|
||||
else {
|
||||
// no CanDestroy tag
|
||||
return false;
|
||||
};
|
||||
|
@ -265,3 +277,31 @@ pub fn check_block_can_be_broken_by_item_in_adventure_mode(
|
|||
|
||||
// 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.
|
||||
pub selected_hotbar_slot: u8,
|
||||
}
|
||||
|
||||
impl InventoryComponent {
|
||||
/// Returns a reference to the currently active menu. If a container is open
|
||||
/// it'll return [`Self::container_menu`], otherwise
|
||||
|
|
|
@ -21,7 +21,7 @@ mod get_mc_dir;
|
|||
pub mod interact;
|
||||
pub mod inventory;
|
||||
mod local_player;
|
||||
mod mining;
|
||||
pub mod mining;
|
||||
mod movement;
|
||||
pub mod packet_handling;
|
||||
pub mod ping;
|
||||
|
|
|
@ -2,11 +2,9 @@ use std::{io, sync::Arc};
|
|||
|
||||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_core::{ChunkPos, GameMode};
|
||||
use azalea_entity::{Dead, Position};
|
||||
use azalea_protocol::packets::game::ServerboundGamePacket;
|
||||
use azalea_world::{
|
||||
entity::{self, Dead, WorldName},
|
||||
Instance, InstanceContainer, PartialInstance,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName, PartialInstance};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
|
@ -32,7 +30,7 @@ use crate::{
|
|||
/// 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.
|
||||
///
|
||||
/// [`Local`]: azalea_world::entity::Local
|
||||
/// [`Local`]: azalea_entity::Local
|
||||
/// [`Client`]: crate::Client
|
||||
#[derive(Component)]
|
||||
pub struct LocalPlayer {
|
||||
|
@ -135,7 +133,7 @@ impl Drop for LocalPlayer {
|
|||
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
|
||||
pub fn update_in_loaded_chunk(
|
||||
mut commands: bevy_ecs::system::Commands,
|
||||
query: Query<(Entity, &WorldName, &entity::Position)>,
|
||||
query: Query<(Entity, &InstanceName, &Position)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
for (entity, local_player, position) in &query {
|
||||
|
|
|
@ -1,36 +1,543 @@
|
|||
use azalea_core::BlockPos;
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use azalea_block::{Block, BlockState, FluidState};
|
||||
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 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.
|
||||
pub struct MinePlugin;
|
||||
impl Plugin for MinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
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 {
|
||||
/// Start mining a block.
|
||||
pub fn start_mining_block(&self, position: BlockPos) {
|
||||
self.ecs.lock().send_event(StartMiningBlockEvent {
|
||||
entity: self.entity,
|
||||
position,
|
||||
});
|
||||
}
|
||||
/// Information about the block we're currently mining. This is only present if
|
||||
/// we're currently mining a block.
|
||||
#[derive(Component)]
|
||||
pub struct Mining {
|
||||
pub pos: BlockPos,
|
||||
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)]
|
||||
pub struct StartMiningBlockEvent {
|
||||
pub entity: Entity,
|
||||
pub position: BlockPos,
|
||||
}
|
||||
|
||||
fn handle_start_mining_block_event(mut _events: EventReader<StartMiningBlockEvent>) {
|
||||
// for event in events.iter() {
|
||||
// //
|
||||
// }
|
||||
fn handle_start_mining_block_event(
|
||||
mut events: EventReader<StartMiningBlockEvent>,
|
||||
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::{
|
||||
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_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
|
||||
use azalea_protocol::packets::game::{
|
||||
|
@ -10,10 +12,7 @@ use azalea_protocol::packets::game::{
|
|||
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
||||
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
||||
};
|
||||
use azalea_world::{
|
||||
entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId},
|
||||
MoveEntityError,
|
||||
};
|
||||
use azalea_world::{MinecraftEntityId, MoveEntityError};
|
||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||
use bevy_ecs::prelude::Event;
|
||||
use bevy_ecs::{
|
||||
|
@ -89,7 +88,7 @@ impl Client {
|
|||
/// `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) {
|
||||
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);
|
||||
}
|
||||
|
@ -110,12 +109,12 @@ pub(crate) fn send_position(
|
|||
&MinecraftEntityId,
|
||||
&mut LocalPlayer,
|
||||
&mut PhysicsState,
|
||||
&entity::Position,
|
||||
&mut entity::LastSentPosition,
|
||||
&mut entity::Physics,
|
||||
&entity::LookDirection,
|
||||
&Position,
|
||||
&mut LastSentPosition,
|
||||
&mut Physics,
|
||||
&LookDirection,
|
||||
&mut LastSentLookDirection,
|
||||
&entity::metadata::Sprinting,
|
||||
&Sprinting,
|
||||
),
|
||||
&LocalPlayerInLoadedChunk,
|
||||
>,
|
||||
|
@ -223,7 +222,7 @@ impl LocalPlayer {
|
|||
fn send_sprinting_if_needed(
|
||||
&mut self,
|
||||
id: &MinecraftEntityId,
|
||||
sprinting: &entity::metadata::Sprinting,
|
||||
sprinting: &Sprinting,
|
||||
physics_state: &mut PhysicsState,
|
||||
) {
|
||||
let was_sprinting = physics_state.was_sprinting;
|
||||
|
@ -287,9 +286,9 @@ pub fn local_player_ai_step(
|
|||
mut query: Query<
|
||||
(
|
||||
&mut PhysicsState,
|
||||
&mut entity::Physics,
|
||||
&mut entity::metadata::Sprinting,
|
||||
&mut entity::Attributes,
|
||||
&mut Physics,
|
||||
&mut Sprinting,
|
||||
&mut Attributes,
|
||||
),
|
||||
With<LocalPlayerInLoadedChunk>,
|
||||
>,
|
||||
|
@ -431,12 +430,12 @@ fn set_sprinting(
|
|||
if sprinting {
|
||||
attributes
|
||||
.speed
|
||||
.insert(entity::attributes::sprinting_modifier())
|
||||
.insert(azalea_entity::attributes::sprinting_modifier())
|
||||
.is_ok()
|
||||
} else {
|
||||
attributes
|
||||
.speed
|
||||
.remove(&entity::attributes::sprinting_modifier().uuid)
|
||||
.remove(&azalea_entity::attributes::sprinting_modifier().uuid)
|
||||
.is_none()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
||||
|
||||
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::{
|
||||
connect::{ReadConnection, WriteConnection},
|
||||
packets::game::{
|
||||
|
@ -15,15 +20,7 @@ use azalea_protocol::{
|
|||
},
|
||||
read::ReadPacketError,
|
||||
};
|
||||
use azalea_world::{
|
||||
entity::{
|
||||
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
||||
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LookDirection,
|
||||
MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
|
||||
},
|
||||
entity::{LoadedBy, RelativeEntityUpdate},
|
||||
InstanceContainer, PartialInstance,
|
||||
};
|
||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
||||
use bevy_app::{App, First, Plugin, PreUpdate};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
|
@ -195,7 +192,7 @@ fn process_packet_events(ecs: &mut World) {
|
|||
Commands,
|
||||
Query<(
|
||||
&mut LocalPlayer,
|
||||
Option<&mut WorldName>,
|
||||
Option<&mut InstanceName>,
|
||||
&GameProfileComponent,
|
||||
&ClientInformation,
|
||||
)>,
|
||||
|
@ -225,7 +222,7 @@ fn process_packet_events(ecs: &mut World) {
|
|||
} else {
|
||||
commands
|
||||
.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
|
||||
// there)
|
||||
|
@ -348,8 +345,14 @@ fn process_packet_events(ecs: &mut World) {
|
|||
)>,
|
||||
> = SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let Ok((local_player, mut physics, mut direction, mut position, mut last_sent_position)) =
|
||||
query.get_mut(player_entity) else {
|
||||
let Ok((
|
||||
local_player,
|
||||
mut physics,
|
||||
mut direction,
|
||||
mut position,
|
||||
mut last_sent_position,
|
||||
)) = query.get_mut(player_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -555,12 +558,12 @@ fn process_packet_events(ecs: &mut World) {
|
|||
ClientboundGamePacket::AddEntity(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);
|
||||
let (mut commands, mut query) = system_state.get_mut(ecs);
|
||||
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 mut entity_commands = commands.spawn((
|
||||
MinecraftEntityId(p.id),
|
||||
|
@ -622,12 +625,12 @@ fn process_packet_events(ecs: &mut World) {
|
|||
#[allow(clippy::type_complexity)]
|
||||
let mut system_state: SystemState<(
|
||||
Commands,
|
||||
Query<(&TabList, Option<&WorldName>)>,
|
||||
Query<(&TabList, Option<&InstanceName>)>,
|
||||
)> = SystemState::new(ecs);
|
||||
let (mut commands, mut query) = system_state.get_mut(ecs);
|
||||
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 mut spawned = commands.spawn((
|
||||
MinecraftEntityId(p.id),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use azalea_auth::game_profile::GameProfile;
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_core::GameMode;
|
||||
use azalea_world::entity::EntityInfos;
|
||||
use azalea_entity::EntityInfos;
|
||||
use bevy_ecs::{
|
||||
event::EventReader,
|
||||
system::{Commands, Res},
|
||||
|
|
|
@ -22,8 +22,7 @@ pub use direction::*;
|
|||
mod delta;
|
||||
pub use delta::*;
|
||||
|
||||
mod particle;
|
||||
pub use particle::*;
|
||||
pub mod particle;
|
||||
|
||||
mod cursor3d;
|
||||
pub use cursor3d::*;
|
||||
|
@ -37,61 +36,5 @@ pub use aabb::*;
|
|||
mod block_hit_result;
|
||||
pub use block_hit_result::*;
|
||||
|
||||
// some random math things used in minecraft are defined down here
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
pub mod math;
|
||||
pub mod tier;
|
||||
|
|
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,
|
||||
};
|
||||
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 bevy_ecs::component::Component;
|
||||
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
|
||||
//! 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_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
||||
use bevy_app::{App, Plugin, PostUpdate, PreUpdate, Update};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
|
@ -20,7 +14,6 @@ use bevy_ecs::{
|
|||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use log::{debug, warn};
|
||||
use nohash_hasher::IntMap;
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
|
@ -29,6 +22,15 @@ use std::{
|
|||
};
|
||||
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};
|
||||
|
||||
/// A Bevy [`SystemSet`] for various types of entity updates.
|
||||
|
@ -75,6 +77,7 @@ impl Plugin for EntityPlugin {
|
|||
add_dead,
|
||||
update_bounding_box,
|
||||
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
|
||||
// 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
|
||||
/// means this update won't be run multiple times by different clients in the
|
||||
/// same world.
|
||||
|
@ -211,15 +187,7 @@ impl EntityInfos {
|
|||
|
||||
/// Update the chunk position indexes in [`EntityInfos`].
|
||||
fn update_entity_chunk_positions(
|
||||
mut query: Query<
|
||||
(
|
||||
Entity,
|
||||
&entity::Position,
|
||||
&mut entity::LastSentPosition,
|
||||
&entity::WorldName,
|
||||
),
|
||||
Changed<entity::Position>,
|
||||
>,
|
||||
mut query: Query<(Entity, &Position, &mut LastSentPosition, &InstanceName), Changed<Position>>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
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 entity_infos: ResMut<EntityInfos>,
|
||||
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 {
|
||||
let world_lock = instance_container.get(world_name).unwrap();
|
|
@ -3,15 +3,18 @@
|
|||
pub mod attributes;
|
||||
mod data;
|
||||
mod dimensions;
|
||||
mod effects;
|
||||
mod enchantments;
|
||||
mod info;
|
||||
pub mod metadata;
|
||||
|
||||
use crate::ChunkStorage;
|
||||
pub mod mining;
|
||||
mod systems;
|
||||
|
||||
use self::{attributes::AttributeInstance, metadata::Health};
|
||||
pub use attributes::Attributes;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
||||
use azalea_world::{ChunkStorage, InstanceName};
|
||||
use bevy_ecs::{
|
||||
bundle::Bundle,
|
||||
component::Component,
|
||||
|
@ -23,23 +26,12 @@ pub use data::*;
|
|||
use derive_more::{Deref, DerefMut};
|
||||
pub use dimensions::{update_bounding_box, EntityDimensions};
|
||||
pub use info::{
|
||||
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy, PartialEntityInfos,
|
||||
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy,
|
||||
RelativeEntityUpdate,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
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(
|
||||
physics: &mut Physics,
|
||||
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.
|
||||
///
|
||||
/// 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 kind: EntityKind,
|
||||
pub uuid: EntityUuid,
|
||||
pub world_name: WorldName,
|
||||
pub world_name: InstanceName,
|
||||
pub position: Position,
|
||||
pub last_sent_position: LastSentPosition,
|
||||
pub physics: Physics,
|
||||
|
@ -311,6 +298,7 @@ pub struct EntityBundle {
|
|||
pub eye_height: EyeHeight,
|
||||
pub attributes: Attributes,
|
||||
pub jumping: Jumping,
|
||||
pub fluid_on_eyes: FluidOnEyes,
|
||||
}
|
||||
|
||||
impl EntityBundle {
|
||||
|
@ -330,7 +318,7 @@ impl EntityBundle {
|
|||
Self {
|
||||
kind: EntityKind(kind),
|
||||
uuid: EntityUuid(uuid),
|
||||
world_name: WorldName(world_name),
|
||||
world_name: InstanceName(world_name),
|
||||
position: Position(pos),
|
||||
last_sent_position: LastSentPosition(pos),
|
||||
physics: Physics {
|
||||
|
@ -359,6 +347,7 @@ impl EntityBundle {
|
|||
},
|
||||
|
||||
jumping: Jumping(false),
|
||||
fluid_on_eyes: FluidOnEyes(azalea_registry::Fluid::Empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -375,6 +364,9 @@ pub struct PlayerBundle {
|
|||
#[derive(Component)]
|
||||
pub struct Local;
|
||||
|
||||
#[derive(Component, Clone, Debug, PartialEq, Deref, DerefMut)]
|
||||
pub struct FluidOnEyes(azalea_registry::Fluid);
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
|
@ -8,7 +8,7 @@ use super::{
|
|||
SnifferState, VillagerData,
|
||||
};
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_core::{BlockPos, Direction, Particle, Vec3};
|
||||
use azalea_core::{particle::Particle, BlockPos, Direction, Vec3};
|
||||
use azalea_inventory::ItemSlot;
|
||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||
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};
|
||||
|
||||
// TODO: remove this here and in azalea-inventory-macros when rust makes
|
||||
// Default be implemented for all array sizes (since right now it's only up to
|
||||
// 32)
|
||||
// Default be implemented for all array sizes
|
||||
// https://github.com/rust-lang/rust/issues/61415
|
||||
|
||||
/// A fixed-size list of [`ItemSlot`]s.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -11,6 +11,7 @@ version = "0.7.0"
|
|||
[dependencies]
|
||||
azalea-block = { path = "../azalea-block", 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-registry = { path = "../azalea-registry", version = "^0.7.0" }
|
||||
azalea-world = { path = "../azalea-world", version = "^0.7.0" }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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_world::ChunkStorage;
|
||||
use bevy_ecs::entity::Entity;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use std::{cmp::Ordering, convert::TryInto};
|
||||
|
||||
use super::CubePointRange;
|
||||
use azalea_core::{gcd, lcm, EPSILON};
|
||||
use azalea_core::{
|
||||
math::{gcd, lcm},
|
||||
EPSILON,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IndexMerger {
|
||||
|
|
|
@ -5,7 +5,7 @@ mod shape;
|
|||
mod world_collisions;
|
||||
|
||||
use azalea_core::{Axis, Vec3, AABB, EPSILON};
|
||||
use azalea_world::{entity, Instance, MoveEntityError};
|
||||
use azalea_world::{Instance, MoveEntityError};
|
||||
pub use blocks::BlockWithShape;
|
||||
pub use discrete_voxel_shape::*;
|
||||
pub use shape::*;
|
||||
|
@ -49,7 +49,7 @@ pub enum MoverType {
|
|||
|
||||
// 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;
|
||||
// TODO: get_entity_collisions
|
||||
// let entity_collisions = world.get_entity_collisions(self,
|
||||
|
@ -71,8 +71,8 @@ pub fn move_colliding(
|
|||
_mover_type: &MoverType,
|
||||
movement: &Vec3,
|
||||
world: &Instance,
|
||||
position: &mut entity::Position,
|
||||
physics: &mut entity::Physics,
|
||||
position: &mut azalea_entity::Position,
|
||||
physics: &mut azalea_entity::Physics,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
// TODO: do all these
|
||||
|
||||
|
@ -122,7 +122,7 @@ pub fn move_colliding(
|
|||
|
||||
// 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
|
||||
// .world
|
||||
// .get_block_state(&block_pos_below)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::mergers::IndexMerger;
|
||||
use crate::collision::{BitSetDiscreteVoxelShape, DiscreteVoxelShape, AABB};
|
||||
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};
|
||||
|
||||
|
|
|
@ -71,9 +71,7 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
|||
}
|
||||
|
||||
let chunk = self.get_chunk(item.pos.x, item.pos.z);
|
||||
let Some(chunk) = chunk else {
|
||||
continue
|
||||
};
|
||||
let Some(chunk) = chunk else { continue };
|
||||
|
||||
let pos = item.pos;
|
||||
let block_state: BlockState = chunk
|
||||
|
|
|
@ -6,13 +6,12 @@ pub mod collision;
|
|||
|
||||
use azalea_block::{Block, BlockState};
|
||||
use azalea_core::{BlockPos, Vec3};
|
||||
use azalea_world::{
|
||||
entity::{
|
||||
use azalea_entity::update_bounding_box;
|
||||
use azalea_entity::{
|
||||
clamp_look_direction, metadata::Sprinting, move_relative, Attributes, Jumping, Local,
|
||||
LookDirection, Physics, Position, WorldName,
|
||||
},
|
||||
Instance, InstanceContainer,
|
||||
LookDirection, Physics, Position,
|
||||
};
|
||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
|
@ -35,7 +34,7 @@ impl Plugin for PhysicsPlugin {
|
|||
.add_systems(
|
||||
Update,
|
||||
force_jump_listener
|
||||
.before(azalea_world::entity::update_bounding_box)
|
||||
.before(update_bounding_box)
|
||||
.after(clamp_look_direction),
|
||||
)
|
||||
.add_systems(FixedUpdate, (ai_step, travel).chain().in_set(PhysicsSet));
|
||||
|
@ -51,7 +50,7 @@ fn travel(
|
|||
&mut LookDirection,
|
||||
&mut Position,
|
||||
&Attributes,
|
||||
&WorldName,
|
||||
&InstanceName,
|
||||
),
|
||||
With<Local>,
|
||||
>,
|
||||
|
@ -176,7 +175,7 @@ pub fn force_jump_listener(
|
|||
&Position,
|
||||
&LookDirection,
|
||||
&Sprinting,
|
||||
&WorldName,
|
||||
&InstanceName,
|
||||
)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
mut events: EventReader<ForceJumpEvent>,
|
||||
|
@ -327,10 +326,8 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
use azalea_core::{ChunkPos, ResourceLocation};
|
||||
use azalea_world::{
|
||||
entity::{EntityBundle, EntityPlugin, MinecraftEntityId},
|
||||
Chunk, PartialInstance,
|
||||
};
|
||||
use azalea_entity::{EntityBundle, EntityPlugin};
|
||||
use azalea_world::{Chunk, MinecraftEntityId, PartialInstance};
|
||||
use bevy_app::App;
|
||||
use bevy_time::fixed_timestep::FixedTime;
|
||||
use uuid::Uuid;
|
||||
|
|
|
@ -25,6 +25,7 @@ azalea-core = { path = "../azalea-core", optional = true, version = "^0.7.0", fe
|
|||
"serde",
|
||||
] }
|
||||
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-nbt = { path = "../azalea-nbt", version = "^0.7.0", features = [
|
||||
"serde",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::{ResourceLocation, Vec3};
|
||||
use azalea_entity::{metadata::apply_default_metadata, EntityBundle};
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_world::entity::{metadata::apply_default_metadata, EntityBundle};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::{ResourceLocation, Vec3};
|
||||
use azalea_entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle};
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_registry::EntityKind;
|
||||
use azalea_world::entity::{metadata::PlayerMetadataBundle, EntityBundle, PlayerBundle};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// This packet is sent by the server when a player comes into visible range,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
|
||||
use azalea_core::ParticleData;
|
||||
use azalea_core::particle::ParticleData;
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_entity::EntityMetadataItems;
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_world::entity::EntityMetadataItems;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||
pub struct ClientboundSetEntityDataPacket {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::ResourceLocation;
|
||||
use azalea_entity::attributes::AttributeModifier;
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_world::entity::attributes::AttributeModifier;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||
pub struct ClientboundUpdateAttributesPacket {
|
||||
|
|
|
@ -221,9 +221,11 @@ impl McBufWritable for Recipe {
|
|||
impl McBufReadable for Recipe {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let recipe_serializer_name = ResourceLocation::read_from(buf)?;
|
||||
let Ok(recipe_serializer) =
|
||||
RecipeSerializer::from_str(&recipe_serializer_name.to_string()) else {
|
||||
return Err(BufReadError::UnexpectedStringEnumVariant { id: recipe_serializer_name.to_string() });
|
||||
let Ok(recipe_serializer) = RecipeSerializer::from_str(&recipe_serializer_name.to_string())
|
||||
else {
|
||||
return Err(BufReadError::UnexpectedStringEnumVariant {
|
||||
id: recipe_serializer_name.to_string(),
|
||||
});
|
||||
};
|
||||
let identifier = ResourceLocation::read_from(buf)?;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ version = "0.7.0"
|
|||
[dependencies]
|
||||
azalea-buf = { path = "../azalea-buf", 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 }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// auto-generated (so you can add doc comments to the registry enums if you
|
||||
// want)
|
||||
|
||||
pub mod tags;
|
||||
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
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 bevy_ecs::system::Resource;
|
||||
use bevy_ecs::{component::Component, system::Resource};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use log::error;
|
||||
use nohash_hasher::IntMap;
|
||||
use parking_lot::RwLock;
|
||||
|
@ -8,7 +9,7 @@ use std::{
|
|||
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
|
||||
/// 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.
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -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 chunk_storage;
|
||||
mod container;
|
||||
pub mod entity;
|
||||
pub mod iterators;
|
||||
pub mod palette;
|
||||
mod world;
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
use crate::{
|
||||
entity::{
|
||||
EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName,
|
||||
},
|
||||
iterators::ChunkIterator,
|
||||
palette::Palette,
|
||||
ChunkStorage, InstanceContainer, PartialChunkStorage,
|
||||
};
|
||||
use azalea_block::{BlockState, BlockStates};
|
||||
use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, PartialChunkStorage};
|
||||
use azalea_block::{BlockState, BlockStates, FluidState};
|
||||
use azalea_core::{BlockPos, ChunkPos};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::{Changed, With, Without},
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
};
|
||||
use log::{debug, error, info};
|
||||
use bevy_ecs::{component::Component, entity::Entity};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use nohash_hasher::IntMap;
|
||||
use std::fmt::Formatter;
|
||||
use std::{
|
||||
|
@ -45,133 +34,45 @@ impl PartialInstance {
|
|||
}
|
||||
}
|
||||
|
||||
/// Remove new entities that have the same id as an existing entity, and
|
||||
/// increase the reference counts.
|
||||
///
|
||||
/// This is the reason why spawning entities into the ECS when you get a spawn
|
||||
/// entity packet is okay. This system will make sure the new entity gets
|
||||
/// combined into the old one.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deduplicate_entities(
|
||||
mut commands: Commands,
|
||||
mut query: Query<
|
||||
(Entity, &MinecraftEntityId, &WorldName),
|
||||
(Changed<MinecraftEntityId>, Without<Local>),
|
||||
>,
|
||||
mut loaded_by_query: Query<&mut LoadedBy>,
|
||||
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;
|
||||
}
|
||||
/// 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);
|
||||
|
||||
// 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());
|
||||
impl std::hash::Hash for MinecraftEntityId {
|
||||
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
||||
hasher.write_u32(self.0);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
impl nohash_hasher::IsEnabled for MinecraftEntityId {}
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
/// shared worlds.
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -191,6 +92,18 @@ impl Instance {
|
|||
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.
|
||||
///
|
||||
/// 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 {
|
||||
/// Make an empty world from this `ChunkStorage`. This is meant to be a
|
||||
/// convenience function for tests.
|
||||
|
|
|
@ -40,6 +40,7 @@ thiserror = "^1.0.43"
|
|||
tokio = "^1.29.1"
|
||||
uuid = "1.4.0"
|
||||
bevy_log = "0.11.0"
|
||||
azalea-entity = { version = "0.1.0", path = "../azalea-entity" }
|
||||
|
||||
[features]
|
||||
default = ["log"]
|
||||
|
|
|
@ -89,7 +89,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
bot.disconnect();
|
||||
}
|
||||
let Some(sender) = m.username() else {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
};
|
||||
// let mut ecs = bot.ecs.lock();
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
"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" => {
|
||||
let target_pos = bot
|
||||
.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());
|
||||
let Some(target_pos) = target_pos else {
|
||||
bot.chat("no lever found");
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
};
|
||||
bot.goto(BlockPosGoal::from(target_pos));
|
||||
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());
|
||||
let Some(target_pos) = target_pos else {
|
||||
bot.chat("no chest found");
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
};
|
||||
bot.look_at(target_pos.center());
|
||||
let container = bot.open_container(target_pos).await;
|
||||
|
|
|
@ -9,9 +9,10 @@ use crate::ecs::{
|
|||
system::{Commands, Query},
|
||||
};
|
||||
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_world::entity::{clamp_look_direction, EyeHeight, LookDirection};
|
||||
use azalea_world::entity::{metadata::Player, Jumping, Local, Position};
|
||||
use bevy_app::{FixedUpdate, Update};
|
||||
use bevy_ecs::prelude::Event;
|
||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
mod auto_respawn;
|
||||
mod bot;
|
||||
mod container;
|
||||
pub mod mining;
|
||||
pub mod pathfinder;
|
||||
pub mod prelude;
|
||||
pub mod swarm;
|
||||
|
@ -17,9 +18,10 @@ pub use azalea_brigadier as brigadier;
|
|||
pub use azalea_chat::FormattedText;
|
||||
pub use azalea_client::*;
|
||||
pub use azalea_core::{BlockPos, Vec3};
|
||||
pub use azalea_entity as entity;
|
||||
pub use azalea_protocol as protocol;
|
||||
pub use azalea_registry::{Block, EntityKind, Item};
|
||||
pub use azalea_world::{entity, Instance};
|
||||
pub use azalea_world::Instance;
|
||||
pub use bot::DefaultBotPlugins;
|
||||
use ecs::component::Component;
|
||||
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 azalea_client::{StartSprintEvent, StartWalkEvent};
|
||||
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_world::entity::metadata::Player;
|
||||
use azalea_world::entity::Local;
|
||||
use azalea_world::{
|
||||
entity::{Physics, Position, WorldName},
|
||||
InstanceContainer,
|
||||
};
|
||||
use azalea_world::{InstanceContainer, InstanceName};
|
||||
use bevy_app::{FixedUpdate, Update};
|
||||
use bevy_ecs::prelude::Event;
|
||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||
|
@ -106,7 +104,7 @@ pub struct ComputePath(Task<Option<PathFoundEvent>>);
|
|||
fn goto_listener(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<GotoEvent>,
|
||||
mut query: Query<(&Position, &WorldName)>,
|
||||
mut query: Query<(&Position, &InstanceName)>,
|
||||
instance_container: Res<InstanceContainer>,
|
||||
) {
|
||||
let thread_pool = AsyncComputeTaskPool::get();
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
//! re-exported here.
|
||||
|
||||
pub use crate::{
|
||||
bot::BotClientExt, container::ContainerClientExt, pathfinder::PathfinderClientExt,
|
||||
ClientBuilder,
|
||||
bot::BotClientExt, container::ContainerClientExt, mining::MiningExt,
|
||||
pathfinder::PathfinderClientExt, ClientBuilder,
|
||||
};
|
||||
pub use azalea_client::{Account, Client, Event};
|
||||
// this is necessary to make the macros that reference bevy_ecs work
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea_client::LocalPlayer;
|
||||
use azalea_world::entity::MinecraftEntityId;
|
||||
use azalea_world::MinecraftEntityId;
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
|
4
codegen/.gitignore
vendored
4
codegen/.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
downloads
|
||||
__pycache__
|
||||
*.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)
|
||||
|
||||
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(
|
||||
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.packet
|
||||
import lib.code.utils
|
||||
import lib.code.tags
|
||||
import lib.download
|
||||
import lib.extract
|
||||
import lib.utils
|
||||
|
||||
version_id = lib.code.version.get_version_id()
|
||||
registries = lib.extract.get_registries_report(version_id)
|
||||
def generate(version_id: str):
|
||||
registries = lib.extract.get_registries_report(version_id)
|
||||
|
||||
lib.code.registry.generate_registries(registries)
|
||||
lib.code.inventory.update_menus(registries['minecraft:menu']['entries'])
|
||||
lib.code.registry.generate_registries(registries)
|
||||
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.
|
||||
|
||||
|
||||
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:
|
||||
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:
|
||||
block_data_burger = blocks_burger[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_burger = block_data_burger.get('states', [])
|
||||
|
@ -134,9 +135,28 @@ def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: li
|
|||
else:
|
||||
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
|
||||
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('}')
|
||||
|
|
|
@ -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):
|
||||
# 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 = ''
|
||||
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 os
|
||||
|
||||
# make sure the downloads directory exists
|
||||
print('Making downloads')
|
||||
if not os.path.exists(get_dir_location('downloads')):
|
||||
print('Made downloads directory.', get_dir_location('downloads'))
|
||||
os.mkdir(get_dir_location('downloads'))
|
||||
# make sure the cache directory exists
|
||||
print('Making __cache__')
|
||||
if not os.path.exists(get_dir_location('__cache__')):
|
||||
print('Made __cache__ directory.', get_dir_location('__cache__'))
|
||||
os.mkdir(get_dir_location('__cache__'))
|
||||
|
||||
|
||||
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')
|
||||
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')
|
||||
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():
|
||||
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')
|
||||
os.system(
|
||||
f'cd {get_dir_location("downloads")} && git clone https://gitlab.bixilon.de/bixilon/pixlyzer.git && cd pixlyzer && git pull')
|
||||
return get_dir_location('downloads/pixlyzer')
|
||||
f'cd {get_dir_location("__cache__")} && git clone https://gitlab.bixilon.de/bixilon/pixlyzer.git && cd pixlyzer && git pull')
|
||||
return get_dir_location('__cache__/pixlyzer')
|
||||
|
||||
|
||||
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(
|
||||
f'\033[92mDownloading version manifest...\033[m')
|
||||
version_manifest_data = requests.get(
|
||||
'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)
|
||||
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)
|
||||
return version_manifest_data
|
||||
|
||||
|
||||
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()
|
||||
|
||||
print(
|
||||
|
@ -55,60 +55,60 @@ def get_version_data(version_id: str):
|
|||
filter(lambda v: v['id'] == version_id, version_manifest_data['versions']))['url']
|
||||
except StopIteration:
|
||||
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()
|
||||
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)
|
||||
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)
|
||||
return package_data
|
||||
|
||||
|
||||
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)
|
||||
print('\033[92mDownloading client jar...\033[m')
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
print('\033[92mDownloading server jar...\033[m')
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
client_mappings_url = package_data['downloads']['client_mappings']['url']
|
||||
|
||||
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)
|
||||
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()
|
||||
return Mappings.parse(mappings_text)
|
||||
|
||||
|
||||
def get_yarn_versions():
|
||||
# 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')
|
||||
yarn_versions_data = requests.get(
|
||||
'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)
|
||||
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)
|
||||
return yarn_versions_data
|
||||
|
||||
|
@ -121,7 +121,7 @@ def get_yarn_data(version_id: str):
|
|||
|
||||
def get_fabric_api_versions():
|
||||
# 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')
|
||||
fabric_api_versions_xml_text = requests.get(
|
||||
'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'):
|
||||
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))
|
||||
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())
|
||||
return fabric_api_versions
|
||||
|
||||
|
||||
def get_fabric_loader_versions():
|
||||
# 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')
|
||||
fabric_api_versions_json = requests.get(
|
||||
'https://meta.fabricmc.net/v2/versions/loader').json()
|
||||
|
@ -157,10 +157,10 @@ def get_fabric_loader_versions():
|
|||
for version in fabric_api_versions_json:
|
||||
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))
|
||||
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())
|
||||
return fabric_api_versions
|
||||
|
||||
|
@ -174,14 +174,14 @@ def clear_version_cache():
|
|||
'fabric_loader_versions.json'
|
||||
]
|
||||
for file in files:
|
||||
if os.path.exists(get_dir_location(f'downloads/{file}')):
|
||||
os.remove(get_dir_location(f'downloads/{file}'))
|
||||
if os.path.exists(get_dir_location(f'__cache__/{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):
|
||||
os.system(
|
||||
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):
|
||||
os.system(
|
||||
f'cd {pixlyzer_path} && git pull')
|
|
@ -12,27 +12,44 @@ import os
|
|||
|
||||
|
||||
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
|
||||
|
||||
get_server_jar(version_id)
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
def get_registries_report(version_id: str):
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
burger_data = get_burger_data_for_version(version_id)
|
||||
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):
|
||||
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_client_jar(version_id)
|
||||
|
||||
print('\033[92mRunning Burger...\033[m')
|
||||
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)
|
||||
|
||||
|
||||
|
@ -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.
|
||||
'''
|
||||
|
||||
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
|
||||
# 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):
|
||||
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:
|
||||
return f.read()
|
||||
|
||||
|
|
|
@ -133,9 +133,8 @@ language = lib.extract.get_en_us_lang(new_version_id)
|
|||
lib.code.language.write_language(language)
|
||||
|
||||
print('Generating registries...')
|
||||
registries = lib.extract.get_registries_report(new_version_id)
|
||||
lib.code.registry.generate_registries(registries)
|
||||
lib.code.inventory.update_menus(registries['minecraft:menu']['entries'])
|
||||
import genregistries
|
||||
genregistries.generate(new_version_id)
|
||||
|
||||
print('Generating entity metadata...')
|
||||
burger_entities_data = new_burger_data[0]['entities']
|
||||
|
|
Loading…
Reference in a new issue