fix: slime spawns
This commit is contained in:
parent
f6d0b4ab20
commit
09790fc093
295
Cargo.lock
generated
295
Cargo.lock
generated
@ -832,35 +832,6 @@ version = "0.10.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cookie"
|
|
||||||
version = "0.18.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
|
|
||||||
dependencies = [
|
|
||||||
"percent-encoding",
|
|
||||||
"time",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cookie_store"
|
|
||||||
version = "0.22.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
|
|
||||||
dependencies = [
|
|
||||||
"cookie",
|
|
||||||
"document-features",
|
|
||||||
"idna",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"time",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpp_demangle"
|
name = "cpp_demangle"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -1444,15 +1415,6 @@ dependencies = [
|
|||||||
"syn 3.0.4",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "document-features"
|
|
||||||
version = "0.2.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
|
||||||
dependencies = [
|
|
||||||
"litrs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.16.9"
|
version = "0.16.9"
|
||||||
@ -1824,8 +1786,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1835,9 +1799,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi",
|
||||||
"rand_core 0.10.1",
|
"rand_core 0.10.1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2096,6 +2062,22 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.27.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-timeout"
|
name = "hyper-timeout"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -2115,13 +2097,16 @@ version = "0.1.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2466,12 +2451,6 @@ version = "0.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "litrs"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@ -2487,6 +2466,12 @@ version = "0.4.34"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-slab"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lz4-java-wrc"
|
name = "lz4-java-wrc"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -3346,6 +3331,7 @@ dependencies = [
|
|||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"reqwest",
|
||||||
"rsa",
|
"rsa",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustc_version_runtime",
|
"rustc_version_runtime",
|
||||||
@ -3364,7 +3350,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-serde-structured",
|
"tracing-serde-structured",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ureq",
|
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-encoder 0.257.1",
|
"wasm-encoder 0.257.1",
|
||||||
"wasmparser 0.257.1",
|
"wasmparser 0.257.1",
|
||||||
@ -3455,6 +3440,7 @@ dependencies = [
|
|||||||
"pumpkin-util",
|
"pumpkin-util",
|
||||||
"pumpkin-world",
|
"pumpkin-world",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rustc-hash",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@ -3502,12 +3488,12 @@ name = "pumpkin-plugin-utils"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pumpkin-plugin-api",
|
"pumpkin-plugin-api",
|
||||||
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ureq",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3554,13 +3540,13 @@ dependencies = [
|
|||||||
"pumpkin-codecs",
|
"pumpkin-codecs",
|
||||||
"pumpkin-nbt",
|
"pumpkin-nbt",
|
||||||
"quote",
|
"quote",
|
||||||
|
"reqwest",
|
||||||
"rsa",
|
"rsa",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"syn 3.0.4",
|
"syn 3.0.4",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"ureq",
|
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3605,6 +3591,62 @@ version = "0.1.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn"
|
||||||
|
version = "0.11.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"cfg_aliases",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp 0.5.15",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"socket2",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-proto"
|
||||||
|
version = "0.11.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"lru-slab",
|
||||||
|
"rand",
|
||||||
|
"rand_pcg",
|
||||||
|
"ring",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"slab",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-udp"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||||
|
dependencies = [
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"socket2",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-udp"
|
name = "quinn-udp"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@ -3668,6 +3710,15 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_pcg"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@ -3775,6 +3826,47 @@ dependencies = [
|
|||||||
"bytecheck",
|
"bytecheck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc6979"
|
name = "rfc6979"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -4217,6 +4309,7 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"web-time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4262,6 +4355,12 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888"
|
checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -4385,6 +4484,18 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serdect"
|
name = "serdect"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@ -4631,6 +4742,9 @@ name = "sync_wrapper"
|
|||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
@ -4986,6 +5100,24 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -5121,37 +5253,6 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ureq"
|
|
||||||
version = "3.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.23.1",
|
|
||||||
"cookie_store",
|
|
||||||
"log",
|
|
||||||
"percent-encoding",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"ureq-proto",
|
|
||||||
"utf8-zero",
|
|
||||||
"webpki-roots",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ureq-proto"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.23.1",
|
|
||||||
"http",
|
|
||||||
"httparse",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@ -5164,12 +5265,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8-zero"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@ -5245,6 +5340,16 @@ dependencies = [
|
|||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.127"
|
version = "0.2.127"
|
||||||
@ -5656,6 +5761,26 @@ dependencies = [
|
|||||||
"wasmtime",
|
"wasmtime",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@ -5678,7 +5803,7 @@ dependencies = [
|
|||||||
"event-listener",
|
"event-listener",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"quinn-udp",
|
"quinn-udp 0.6.1",
|
||||||
"rtc",
|
"rtc",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -207,7 +207,7 @@ time = { version = "0.3", default-features = false, features = ["std"] }
|
|||||||
arc-swap = { version = "1.9", default-features = false }
|
arc-swap = { version = "1.9", default-features = false }
|
||||||
tokio-util = { version = "0.7.19", default-features = false }
|
tokio-util = { version = "0.7.19", default-features = false }
|
||||||
toml = { version = "1.1", default-features = false, features = ["parse", "display", "serde"] }
|
toml = { version = "1.1", default-features = false, features = ["parse", "display", "serde"] }
|
||||||
ureq = { version = "3.4.0", default-features = false, features = ["rustls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "http2"] }
|
||||||
notify = { version = "8.2.0", default-features = false, features = ["macos_fsevent"] }
|
notify = { version = "8.2.0", default-features = false, features = ["macos_fsevent"] }
|
||||||
|
|
||||||
sysinfo = { version = "0.39", default-features = false, features = ["system"] }
|
sysinfo = { version = "0.39", default-features = false, features = ["system"] }
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@ pumpkin-util.workspace = true
|
|||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
rustc-hash.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -58,7 +58,7 @@ pub mod stonecutter_screen_handler;
|
|||||||
pub mod sync_handler;
|
pub mod sync_handler;
|
||||||
pub mod window_property;
|
pub mod window_property;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
pub use error::InventoryError;
|
pub use error::InventoryError;
|
||||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||||
@ -71,10 +71,10 @@ use crate::player::player_inventory::PlayerInventory;
|
|||||||
/// (head, chest, legs, feet, off-hand) used by the player screen handler.
|
/// (head, chest, legs, feet, off-hand) used by the player screen handler.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A `HashMap` where keys are slot indices and values are the corresponding [`EquipmentSlot`]s.
|
/// A `FxHashMap` where keys are slot indices and values are the corresponding [`EquipmentSlot`]s.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build_equipment_slots() -> HashMap<usize, EquipmentSlot> {
|
pub fn build_equipment_slots() -> FxHashMap<usize, EquipmentSlot> {
|
||||||
let mut equipment_slots = HashMap::new();
|
let mut equipment_slots = FxHashMap::default();
|
||||||
equipment_slots.insert(
|
equipment_slots.insert(
|
||||||
EquipmentSlot::FEET.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
|
EquipmentSlot::FEET.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
|
||||||
EquipmentSlot::FEET,
|
EquipmentSlot::FEET,
|
||||||
@ -92,7 +92,6 @@ pub fn build_equipment_slots() -> HashMap<usize, EquipmentSlot> {
|
|||||||
EquipmentSlot::HEAD,
|
EquipmentSlot::HEAD,
|
||||||
);
|
);
|
||||||
|
|
||||||
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
|
|
||||||
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
|
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
|
||||||
equipment_slots
|
equipment_slots
|
||||||
}
|
}
|
||||||
|
|||||||
@ -492,7 +492,6 @@ impl Slot for MerchantResultSlot {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::HashMap,
|
|
||||||
sync::atomic::{AtomicI32, AtomicUsize, Ordering},
|
sync::atomic::{AtomicI32, AtomicUsize, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -627,7 +626,7 @@ mod tests {
|
|||||||
(
|
(
|
||||||
Arc::new(PlayerInventory::new(
|
Arc::new(PlayerInventory::new(
|
||||||
Arc::new(Mutex::new(EntityEquipment::new())),
|
Arc::new(Mutex::new(EntityEquipment::new())),
|
||||||
Arc::new(HashMap::new()),
|
Arc::new(rustc_hash::FxHashMap::default()),
|
||||||
)),
|
)),
|
||||||
Arc::new(SimpleInventory::new(3)),
|
Arc::new(SimpleInventory::new(3)),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -16,8 +16,8 @@ use pumpkin_data::item_stack::ItemStack;
|
|||||||
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
|
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
|
||||||
use pumpkin_util::Hand;
|
use pumpkin_util::Hand;
|
||||||
use pumpkin_world::inventory::{Clearable, Inventory};
|
use pumpkin_world::inventory::{Clearable, Inventory};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ pub struct PlayerInventory {
|
|||||||
/// Mapping of slot indices to equipment slot types.
|
/// Mapping of slot indices to equipment slot types.
|
||||||
///
|
///
|
||||||
/// Used to identify which slots correspond to armor and off-hand equipment.
|
/// Used to identify which slots correspond to armor and off-hand equipment.
|
||||||
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
pub equipment_slots: Arc<FxHashMap<usize, EquipmentSlot>>,
|
||||||
/// The currently selected hotbar slot index (0-8).
|
/// The currently selected hotbar slot index (0-8).
|
||||||
pub selected_slot: AtomicU8,
|
pub selected_slot: AtomicU8,
|
||||||
/// The entity equipment storage for armor and off-hand items.
|
/// The entity equipment storage for armor and off-hand items.
|
||||||
@ -60,7 +60,7 @@ impl PlayerInventory {
|
|||||||
// TODO: Add inventory load from nbt
|
// TODO: Add inventory load from nbt
|
||||||
pub fn new(
|
pub fn new(
|
||||||
entity_equipment: Arc<Mutex<EntityEquipment>>,
|
entity_equipment: Arc<Mutex<EntityEquipment>>,
|
||||||
equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
equipment_slots: Arc<FxHashMap<usize, EquipmentSlot>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
main_inventory: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
|
main_inventory: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
ureq = { workspace = true, features = ["json"] }
|
reqwest = { workspace = true, features = ["blocking", "json"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@ -18,7 +18,7 @@ pub enum HttpError {
|
|||||||
|
|
||||||
/// Helper client for querying Pumpkin marketplace REST APIs.
|
/// Helper client for querying Pumpkin marketplace REST APIs.
|
||||||
pub struct HttpClient {
|
pub struct HttpClient {
|
||||||
user_agent: String,
|
client: reqwest::blocking::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HttpClient {
|
impl Default for HttpClient {
|
||||||
@ -31,9 +31,11 @@ impl HttpClient {
|
|||||||
/// Creates a new HTTP client with the specified User-Agent header.
|
/// Creates a new HTTP client with the specified User-Agent header.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(user_agent: &str) -> Self {
|
pub fn new(user_agent: &str) -> Self {
|
||||||
Self {
|
let client = reqwest::blocking::Client::builder()
|
||||||
user_agent: user_agent.to_string(),
|
.user_agent(user_agent)
|
||||||
}
|
.build()
|
||||||
|
.unwrap_or_default();
|
||||||
|
Self { client }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP GET request and returns the response body as a string.
|
/// Performs an HTTP GET request and returns the response body as a string.
|
||||||
@ -42,24 +44,23 @@ impl HttpClient {
|
|||||||
///
|
///
|
||||||
/// Returns `HttpError` if the request fails or returns a non-2xx status code.
|
/// Returns `HttpError` if the request fails or returns a non-2xx status code.
|
||||||
pub fn get(&self, url: &str) -> Result<String, HttpError> {
|
pub fn get(&self, url: &str) -> Result<String, HttpError> {
|
||||||
let mut response = ureq::get(url)
|
let response = self
|
||||||
.header("User-Agent", &self.user_agent)
|
.client
|
||||||
|
.get(url)
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.call()
|
.send()
|
||||||
.map_err(|e| HttpError::RequestFailed(e.to_string()))?;
|
.map_err(|e| HttpError::RequestFailed(e.to_string()))?;
|
||||||
|
|
||||||
let status = response.status().as_u16();
|
let status = response.status().as_u16();
|
||||||
if status < 200 || status >= 300 {
|
if status < 200 || status >= 300 {
|
||||||
let body = response
|
let body = response
|
||||||
.body_mut()
|
.text()
|
||||||
.read_to_string()
|
|
||||||
.unwrap_or_else(|_| "Unknown error".to_string());
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
||||||
return Err(HttpError::BadStatus(status, body));
|
return Err(HttpError::BadStatus(status, body));
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
.body_mut()
|
.text()
|
||||||
.read_to_string()
|
|
||||||
.map_err(|e| HttpError::BodyRead(e.to_string()))
|
.map_err(|e| HttpError::BodyRead(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,25 +70,25 @@ impl HttpClient {
|
|||||||
///
|
///
|
||||||
/// Returns `HttpError` if the request fails or returns a non-2xx status code.
|
/// Returns `HttpError` if the request fails or returns a non-2xx status code.
|
||||||
pub fn post_json(&self, url: &str, json_payload: &str) -> Result<String, HttpError> {
|
pub fn post_json(&self, url: &str, json_payload: &str) -> Result<String, HttpError> {
|
||||||
let mut response = ureq::post(url)
|
let response = self
|
||||||
.header("User-Agent", &self.user_agent)
|
.client
|
||||||
|
.post(url)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.send(json_payload)
|
.body(json_payload.to_string())
|
||||||
|
.send()
|
||||||
.map_err(|e| HttpError::RequestFailed(e.to_string()))?;
|
.map_err(|e| HttpError::RequestFailed(e.to_string()))?;
|
||||||
|
|
||||||
let status = response.status().as_u16();
|
let status = response.status().as_u16();
|
||||||
if status < 200 || status >= 300 {
|
if status < 200 || status >= 300 {
|
||||||
let body = response
|
let body = response
|
||||||
.body_mut()
|
.text()
|
||||||
.read_to_string()
|
|
||||||
.unwrap_or_else(|_| "Unknown error".to_string());
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
||||||
return Err(HttpError::BadStatus(status, body));
|
return Err(HttpError::BadStatus(status, body));
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
.body_mut()
|
.text()
|
||||||
.read_to_string()
|
|
||||||
.map_err(|e| HttpError::BodyRead(e.to_string()))
|
.map_err(|e| HttpError::BodyRead(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ ecdsa.workspace = true
|
|||||||
rsa.workspace = true
|
rsa.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
crypto-bigint.workspace = true
|
crypto-bigint.workspace = true
|
||||||
ureq.workspace = true
|
reqwest.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true, features = ["html_reports"] }
|
criterion = { workspace = true, features = ["html_reports"] }
|
||||||
|
|||||||
@ -244,32 +244,26 @@ pub const OIDC_DISCOVERY_URL: &str =
|
|||||||
"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/1.0.0.0";
|
"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/1.0.0.0";
|
||||||
|
|
||||||
/// Fetches the OIDC JSON Web Key Set (JWKS) from the discovery endpoint.
|
/// Fetches the OIDC JSON Web Key Set (JWKS) from the discovery endpoint.
|
||||||
pub fn fetch_oidc_jwks(
|
pub async fn fetch_oidc_jwks(
|
||||||
discovery_url: Option<&str>,
|
discovery_url: Option<&str>,
|
||||||
connect_timeout_ms: u32,
|
connect_timeout_ms: u32,
|
||||||
read_timeout_ms: u32,
|
read_timeout_ms: u32,
|
||||||
) -> Result<(String, Jwks), AuthError> {
|
) -> Result<(String, Jwks), AuthError> {
|
||||||
let url = discovery_url.unwrap_or(OIDC_DISCOVERY_URL);
|
let url = discovery_url.unwrap_or(OIDC_DISCOVERY_URL);
|
||||||
let config = ureq::Agent::config_builder()
|
let client = reqwest::Client::builder()
|
||||||
.timeout_connect(Some(std::time::Duration::from_millis(
|
.connect_timeout(std::time::Duration::from_millis(connect_timeout_ms as u64))
|
||||||
connect_timeout_ms as u64,
|
.timeout(std::time::Duration::from_millis(read_timeout_ms as u64))
|
||||||
)))
|
.build()
|
||||||
.timeout_recv_response(Some(std::time::Duration::from_millis(
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
|
||||||
read_timeout_ms as u64,
|
|
||||||
)))
|
|
||||||
.build();
|
|
||||||
let agent: ureq::Agent = config.into();
|
|
||||||
|
|
||||||
let discovery: Value = agent
|
let discovery: Value = client
|
||||||
.get(url)
|
.get(url)
|
||||||
.call()
|
.send()
|
||||||
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.json()
|
||||||
.read_to_string()
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(format!("Failed to read response: {e}")))
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
|
||||||
.and_then(|s| {
|
|
||||||
serde_json::from_str(&s).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let service_uri = discovery
|
let service_uri = discovery
|
||||||
.get("result")
|
.get("result")
|
||||||
@ -281,16 +275,14 @@ pub fn fetch_oidc_jwks(
|
|||||||
.ok_or_else(|| AuthError::PublicKeyBuild("Discovery missing serviceUri".into()))?;
|
.ok_or_else(|| AuthError::PublicKeyBuild("Discovery missing serviceUri".into()))?;
|
||||||
|
|
||||||
let openid_config_url = format!("{service_uri}/.well-known/openid-configuration");
|
let openid_config_url = format!("{service_uri}/.well-known/openid-configuration");
|
||||||
let openid_config: Value = agent
|
let openid_config: Value = client
|
||||||
.get(&openid_config_url)
|
.get(&openid_config_url)
|
||||||
.call()
|
.send()
|
||||||
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.json()
|
||||||
.read_to_string()
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(format!("Failed to read response: {e}")))
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
|
||||||
.and_then(|s| {
|
|
||||||
serde_json::from_str(&s).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let jwks_uri = openid_config
|
let jwks_uri = openid_config
|
||||||
.get("jwks_uri")
|
.get("jwks_uri")
|
||||||
@ -303,16 +295,14 @@ pub fn fetch_oidc_jwks(
|
|||||||
.ok_or_else(|| AuthError::PublicKeyBuild("OpenID config missing issuer".into()))?
|
.ok_or_else(|| AuthError::PublicKeyBuild("OpenID config missing issuer".into()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let jwks: Jwks = agent
|
let jwks: Jwks = client
|
||||||
.get(jwks_uri)
|
.get(jwks_uri)
|
||||||
.call()
|
.send()
|
||||||
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?
|
||||||
.body_mut()
|
.json()
|
||||||
.read_to_string()
|
.await
|
||||||
.map_err(|e| AuthError::PublicKeyBuild(format!("Failed to read response: {e}")))
|
.map_err(|e| AuthError::PublicKeyBuild(e.to_string()))?;
|
||||||
.and_then(|s| {
|
|
||||||
serde_json::from_str(&s).map_err(|e| AuthError::PublicKeyBuild(e.to_string()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((issuer, jwks))
|
Ok((issuer, jwks))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::io::{ErrorKind, Read};
|
use std::io::{ErrorKind, Read};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -141,15 +141,15 @@ impl ChunkBitmap {
|
|||||||
// Repeated: u8 key_len, key bytes, u32 value
|
// Repeated: u8 key_len, key bytes, u32 value
|
||||||
// Terminated by a single 0x00 byte.
|
// Terminated by a single 0x00 byte.
|
||||||
|
|
||||||
struct NbtFeatures(HashMap<String, u32>);
|
struct NbtFeatures(FxHashMap<String, u32>);
|
||||||
|
|
||||||
impl NbtFeatures {
|
impl NbtFeatures {
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self(HashMap::new())
|
Self(FxHashMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_bytes(buf: &mut impl Buf) -> Result<Self, ChunkReadingError> {
|
fn from_bytes(buf: &mut impl Buf) -> Result<Self, ChunkReadingError> {
|
||||||
let mut map = HashMap::new();
|
let mut map = FxHashMap::default();
|
||||||
loop {
|
loop {
|
||||||
if !buf.has_remaining() {
|
if !buf.has_remaining() {
|
||||||
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
return Err(ChunkReadingError::IoError(std::io::Error::from(
|
||||||
|
|||||||
@ -845,28 +845,31 @@ impl LightContainer {
|
|||||||
matches!(self, Self::Empty(_))
|
matches!(self, Self::Empty(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
const fn index(x: usize, y: usize, z: usize) -> usize {
|
const fn index(x: usize, y: usize, z: usize) -> usize {
|
||||||
y * 16 * 16 + z * 16 + x
|
y * 16 * 16 + z * 16 + x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
|
pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Self::Full(data) => {
|
Self::Full(data) => {
|
||||||
let index = Self::index(x, y, z);
|
let index = Self::index(x, y, z);
|
||||||
data[index >> 1] >> (4 * (index & 1)) & 0x0F
|
(data[index >> 1] >> (4 * (index & 1))) & 0x0F
|
||||||
}
|
}
|
||||||
Self::Empty(default) => *default,
|
Self::Empty(default) => *default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set(&mut self, x: usize, y: usize, z: usize, value: u8) {
|
pub fn set(&mut self, x: usize, y: usize, z: usize, value: u8) {
|
||||||
match self {
|
match self {
|
||||||
Self::Full(data) => {
|
Self::Full(data) => {
|
||||||
let index = Self::index(x, y, z);
|
let index = Self::index(x, y, z);
|
||||||
let mask = 0x0F << (4 * (index & 1));
|
let shift = 4 * (index & 1);
|
||||||
data[index >> 1] &= !mask;
|
let mask = 0x0F << shift;
|
||||||
data[index >> 1] |= value << (4 * (index & 1));
|
data[index >> 1] = (data[index >> 1] & !mask) | (value << shift);
|
||||||
}
|
}
|
||||||
Self::Empty(default) => {
|
Self::Empty(default) => {
|
||||||
if value != *default {
|
if value != *default {
|
||||||
@ -877,6 +880,39 @@ impl LightContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_column_y_range(
|
||||||
|
&mut self,
|
||||||
|
x: usize,
|
||||||
|
z: usize,
|
||||||
|
y_start: usize,
|
||||||
|
y_end: usize,
|
||||||
|
value: u8,
|
||||||
|
) {
|
||||||
|
if y_start >= y_end {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::Full(data) => {
|
||||||
|
let shift = 4 * (x & 1);
|
||||||
|
let mask = 0x0F << shift;
|
||||||
|
let val = (value & 0x0F) << shift;
|
||||||
|
let mut byte_idx = (y_start * 256 + z * 16 + x) >> 1;
|
||||||
|
for _ in y_start..y_end {
|
||||||
|
data[byte_idx] = (data[byte_idx] & !mask) | val;
|
||||||
|
byte_idx += 128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Empty(default) => {
|
||||||
|
if value != *default {
|
||||||
|
*self = Self::new_filled(*default);
|
||||||
|
self.set_column_y_range(x, z, y_start, y_end, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn fill(&mut self, value: u8) {
|
pub fn fill(&mut self, value: u8) {
|
||||||
*self = Self::new_filled(value);
|
*self = Self::new_filled(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -242,41 +242,82 @@ impl GenerationSchedule {
|
|||||||
chunk_map: &HashMap<ChunkPos, ChunkHolder>,
|
chunk_map: &HashMap<ChunkPos, ChunkHolder>,
|
||||||
last_level: &ChunkLevel,
|
last_level: &ChunkLevel,
|
||||||
last_high_priority: &[ChunkPos],
|
last_high_priority: &[ChunkPos],
|
||||||
waiting_for_chunks: &HashSetType<NodeKey>,
|
_waiting_for_chunks: &HashSetType<NodeKey>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
debug_assert!(queue.is_empty());
|
debug_assert!(queue.is_empty());
|
||||||
|
|
||||||
|
let mut to_drop = Vec::new();
|
||||||
let mut ready = Vec::new();
|
let mut ready = Vec::new();
|
||||||
for (key, node) in &mut graph.nodes {
|
|
||||||
if node.stage == StagedChunkEnum::None
|
for (key, node) in &graph.nodes {
|
||||||
|| node.in_degree != 0
|
if node.stage == StagedChunkEnum::None || node.in_flight {
|
||||||
|| node.in_queue
|
|
||||||
|| node.in_flight
|
|
||||||
|| waiting_for_chunks.contains(&key)
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(holder) = chunk_map.get(&node.pos) else {
|
let Some(holder) = chunk_map.get(&node.pos) else {
|
||||||
|
to_drop.push(key);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if holder.current_stage >= node.stage || holder.tasks[node.stage as usize] != key {
|
|
||||||
|
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
||||||
|
if holder.current_stage >= node.stage
|
||||||
|
|| holder.tasks[node.stage as usize] != key
|
||||||
|
|| node.stage > effective_target
|
||||||
|
{
|
||||||
|
to_drop.push(key);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ready.push((key, node.pos, node.stage));
|
|
||||||
|
if node.in_degree == 0 && !node.in_queue {
|
||||||
|
ready.push((key, node.pos, node.stage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, pos, stage) in &ready {
|
for key in to_drop {
|
||||||
let Some(node) = graph.nodes.get_mut(*key) else {
|
let Some(old) = graph.nodes.remove(key) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
node.in_queue = true;
|
let mut edge = old.edge;
|
||||||
queue.push(TaskHeapNode(
|
while !edge.is_null() {
|
||||||
Self::calc_priority(last_level, last_high_priority, *pos, *stage),
|
let Some(cur) = graph.edges.remove(edge) else {
|
||||||
*key,
|
break;
|
||||||
));
|
};
|
||||||
|
if let Some(target_node) = graph.nodes.get_mut(cur.to)
|
||||||
|
&& target_node.in_degree > 0
|
||||||
|
{
|
||||||
|
target_node.in_degree -= 1;
|
||||||
|
if target_node.in_degree == 0 && !target_node.in_queue && !target_node.in_flight
|
||||||
|
{
|
||||||
|
target_node.in_queue = true;
|
||||||
|
queue.push(TaskHeapNode(
|
||||||
|
Self::calc_priority(
|
||||||
|
last_level,
|
||||||
|
last_high_priority,
|
||||||
|
target_node.pos,
|
||||||
|
target_node.stage,
|
||||||
|
),
|
||||||
|
cur.to,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edge = cur.next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ready.len()
|
for (key, pos, stage) in ready {
|
||||||
|
if let Some(node) = graph.nodes.get_mut(key)
|
||||||
|
&& node.in_degree == 0
|
||||||
|
&& !node.in_queue
|
||||||
|
{
|
||||||
|
node.in_queue = true;
|
||||||
|
queue.push(TaskHeapNode(
|
||||||
|
Self::calc_priority(last_level, last_high_priority, pos, stage),
|
||||||
|
key,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
|
/// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
|
||||||
@ -296,7 +337,7 @@ impl GenerationSchedule {
|
|||||||
chunk_pos: ChunkPos,
|
chunk_pos: ChunkPos,
|
||||||
holder: &mut ChunkHolder,
|
holder: &mut ChunkHolder,
|
||||||
req_stage: StagedChunkEnum,
|
req_stage: StagedChunkEnum,
|
||||||
) {
|
) -> [bool; StagedChunkEnum::COUNT] {
|
||||||
// Insert occupied_by edge head
|
// Insert occupied_by edge head
|
||||||
holder.occupied_by = graph.edges.insert(crate::chunk_system::dag::Edge::new(
|
holder.occupied_by = graph.edges.insert(crate::chunk_system::dag::Edge::new(
|
||||||
dependency_task,
|
dependency_task,
|
||||||
@ -317,6 +358,7 @@ impl GenerationSchedule {
|
|||||||
|
|
||||||
// Effective target is the max of what the player wants and what dependencies need.
|
// Effective target is the max of what the player wants and what dependencies need.
|
||||||
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
||||||
|
let mut newly_created = [false; StagedChunkEnum::COUNT];
|
||||||
|
|
||||||
// Create any missing tasks from current_stage+1 up to effective_target.
|
// Create any missing tasks from current_stage+1 up to effective_target.
|
||||||
// We do this even when current_stage >= req_stage, because dependency_stage may
|
// We do this even when current_stage >= req_stage, because dependency_stage may
|
||||||
@ -325,7 +367,6 @@ impl GenerationSchedule {
|
|||||||
let empty = StagedChunkEnum::Empty as usize;
|
let empty = StagedChunkEnum::Empty as usize;
|
||||||
let start = (holder.current_stage as usize + 1).max(empty);
|
let start = (holder.current_stage as usize + 1).max(empty);
|
||||||
let end = effective_target as u8 as usize;
|
let end = effective_target as u8 as usize;
|
||||||
let mut newly_created = [false; StagedChunkEnum::COUNT];
|
|
||||||
|
|
||||||
for (i, flag) in newly_created[start..=end].iter_mut().enumerate() {
|
for (i, flag) in newly_created[start..=end].iter_mut().enumerate() {
|
||||||
let stage_i = start + i;
|
let stage_i = start + i;
|
||||||
@ -385,7 +426,7 @@ impl GenerationSchedule {
|
|||||||
// it was only blocked on `occupied` (handled above) and the stage itself is done.
|
// it was only blocked on `occupied` (handled above) and the stage itself is done.
|
||||||
// Do NOT add an edge here: tasks[req_stage] is null (completed and dropped).
|
// Do NOT add an edge here: tasks[req_stage] is null (completed and dropped).
|
||||||
if holder.current_stage >= req_stage {
|
if holder.current_stage >= req_stage {
|
||||||
return;
|
return newly_created;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire req_stage task → dependency_task so dependency_task can't run until
|
// Wire req_stage task → dependency_task so dependency_task can't run until
|
||||||
@ -400,6 +441,8 @@ impl GenerationSchedule {
|
|||||||
"holder.tasks[req_stage] must not be null before adding edge"
|
"holder.tasks[req_stage] must not be null before adding edge"
|
||||||
);
|
);
|
||||||
graph.add_edge(ano_task, dependency_task);
|
graph.add_edge(ano_task, dependency_task);
|
||||||
|
|
||||||
|
newly_created
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if any tasks parked in `waiting_for_chunks` now have all their neighbor
|
/// Check if any tasks parked in `waiting_for_chunks` now have all their neighbor
|
||||||
@ -411,11 +454,27 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut now_ready: Vec<NodeKey> = Vec::new();
|
let mut now_ready: Vec<NodeKey> = Vec::new();
|
||||||
|
let mut stale: Vec<NodeKey> = Vec::new();
|
||||||
|
|
||||||
self.waiting_for_chunks.retain(|&node_key| {
|
self.waiting_for_chunks.retain(|&node_key| {
|
||||||
let Some(node) = self.graph.nodes.get(node_key) else {
|
let Some(node) = self.graph.nodes.get(node_key) else {
|
||||||
return false; // node was dropped, discard silently
|
return false; // node was dropped, discard silently
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(holder) = self.chunk_map.get(&node.pos) else {
|
||||||
|
stale.push(node_key);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let effective_target = holder.target_stage.max(holder.dependency_stage);
|
||||||
|
if holder.current_stage >= node.stage
|
||||||
|
|| holder.tasks[node.stage as usize] != node_key
|
||||||
|
|| node.stage > effective_target
|
||||||
|
{
|
||||||
|
stale.push(node_key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let read_radius = node.stage.get_read_radius();
|
let read_radius = node.stage.get_read_radius();
|
||||||
let pos = node.pos;
|
let pos = node.pos;
|
||||||
let all_ready = (-read_radius..=read_radius).all(|dx| {
|
let all_ready = (-read_radius..=read_radius).all(|dx| {
|
||||||
@ -437,6 +496,10 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for node_key in stale {
|
||||||
|
self.drop_node(node_key);
|
||||||
|
}
|
||||||
|
|
||||||
for node_key in now_ready {
|
for node_key in now_ready {
|
||||||
if let Some(n) = self.graph.nodes.get_mut(node_key)
|
if let Some(n) = self.graph.nodes.get_mut(node_key)
|
||||||
&& n.in_degree == 0
|
&& n.in_degree == 0
|
||||||
@ -462,6 +525,9 @@ impl GenerationSchedule {
|
|||||||
let Some(new_level) = new_data.0 else {
|
let Some(new_level) = new_data.0 else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
let mut worklist: std::collections::VecDeque<(ChunkPos, StagedChunkEnum, NodeKey)> =
|
||||||
|
std::collections::VecDeque::new();
|
||||||
|
|
||||||
for (pos, (old_stage, new_stage)) in new_level.0 {
|
for (pos, (old_stage, new_stage)) in new_level.0 {
|
||||||
debug_assert_ne!(old_stage, new_stage);
|
debug_assert_ne!(old_stage, new_stage);
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
@ -537,7 +603,7 @@ impl GenerationSchedule {
|
|||||||
let new_pos = pos.add_raw(dx, dz);
|
let new_pos = pos.add_raw(dx, dz);
|
||||||
let req_stage = dependency[dx.abs().max(dz.abs()) as usize];
|
let req_stage = dependency[dx.abs().max(dz.abs()) as usize];
|
||||||
if new_pos == pos {
|
if new_pos == pos {
|
||||||
Self::ensure_dependency_chain(
|
let newly_created = Self::ensure_dependency_chain(
|
||||||
&mut self.graph,
|
&mut self.graph,
|
||||||
&mut self.queue,
|
&mut self.queue,
|
||||||
&self.last_level,
|
&self.last_level,
|
||||||
@ -547,20 +613,33 @@ impl GenerationSchedule {
|
|||||||
&mut holder,
|
&mut holder,
|
||||||
req_stage,
|
req_stage,
|
||||||
);
|
);
|
||||||
|
for (stage_i, &created) in newly_created.iter().enumerate() {
|
||||||
|
if created && stage_i > 1 {
|
||||||
|
let stage = StagedChunkEnum::from(stage_i as u8);
|
||||||
|
let dependency = stage.get_direct_dependencies();
|
||||||
|
let radius = stage.get_direct_radius();
|
||||||
|
let cur_task = holder.tasks[stage_i];
|
||||||
|
for ndx in -radius..=radius {
|
||||||
|
for ndz in -radius..=radius {
|
||||||
|
if ndx == 0 && ndz == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let neighbor_pos = new_pos.add_raw(ndx, ndz);
|
||||||
|
let neighbor_req = dependency
|
||||||
|
[ndx.abs().max(ndz.abs()) as usize];
|
||||||
|
worklist.push_back((
|
||||||
|
neighbor_pos,
|
||||||
|
neighbor_req,
|
||||||
|
cur_task,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ano_chunk = self.chunk_map.entry(new_pos).or_default();
|
worklist.push_back((new_pos, req_stage, task));
|
||||||
Self::ensure_dependency_chain(
|
|
||||||
&mut self.graph,
|
|
||||||
&mut self.queue,
|
|
||||||
&self.last_level,
|
|
||||||
&self.last_high_priority,
|
|
||||||
task,
|
|
||||||
new_pos,
|
|
||||||
ano_chunk,
|
|
||||||
req_stage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -573,6 +652,40 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
self.chunk_map.insert(pos, holder);
|
self.chunk_map.insert(pos, holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while let Some((pos, req_stage, dep_task)) = worklist.pop_front() {
|
||||||
|
let mut holder = self.chunk_map.remove(&pos).unwrap_or_default();
|
||||||
|
let newly_created = Self::ensure_dependency_chain(
|
||||||
|
&mut self.graph,
|
||||||
|
&mut self.queue,
|
||||||
|
&new_level.1,
|
||||||
|
&self.last_high_priority,
|
||||||
|
dep_task,
|
||||||
|
pos,
|
||||||
|
&mut holder,
|
||||||
|
req_stage,
|
||||||
|
);
|
||||||
|
for (stage_i, &created) in newly_created.iter().enumerate() {
|
||||||
|
if created && stage_i > 1 {
|
||||||
|
let stage = StagedChunkEnum::from(stage_i as u8);
|
||||||
|
let dependency = stage.get_direct_dependencies();
|
||||||
|
let radius = stage.get_direct_radius();
|
||||||
|
let cur_task = holder.tasks[stage_i];
|
||||||
|
for dx in -radius..=radius {
|
||||||
|
for dz in -radius..=radius {
|
||||||
|
if dx == 0 && dz == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let neighbor_pos = pos.add_raw(dx, dz);
|
||||||
|
let neighbor_req = dependency[dx.abs().max(dz.abs()) as usize];
|
||||||
|
worklist.push_back((neighbor_pos, neighbor_req, cur_task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.chunk_map.insert(pos, holder);
|
||||||
|
}
|
||||||
|
|
||||||
self.last_level = new_level.1;
|
self.last_level = new_level.1;
|
||||||
self.queue_dirty = true;
|
self.queue_dirty = true;
|
||||||
true
|
true
|
||||||
@ -852,20 +965,16 @@ impl GenerationSchedule {
|
|||||||
debug_assert!(node.in_degree >= 1);
|
debug_assert!(node.in_degree >= 1);
|
||||||
node.in_degree -= 1;
|
node.in_degree -= 1;
|
||||||
if node.in_degree == 0 && !node.in_queue {
|
if node.in_degree == 0 && !node.in_queue {
|
||||||
// Don't queue if parked in waiting_for_chunks — check_waiting_tasks()
|
self.queue.push(TaskHeapNode(
|
||||||
// will re-queue it once chunk data arrives.
|
Self::calc_priority(
|
||||||
if !self.waiting_for_chunks.contains(&cur.to) {
|
&self.last_level,
|
||||||
self.queue.push(TaskHeapNode(
|
&self.last_high_priority,
|
||||||
Self::calc_priority(
|
node.pos,
|
||||||
&self.last_level,
|
node.stage,
|
||||||
&self.last_high_priority,
|
),
|
||||||
node.pos,
|
cur.to,
|
||||||
node.stage,
|
));
|
||||||
),
|
node.in_queue = true;
|
||||||
cur.to,
|
|
||||||
));
|
|
||||||
node.in_queue = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edge = cur.next;
|
edge = cur.next;
|
||||||
@ -933,15 +1042,6 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If this chunk was only loaded for a dependency or cancelled
|
|
||||||
// and is no longer needed, clear dependency_stage and queue unload.
|
|
||||||
if holder.target_stage == StagedChunkEnum::None
|
|
||||||
&& holder.current_stage >= holder.dependency_stage
|
|
||||||
{
|
|
||||||
holder.dependency_stage = StagedChunkEnum::None;
|
|
||||||
self.unload_chunks.insert(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.chunk = Some(chunk);
|
holder.chunk = Some(chunk);
|
||||||
self.chunk_map.insert(pos, holder);
|
self.chunk_map.insert(pos, holder);
|
||||||
|
|
||||||
@ -1021,15 +1121,6 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
|
|
||||||
// If this chunk was only loaded for a dependency or cancelled
|
|
||||||
// and is no longer needed, clear dependency_stage and queue unload.
|
|
||||||
if holder.target_stage == StagedChunkEnum::None
|
|
||||||
&& holder.current_stage >= holder.dependency_stage
|
|
||||||
{
|
|
||||||
holder.dependency_stage = StagedChunkEnum::None;
|
|
||||||
self.unload_chunks.insert(new_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.chunk_map.insert(new_pos, holder);
|
self.chunk_map.insert(new_pos, holder);
|
||||||
}
|
}
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
@ -1056,14 +1147,6 @@ impl GenerationSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear dependency_stage and queue unload if no longer needed
|
|
||||||
if holder.target_stage == StagedChunkEnum::None
|
|
||||||
&& holder.current_stage >= holder.dependency_stage
|
|
||||||
{
|
|
||||||
holder.dependency_stage = StagedChunkEnum::None;
|
|
||||||
self.unload_chunks.insert(new_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.occupied = NodeKey::null();
|
holder.occupied = NodeKey::null();
|
||||||
holder.chunk = Some(Chunk::Proto(chunk));
|
holder.chunk = Some(Chunk::Proto(chunk));
|
||||||
self.chunk_map.insert(new_pos, holder);
|
self.chunk_map.insert(new_pos, holder);
|
||||||
|
|||||||
@ -4,34 +4,73 @@ use crate::generation::height_limit::HeightLimitView;
|
|||||||
use crate::generation::proto_chunk::GenerationCache;
|
use crate::generation::proto_chunk::GenerationCache;
|
||||||
use crate::lighting::storage::{get_block_light, get_sky_light, set_block_light, set_sky_light};
|
use crate::lighting::storage::{get_block_light, get_sky_light, set_block_light, set_sky_light};
|
||||||
use pumpkin_config::lighting::LightingEngineConfig;
|
use pumpkin_config::lighting::LightingEngineConfig;
|
||||||
use pumpkin_data::BlockDirection;
|
use pumpkin_data::{BlockDirection, BlockStateId};
|
||||||
use pumpkin_util::HeightMap;
|
use pumpkin_util::HeightMap;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
//use std::time::Instant;
|
|
||||||
|
|
||||||
// These are hit on every neighbour of every propagated block, so the hash
|
use crate::ProtoChunk;
|
||||||
// function dominates. Use rustc-hash's fast hasher instead of the std default
|
|
||||||
// (SipHash), which is what "Fast" was meant to imply.
|
|
||||||
type FastHashSet<K> = rustc_hash::FxHashSet<K>;
|
|
||||||
type FastHashMap<K, V> = rustc_hash::FxHashMap<K, V>;
|
|
||||||
|
|
||||||
/// Trait to unify Block and Sky light logic
|
|
||||||
pub trait LightProvider {
|
pub trait LightProvider {
|
||||||
fn get_light(cache: &Cache, pos: BlockPos) -> u8;
|
fn get_light(cache: &Cache, pos: BlockPos) -> u8;
|
||||||
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8);
|
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8);
|
||||||
|
fn get_light_proto(
|
||||||
|
chunk: &ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
) -> u8;
|
||||||
|
fn set_light_proto(
|
||||||
|
chunk: &mut ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
level: u8,
|
||||||
|
);
|
||||||
fn propagate_level(current_level: u8, opacity: u8, dir: BlockDirection) -> u8;
|
fn propagate_level(current_level: u8, opacity: u8, dir: BlockDirection) -> u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockLightProvider;
|
pub struct BlockLightProvider;
|
||||||
impl LightProvider for BlockLightProvider {
|
impl LightProvider for BlockLightProvider {
|
||||||
|
#[inline]
|
||||||
fn get_light(cache: &Cache, pos: BlockPos) -> u8 {
|
fn get_light(cache: &Cache, pos: BlockPos) -> u8 {
|
||||||
get_block_light(cache, pos)
|
get_block_light(cache, pos)
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
||||||
set_block_light(cache, pos, level);
|
set_block_light(cache, pos, level);
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
fn get_light_proto(
|
||||||
|
chunk: &ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
) -> u8 {
|
||||||
|
chunk
|
||||||
|
.light
|
||||||
|
.block_light
|
||||||
|
.get(section_idx)
|
||||||
|
.map_or(0, |c| c.get(lx, ly, lz))
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn set_light_proto(
|
||||||
|
chunk: &mut ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
level: u8,
|
||||||
|
) {
|
||||||
|
if let Some(c) = chunk.light.block_light.get_mut(section_idx) {
|
||||||
|
c.set(lx, ly, lz, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
fn propagate_level(current_level: u8, opacity: u8, _dir: BlockDirection) -> u8 {
|
fn propagate_level(current_level: u8, opacity: u8, _dir: BlockDirection) -> u8 {
|
||||||
current_level.saturating_sub(opacity.max(1))
|
current_level.saturating_sub(opacity.max(1))
|
||||||
}
|
}
|
||||||
@ -39,12 +78,42 @@ impl LightProvider for BlockLightProvider {
|
|||||||
|
|
||||||
pub struct SkyLightProvider;
|
pub struct SkyLightProvider;
|
||||||
impl LightProvider for SkyLightProvider {
|
impl LightProvider for SkyLightProvider {
|
||||||
|
#[inline]
|
||||||
fn get_light(cache: &Cache, pos: BlockPos) -> u8 {
|
fn get_light(cache: &Cache, pos: BlockPos) -> u8 {
|
||||||
get_sky_light(cache, pos)
|
get_sky_light(cache, pos)
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
fn set_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
||||||
set_sky_light(cache, pos, level);
|
set_sky_light(cache, pos, level);
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
fn get_light_proto(
|
||||||
|
chunk: &ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
) -> u8 {
|
||||||
|
chunk
|
||||||
|
.light
|
||||||
|
.sky_light
|
||||||
|
.get(section_idx)
|
||||||
|
.map_or(15, |c| c.get(lx, ly, lz))
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn set_light_proto(
|
||||||
|
chunk: &mut ProtoChunk,
|
||||||
|
section_idx: usize,
|
||||||
|
lx: usize,
|
||||||
|
ly: usize,
|
||||||
|
lz: usize,
|
||||||
|
level: u8,
|
||||||
|
) {
|
||||||
|
if let Some(c) = chunk.light.sky_light.get_mut(section_idx) {
|
||||||
|
c.set(lx, ly, lz, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
fn propagate_level(current_level: u8, opacity: u8, dir: BlockDirection) -> u8 {
|
fn propagate_level(current_level: u8, opacity: u8, dir: BlockDirection) -> u8 {
|
||||||
if current_level == 15 && dir == BlockDirection::Down && opacity == 0 {
|
if current_level == 15 && dir == BlockDirection::Down && opacity == 0 {
|
||||||
return 15;
|
return 15;
|
||||||
@ -57,12 +126,114 @@ impl LightProvider for SkyLightProvider {
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct PropagationEntry {
|
pub struct PropagationEntry {
|
||||||
pos: BlockPos,
|
pos: BlockPos,
|
||||||
skip_direction: Option<BlockDirection>, // direction from which the light came, used to prevent back-propagation
|
skip_direction: Option<BlockDirection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VisitedBitSet {
|
||||||
|
bits: Vec<u64>,
|
||||||
|
min_x: i32,
|
||||||
|
min_y: i32,
|
||||||
|
min_z: i32,
|
||||||
|
size_x: usize,
|
||||||
|
size_y: usize,
|
||||||
|
size_z: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VisitedBitSet {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitedBitSet {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
bits: Vec::new(),
|
||||||
|
min_x: 0,
|
||||||
|
min_y: 0,
|
||||||
|
min_z: 0,
|
||||||
|
size_x: 0,
|
||||||
|
size_y: 0,
|
||||||
|
size_z: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_capacity(
|
||||||
|
&mut self,
|
||||||
|
min_x: i32,
|
||||||
|
min_y: i32,
|
||||||
|
min_z: i32,
|
||||||
|
size_x: usize,
|
||||||
|
size_y: usize,
|
||||||
|
size_z: usize,
|
||||||
|
) {
|
||||||
|
self.min_x = min_x;
|
||||||
|
self.min_y = min_y;
|
||||||
|
self.min_z = min_z;
|
||||||
|
self.size_x = size_x;
|
||||||
|
self.size_y = size_y;
|
||||||
|
self.size_z = size_z;
|
||||||
|
let total = size_x * size_y * size_z;
|
||||||
|
let words = total.div_ceil(64);
|
||||||
|
if self.bits.len() == words {
|
||||||
|
self.bits.fill(0);
|
||||||
|
} else {
|
||||||
|
self.bits.resize(words, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.bits.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn test_and_set(&mut self, x: i32, y: i32, z: i32) -> bool {
|
||||||
|
let lx = (x - self.min_x) as usize;
|
||||||
|
let ly = (y - self.min_y) as usize;
|
||||||
|
let lz = (z - self.min_z) as usize;
|
||||||
|
if lx >= self.size_x || ly >= self.size_y || lz >= self.size_z {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let idx = (ly * self.size_z + lz) * self.size_x + lx;
|
||||||
|
let word = idx >> 6;
|
||||||
|
let mask = 1u64 << (idx & 63);
|
||||||
|
if let Some(w) = self.bits.get_mut(word) {
|
||||||
|
let prev = *w;
|
||||||
|
if prev & mask != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*w = prev | mask;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_visited(&self, x: i32, y: i32, z: i32) -> bool {
|
||||||
|
let lx = (x - self.min_x) as usize;
|
||||||
|
let ly = (y - self.min_y) as usize;
|
||||||
|
let lz = (z - self.min_z) as usize;
|
||||||
|
if lx >= self.size_x || ly >= self.size_y || lz >= self.size_z {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let idx = (ly * self.size_z + lz) * self.size_x + lx;
|
||||||
|
let word = idx >> 6;
|
||||||
|
let mask = 1u64 << (idx & 63);
|
||||||
|
if let Some(&w) = self.bits.get(word) {
|
||||||
|
(w & mask) != 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LightPropagator<P: LightProvider> {
|
pub struct LightPropagator<P: LightProvider> {
|
||||||
pub(crate) queue: VecDeque<PropagationEntry>,
|
pub(crate) queue: VecDeque<PropagationEntry>,
|
||||||
pub(crate) visited: FastHashSet<BlockPos>,
|
pub(crate) visited: VisitedBitSet,
|
||||||
pub(crate) decrease_queue: VecDeque<(BlockPos, u8)>,
|
pub(crate) decrease_queue: VecDeque<(BlockPos, u8)>,
|
||||||
_marker: std::marker::PhantomData<P>,
|
_marker: std::marker::PhantomData<P>,
|
||||||
}
|
}
|
||||||
@ -71,8 +242,8 @@ impl<P: LightProvider> LightPropagator<P> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
queue: VecDeque::with_capacity(4096),
|
queue: VecDeque::with_capacity(8192),
|
||||||
visited: FastHashSet::default(),
|
visited: VisitedBitSet::new(),
|
||||||
decrease_queue: VecDeque::new(),
|
decrease_queue: VecDeque::new(),
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
@ -84,19 +255,21 @@ impl<P: LightProvider> LightPropagator<P> {
|
|||||||
self.decrease_queue.clear();
|
self.decrease_queue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Core Propagation Logic (BFS).
|
|
||||||
///
|
|
||||||
/// Reads and writes light directly through the light storage (a fast array
|
|
||||||
/// lookup) instead of maintaining a separate hashed shadow cache and batched
|
|
||||||
/// write buffer; the storage is the single source of truth.
|
|
||||||
pub fn propagate(&mut self, cache: &mut Cache) {
|
pub fn propagate(&mut self, cache: &mut Cache) {
|
||||||
// Cache metadata for bounds checking
|
|
||||||
let cache_x = cache.x;
|
let cache_x = cache.x;
|
||||||
let cache_z = cache.z;
|
let cache_z = cache.z;
|
||||||
let cache_size = cache.size;
|
let cache_size = cache.size;
|
||||||
let min_y = cache.bottom_y() as i32;
|
let min_y = cache.bottom_y() as i32;
|
||||||
let max_y = min_y + cache.height() as i32;
|
let max_y = min_y + cache.height() as i32;
|
||||||
|
|
||||||
|
let min_x = cache_x * 16;
|
||||||
|
let min_z = cache_z * 16;
|
||||||
|
let size_x = (cache_size * 16) as usize;
|
||||||
|
let size_z = (cache_size * 16) as usize;
|
||||||
|
let size_y = (max_y - min_y) as usize;
|
||||||
|
self.visited
|
||||||
|
.ensure_capacity(min_x, min_y, min_z, size_x, size_y, size_z);
|
||||||
|
|
||||||
while let Some(entry) = self.queue.pop_front() {
|
while let Some(entry) = self.queue.pop_front() {
|
||||||
let pos = entry.pos;
|
let pos = entry.pos;
|
||||||
|
|
||||||
@ -106,7 +279,6 @@ impl<P: LightProvider> LightPropagator<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for dir in BlockDirection::all() {
|
for dir in BlockDirection::all() {
|
||||||
// Skip the direction we came from (if specified)
|
|
||||||
if let Some(skip_dir) = entry.skip_direction
|
if let Some(skip_dir) = entry.skip_direction
|
||||||
&& dir == skip_dir
|
&& dir == skip_dir
|
||||||
{
|
{
|
||||||
@ -114,36 +286,78 @@ impl<P: LightProvider> LightPropagator<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let neighbor_pos = pos.offset(dir.to_offset());
|
let neighbor_pos = pos.offset(dir.to_offset());
|
||||||
|
let nx = neighbor_pos.0.x;
|
||||||
|
let ny = neighbor_pos.0.y;
|
||||||
|
let nz = neighbor_pos.0.z;
|
||||||
|
|
||||||
// Skip if already visited (critical early-exit optimization)
|
if self.visited.is_visited(nx, ny, nz) {
|
||||||
if self.visited.contains(&neighbor_pos) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip neighbor if it's outside world bounds
|
if ny < min_y || ny >= max_y {
|
||||||
if neighbor_pos.0.y < min_y || neighbor_pos.0.y >= max_y {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (cx, _rel) = neighbor_pos.chunk_and_chunk_relative_position();
|
let rel_x = (nx >> 4) - cache_x;
|
||||||
let rel_x = cx.x - cache_x;
|
let rel_z = (nz >> 4) - cache_z;
|
||||||
let rel_z = cx.y - cache_z;
|
|
||||||
if rel_x < 0 || rel_x >= cache_size || rel_z < 0 || rel_z >= cache_size {
|
if rel_x < 0 || rel_x >= cache_size || rel_z < 0 || rel_z >= cache_size {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get block opacity
|
let chunk_idx = (rel_x * cache_size + rel_z) as usize;
|
||||||
let state = cache.get_block_state(&neighbor_pos.0);
|
let local_x = (nx & 15) as usize;
|
||||||
let opacity = state.to_state().opacity;
|
let local_z = (nz & 15) as usize;
|
||||||
|
|
||||||
|
let section_idx = ((ny - min_y) >> 4) as usize;
|
||||||
|
let local_y = (ny & 15) as usize;
|
||||||
|
|
||||||
|
let (opacity, neighbor_light) = match &cache.chunks[chunk_idx] {
|
||||||
|
Chunk::Proto(c) => {
|
||||||
|
let local_y_proto = ny - min_y;
|
||||||
|
let state_id =
|
||||||
|
c.get_block_state_raw(local_x as i32, local_y_proto, local_z as i32);
|
||||||
|
let op = if state_id == BlockStateId::AIR {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
state_id.to_state().opacity
|
||||||
|
};
|
||||||
|
let light = P::get_light_proto(c, section_idx, local_x, local_y, local_z);
|
||||||
|
(op, light)
|
||||||
|
}
|
||||||
|
Chunk::Level(lvl) => {
|
||||||
|
let state_id = lvl
|
||||||
|
.section
|
||||||
|
.get_block_absolute_y(local_x, ny, local_z)
|
||||||
|
.unwrap_or(BlockStateId::AIR);
|
||||||
|
let op = if state_id == BlockStateId::AIR {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
state_id.to_state().opacity
|
||||||
|
};
|
||||||
|
(op, P::get_light(cache, neighbor_pos))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let new_level = P::propagate_level(current_light, opacity, dir);
|
let new_level = P::propagate_level(current_light, opacity, dir);
|
||||||
let neighbor_light = P::get_light(cache, neighbor_pos);
|
|
||||||
|
|
||||||
if new_level > neighbor_light {
|
if new_level > neighbor_light {
|
||||||
P::set_light(cache, neighbor_pos, new_level);
|
match &mut cache.chunks[chunk_idx] {
|
||||||
|
Chunk::Proto(c) => {
|
||||||
|
P::set_light_proto(
|
||||||
|
c,
|
||||||
|
section_idx,
|
||||||
|
local_x,
|
||||||
|
local_y,
|
||||||
|
local_z,
|
||||||
|
new_level,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Chunk::Level(_) => {
|
||||||
|
P::set_light(cache, neighbor_pos, new_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add to propagation queue if bright enough
|
if new_level > 1 && self.visited.test_and_set(nx, ny, nz) {
|
||||||
if new_level > 1 && self.visited.insert(neighbor_pos) {
|
|
||||||
self.queue.push_back(PropagationEntry {
|
self.queue.push_back(PropagationEntry {
|
||||||
pos: neighbor_pos,
|
pos: neighbor_pos,
|
||||||
skip_direction: Some(dir.opposite()),
|
skip_direction: Some(dir.opposite()),
|
||||||
@ -154,55 +368,51 @@ impl<P: LightProvider> LightPropagator<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle light removal
|
|
||||||
pub fn process_decrease_queue(&mut self, cache: &mut Cache) {
|
pub fn process_decrease_queue(&mut self, cache: &mut Cache) {
|
||||||
{
|
let cache_x = cache.x;
|
||||||
// Cache metadata for bounds checking
|
let cache_z = cache.z;
|
||||||
let cache_x = cache.x;
|
let cache_size = cache.size;
|
||||||
let cache_z = cache.z;
|
|
||||||
let cache_size = cache.size;
|
|
||||||
|
|
||||||
while let Some((pos, old_val)) = self.decrease_queue.pop_front() {
|
while let Some((pos, old_val)) = self.decrease_queue.pop_front() {
|
||||||
for dir in BlockDirection::all() {
|
for dir in BlockDirection::all() {
|
||||||
let neighbor_pos = pos.offset(dir.to_offset());
|
let neighbor_pos = pos.offset(dir.to_offset());
|
||||||
|
|
||||||
// Bounds check
|
let (cx, _rel) = neighbor_pos.chunk_and_chunk_relative_position();
|
||||||
let (cx, _rel) = neighbor_pos.chunk_and_chunk_relative_position();
|
let rel_x = cx.x - cache_x;
|
||||||
let rel_x = cx.x - cache_x;
|
let rel_z = cx.y - cache_z;
|
||||||
let rel_z = cx.y - cache_z;
|
|
||||||
|
|
||||||
if rel_x < 0 || rel_x >= cache_size || rel_z < 0 || rel_z >= cache_size {
|
if rel_x < 0 || rel_x >= cache_size || rel_z < 0 || rel_z >= cache_size {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let neighbor_light = P::get_light(cache, neighbor_pos);
|
let neighbor_light = P::get_light(cache, neighbor_pos);
|
||||||
if neighbor_light == 0 {
|
if neighbor_light == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = cache.get_block_state(&neighbor_pos.0);
|
let state = cache.get_block_state(&neighbor_pos.0);
|
||||||
let opacity = state.to_state().opacity;
|
let opacity = state.to_state().opacity;
|
||||||
|
|
||||||
let predicted = P::propagate_level(old_val, opacity, dir);
|
let predicted = P::propagate_level(old_val, opacity, dir);
|
||||||
|
|
||||||
if neighbor_light == predicted || neighbor_light < old_val {
|
if neighbor_light == predicted || neighbor_light < old_val {
|
||||||
// Darken
|
P::set_light(cache, neighbor_pos, 0);
|
||||||
P::set_light(cache, neighbor_pos, 0);
|
self.decrease_queue
|
||||||
self.decrease_queue
|
.push_back((neighbor_pos, neighbor_light));
|
||||||
.push_back((neighbor_pos, neighbor_light));
|
} else if neighbor_light >= old_val {
|
||||||
} else if neighbor_light >= old_val {
|
let nx = neighbor_pos.0.x;
|
||||||
// Re-illuminate from this bright neighbor
|
let ny = neighbor_pos.0.y;
|
||||||
self.queue.push_back(PropagationEntry {
|
let nz = neighbor_pos.0.z;
|
||||||
pos: neighbor_pos,
|
self.queue.push_back(PropagationEntry {
|
||||||
skip_direction: None,
|
pos: neighbor_pos,
|
||||||
});
|
skip_direction: None,
|
||||||
self.visited.insert(neighbor_pos);
|
});
|
||||||
}
|
self.visited.test_and_set(nx, ny, nz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.propagate(cache); // Re-propagate from survivors
|
self.propagate(cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,8 +429,6 @@ impl BlockLightPropagator {
|
|||||||
pub fn propagate_light(&mut self, cache: &mut Cache) {
|
pub fn propagate_light(&mut self, cache: &mut Cache) {
|
||||||
self.clear();
|
self.clear();
|
||||||
|
|
||||||
//let scan_start = Instant::now();
|
|
||||||
|
|
||||||
let min_y = cache.bottom_y() as i32;
|
let min_y = cache.bottom_y() as i32;
|
||||||
let max_y = min_y + cache.height() as i32;
|
let max_y = min_y + cache.height() as i32;
|
||||||
let center_x = cache.x + (cache.size / 2);
|
let center_x = cache.x + (cache.size / 2);
|
||||||
@ -231,33 +439,92 @@ impl BlockLightPropagator {
|
|||||||
let end_x = start_x + 18;
|
let end_x = start_x + 18;
|
||||||
let end_z = start_z + 18;
|
let end_z = start_z + 18;
|
||||||
|
|
||||||
for y in min_y..max_y {
|
let min_x = cache.x * 16;
|
||||||
for z in start_z..end_z {
|
let min_z = cache.z * 16;
|
||||||
for x in start_x..end_x {
|
let size_x = (cache.size * 16) as usize;
|
||||||
let pos_vec = Vector3::new(x, y, z);
|
let size_z = (cache.size * 16) as usize;
|
||||||
let state = cache.get_block_state(&pos_vec);
|
let size_y = (max_y - min_y) as usize;
|
||||||
let emission = state.to_state().luminance;
|
self.visited
|
||||||
if emission > 0 {
|
.ensure_capacity(min_x, min_y, min_z, size_x, size_y, size_z);
|
||||||
let pos = BlockPos(pos_vec);
|
|
||||||
set_block_light(cache, pos, emission);
|
for z in start_z..end_z {
|
||||||
if self.visited.insert(pos) {
|
let rel_z = (z >> 4) - cache.z;
|
||||||
// Block light propagates in all directions
|
let local_z = (z & 15) as usize;
|
||||||
self.queue.push_back(PropagationEntry {
|
|
||||||
pos,
|
for x in start_x..end_x {
|
||||||
skip_direction: None,
|
let rel_x = (x >> 4) - cache.x;
|
||||||
});
|
if rel_x < 0 || rel_x >= cache.size || rel_z < 0 || rel_z >= cache.size {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let chunk_idx = (rel_x * cache.size + rel_z) as usize;
|
||||||
|
let local_x = (x & 15) as usize;
|
||||||
|
|
||||||
|
match &mut cache.chunks[chunk_idx] {
|
||||||
|
Chunk::Proto(c) => {
|
||||||
|
for y in min_y..max_y {
|
||||||
|
let local_y_proto = y - min_y;
|
||||||
|
let state_id = c.get_block_state_raw(
|
||||||
|
local_x as i32,
|
||||||
|
local_y_proto,
|
||||||
|
local_z as i32,
|
||||||
|
);
|
||||||
|
if state_id == BlockStateId::AIR {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let emission = state_id.to_state().luminance;
|
||||||
|
if emission > 0 {
|
||||||
|
let section_idx = (local_y_proto >> 4) as usize;
|
||||||
|
let local_y = (y & 15) as usize;
|
||||||
|
if section_idx < c.light.block_light.len() {
|
||||||
|
c.light.block_light[section_idx]
|
||||||
|
.set(local_x, local_y, local_z, emission);
|
||||||
|
}
|
||||||
|
if self.visited.test_and_set(x, y, z) {
|
||||||
|
let pos = BlockPos(Vector3::new(x, y, z));
|
||||||
|
self.queue.push_back(PropagationEntry {
|
||||||
|
pos,
|
||||||
|
skip_direction: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chunk::Level(lvl) => {
|
||||||
|
let mut light_engine = lvl
|
||||||
|
.light_engine
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
for y in min_y..max_y {
|
||||||
|
let state_id = lvl
|
||||||
|
.section
|
||||||
|
.get_block_absolute_y(local_x, y, local_z)
|
||||||
|
.unwrap_or(BlockStateId::AIR);
|
||||||
|
if state_id == BlockStateId::AIR {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let emission = state_id.to_state().luminance;
|
||||||
|
if emission > 0 {
|
||||||
|
let section_idx = ((y - min_y) >> 4) as usize;
|
||||||
|
let local_y = (y & 15) as usize;
|
||||||
|
if section_idx < light_engine.block_light.len() {
|
||||||
|
light_engine.block_light[section_idx]
|
||||||
|
.set(local_x, local_y, local_z, emission);
|
||||||
|
}
|
||||||
|
if self.visited.test_and_set(x, y, z) {
|
||||||
|
let pos = BlockPos(Vector3::new(x, y, z));
|
||||||
|
self.queue.push_back(PropagationEntry {
|
||||||
|
pos,
|
||||||
|
skip_direction: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//let scan_elapsed = scan_start.elapsed();
|
|
||||||
//let propagate_start = Instant::now();
|
|
||||||
|
|
||||||
self.propagate(cache);
|
self.propagate(cache);
|
||||||
|
|
||||||
//let propagate_elapsed = propagate_start.elapsed();
|
|
||||||
//log::info!("Block light timing - Scan: {:?}, Propagate: {:?}", scan_elapsed, propagate_elapsed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +533,6 @@ impl SkyLightPropagator {
|
|||||||
pub fn convert_light(&mut self, cache: &mut Cache) {
|
pub fn convert_light(&mut self, cache: &mut Cache) {
|
||||||
self.clear();
|
self.clear();
|
||||||
|
|
||||||
//let scan_start = Instant::now();
|
|
||||||
|
|
||||||
let center_x = cache.x + (cache.size / 2);
|
let center_x = cache.x + (cache.size / 2);
|
||||||
let center_z = cache.z + (cache.size / 2);
|
let center_z = cache.z + (cache.size / 2);
|
||||||
let start_x = center_x * 16 - 1;
|
let start_x = center_x * 16 - 1;
|
||||||
@ -278,25 +543,29 @@ impl SkyLightPropagator {
|
|||||||
let bottom_y = cache.bottom_y() as i32;
|
let bottom_y = cache.bottom_y() as i32;
|
||||||
let max_y = bottom_y + cache.height() as i32;
|
let max_y = bottom_y + cache.height() as i32;
|
||||||
|
|
||||||
// Pre-allocate with exact size needed
|
let min_x = cache.x * 16;
|
||||||
let capacity = ((end_x - start_x) * (end_z - start_z)) as usize;
|
let min_z = cache.z * 16;
|
||||||
let mut surface_heights =
|
let size_x = (cache.size * 16) as usize;
|
||||||
FastHashMap::with_capacity_and_hasher(capacity, rustc_hash::FxBuildHasher);
|
let size_z = (cache.size * 16) as usize;
|
||||||
|
let size_y = (max_y - bottom_y) as usize;
|
||||||
|
self.visited
|
||||||
|
.ensure_capacity(min_x, bottom_y, min_z, size_x, size_y, size_z);
|
||||||
|
|
||||||
|
let mut surface_heights = [0i32; 18 * 18];
|
||||||
|
|
||||||
// Process in Z-outer, X-inner order for better cache locality
|
|
||||||
for z in start_z..end_z {
|
for z in start_z..end_z {
|
||||||
let chunk_z = z >> 4;
|
let chunk_z = z >> 4;
|
||||||
let local_z = (z & 15) as usize;
|
let local_z = (z & 15) as usize;
|
||||||
|
let lz = (z - start_z) as usize;
|
||||||
|
|
||||||
for x in start_x..end_x {
|
for x in start_x..end_x {
|
||||||
let chunk_x = x >> 4;
|
let chunk_x = x >> 4;
|
||||||
let local_x = (x & 15) as usize;
|
let local_x = (x & 15) as usize;
|
||||||
|
let lx = (x - start_x) as usize;
|
||||||
|
|
||||||
// Get heightmap (top solid blocks)
|
|
||||||
let top_y = cache.get_top_y(&HeightMap::WorldSurface, x, z);
|
let top_y = cache.get_top_y(&HeightMap::WorldSurface, x, z);
|
||||||
surface_heights.insert((x, z), top_y);
|
surface_heights[lx * 18 + lz] = top_y;
|
||||||
|
|
||||||
// Get chunk index once per column
|
|
||||||
let rel_x = chunk_x - cache.x;
|
let rel_x = chunk_x - cache.x;
|
||||||
let rel_z = chunk_z - cache.z;
|
let rel_z = chunk_z - cache.z;
|
||||||
|
|
||||||
@ -306,89 +575,123 @@ impl SkyLightPropagator {
|
|||||||
|
|
||||||
let chunk_idx = (rel_x * cache.size + rel_z) as usize;
|
let chunk_idx = (rel_x * cache.size + rel_z) as usize;
|
||||||
|
|
||||||
// Fill everything above heightmap with 15 immediately
|
match &mut cache.chunks[chunk_idx] {
|
||||||
for y in (top_y + 1)..max_y {
|
Chunk::Proto(c) => {
|
||||||
let section_idx = ((y - bottom_y) >> 4) as usize;
|
let top_local_y = (top_y + 1 - bottom_y).max(0) as usize;
|
||||||
let local_y = (y & 15) as usize;
|
let top_sec = top_local_y >> 4;
|
||||||
|
let top_rem = top_local_y & 15;
|
||||||
// Direct array access - skip all function call overhead
|
if top_sec < c.light.sky_light.len() {
|
||||||
match &mut cache.chunks[chunk_idx] {
|
c.light.sky_light[top_sec]
|
||||||
Chunk::Proto(c) => {
|
.set_column_y_range(local_x, local_z, top_rem, 16, 15);
|
||||||
if section_idx < c.light.sky_light.len() {
|
for sec in (top_sec + 1)..c.light.sky_light.len() {
|
||||||
c.light.sky_light[section_idx].set(local_x, local_y, local_z, 15);
|
c.light.sky_light[sec]
|
||||||
|
.set_column_y_range(local_x, local_z, 0, 16, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Chunk::Level(c) => {
|
|
||||||
let mut light_engine = c
|
let mut light: i32 = 15;
|
||||||
.light_engine
|
for y in (bottom_y..=top_y).rev() {
|
||||||
.lock()
|
let local_y_proto = y - bottom_y;
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
let state_id = c.get_block_state_raw(
|
||||||
|
local_x as i32,
|
||||||
|
local_y_proto,
|
||||||
|
local_z as i32,
|
||||||
|
);
|
||||||
|
let opacity = if state_id == BlockStateId::AIR {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
state_id.to_state().opacity as i32
|
||||||
|
};
|
||||||
|
|
||||||
|
light = light.saturating_sub(opacity);
|
||||||
|
let light_val = if light <= 0 { 0 } else { light as u8 };
|
||||||
|
let section_idx = (local_y_proto >> 4) as usize;
|
||||||
|
let local_y = (y & 15) as usize;
|
||||||
|
|
||||||
|
if section_idx < c.light.sky_light.len() {
|
||||||
|
c.light.sky_light[section_idx]
|
||||||
|
.set(local_x, local_y, local_z, light_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if light <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chunk::Level(c) => {
|
||||||
|
let mut light_engine = c
|
||||||
|
.light_engine
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
|
||||||
|
for y in (top_y + 1)..max_y {
|
||||||
|
let section_idx = ((y - bottom_y) >> 4) as usize;
|
||||||
|
let local_y = (y & 15) as usize;
|
||||||
if section_idx < light_engine.sky_light.len() {
|
if section_idx < light_engine.sky_light.len() {
|
||||||
light_engine.sky_light[section_idx]
|
light_engine.sky_light[section_idx]
|
||||||
.set(local_x, local_y, local_z, 15);
|
.set(local_x, local_y, local_z, 15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only iterate from top_y DOWN - not from max_y
|
let mut light: i32 = 15;
|
||||||
let mut light: i32 = 15;
|
for y in (bottom_y..=top_y).rev() {
|
||||||
|
let section_idx = ((y - bottom_y) >> 4) as usize;
|
||||||
|
let local_y = (y & 15) as usize;
|
||||||
|
|
||||||
for y in (bottom_y..=top_y).rev() {
|
let state_id = c
|
||||||
let section_idx = ((y - bottom_y) >> 4) as usize;
|
.section
|
||||||
let local_y = (y & 15) as usize;
|
.get_block_absolute_y(local_x, y, local_z)
|
||||||
|
.unwrap_or(BlockStateId::AIR);
|
||||||
|
let opacity = if state_id == BlockStateId::AIR {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
state_id.to_state().opacity as i32
|
||||||
|
};
|
||||||
|
|
||||||
// Get block opacity
|
light = light.saturating_sub(opacity);
|
||||||
let opacity = {
|
let light_val = if light <= 0 { 0 } else { light as u8 };
|
||||||
let pos_vec = Vector3::new(x, y, z);
|
|
||||||
let state = cache.get_block_state(&pos_vec);
|
|
||||||
state.to_state().opacity
|
|
||||||
} as i32;
|
|
||||||
|
|
||||||
// Reduce light by opacity
|
|
||||||
light = light.saturating_sub(opacity);
|
|
||||||
|
|
||||||
// Set the light value directly
|
|
||||||
let light_val = if light <= 0 { 0 } else { light as u8 };
|
|
||||||
|
|
||||||
match &mut cache.chunks[chunk_idx] {
|
|
||||||
Chunk::Proto(c) => {
|
|
||||||
if section_idx < c.light.sky_light.len() {
|
|
||||||
c.light.sky_light[section_idx]
|
|
||||||
.set(local_x, local_y, local_z, light_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Chunk::Level(c) => {
|
|
||||||
let mut light_engine = c
|
|
||||||
.light_engine
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
||||||
if section_idx < light_engine.sky_light.len() {
|
if section_idx < light_engine.sky_light.len() {
|
||||||
light_engine.sky_light[section_idx]
|
light_engine.sky_light[section_idx]
|
||||||
.set(local_x, local_y, local_z, light_val);
|
.set(local_x, local_y, local_z, light_val);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Early exit when light hits 0
|
if light <= 0 {
|
||||||
if light <= 0 {
|
break;
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue horizontal propagation
|
|
||||||
for z in start_z..end_z {
|
for z in start_z..end_z {
|
||||||
|
let lz = (z - start_z) as usize;
|
||||||
for x in start_x..end_x {
|
for x in start_x..end_x {
|
||||||
let top_y = surface_heights[&(x, z)];
|
let lx = (x - start_x) as usize;
|
||||||
|
let top_y = surface_heights[lx * 18 + lz];
|
||||||
|
|
||||||
let north_top = surface_heights.get(&(x, z - 1)).copied().unwrap_or(top_y);
|
let north_top = if lz > 0 {
|
||||||
let south_top = surface_heights.get(&(x, z + 1)).copied().unwrap_or(top_y);
|
surface_heights[lx * 18 + (lz - 1)]
|
||||||
let west_top = surface_heights.get(&(x - 1, z)).copied().unwrap_or(top_y);
|
} else {
|
||||||
let east_top = surface_heights.get(&(x + 1, z)).copied().unwrap_or(top_y);
|
top_y
|
||||||
|
};
|
||||||
|
let south_top = if lz + 1 < 18 {
|
||||||
|
surface_heights[lx * 18 + (lz + 1)]
|
||||||
|
} else {
|
||||||
|
top_y
|
||||||
|
};
|
||||||
|
let west_top = if lx > 0 {
|
||||||
|
surface_heights[(lx - 1) * 18 + lz]
|
||||||
|
} else {
|
||||||
|
top_y
|
||||||
|
};
|
||||||
|
let east_top = if lx + 1 < 18 {
|
||||||
|
surface_heights[(lx + 1) * 18 + lz]
|
||||||
|
} else {
|
||||||
|
top_y
|
||||||
|
};
|
||||||
|
|
||||||
// We must check up to the highest neighbor to catch the "air sources"
|
|
||||||
let max_check_y = top_y
|
let max_check_y = top_y
|
||||||
.max(north_top)
|
.max(north_top)
|
||||||
.max(south_top)
|
.max(south_top)
|
||||||
@ -399,7 +702,6 @@ impl SkyLightPropagator {
|
|||||||
let pos = BlockPos(Vector3::new(x, y, z));
|
let pos = BlockPos(Vector3::new(x, y, z));
|
||||||
let light = get_sky_light(cache, pos);
|
let light = get_sky_light(cache, pos);
|
||||||
|
|
||||||
// Use continue, or only break if we are safely below all possible side-light
|
|
||||||
if light == 0 {
|
if light == 0 {
|
||||||
if y <= top_y {
|
if y <= top_y {
|
||||||
break;
|
break;
|
||||||
@ -411,7 +713,7 @@ impl SkyLightPropagator {
|
|||||||
let below_neighbor =
|
let below_neighbor =
|
||||||
y < north_top || y < south_top || y < west_top || y < east_top;
|
y < north_top || y < south_top || y < west_top || y < east_top;
|
||||||
|
|
||||||
if (is_at_surface || below_neighbor) && self.visited.insert(pos) {
|
if (is_at_surface || below_neighbor) && self.visited.test_and_set(x, y, z) {
|
||||||
let skip_dir = (y >= top_y).then_some(BlockDirection::Up);
|
let skip_dir = (y >= top_y).then_some(BlockDirection::Up);
|
||||||
|
|
||||||
self.queue.push_back(PropagationEntry {
|
self.queue.push_back(PropagationEntry {
|
||||||
@ -423,13 +725,7 @@ impl SkyLightPropagator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//let propagate_start = Instant::now();
|
|
||||||
|
|
||||||
self.propagate(cache);
|
self.propagate(cache);
|
||||||
|
|
||||||
//let propagate_elapsed = propagate_start.elapsed();
|
|
||||||
//let scan_elapsed = scan_start.elapsed();
|
|
||||||
//log::info!("Sky light timing - Scan: {:?}, Propagate: {:?}", scan_elapsed, propagate_elapsed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +770,6 @@ impl LightEngine {
|
|||||||
old_luminance: u8,
|
old_luminance: u8,
|
||||||
new_luminance: u8,
|
new_luminance: u8,
|
||||||
) {
|
) {
|
||||||
// Decrease Logic
|
|
||||||
if old_luminance > new_luminance {
|
if old_luminance > new_luminance {
|
||||||
let current_light = get_block_light(cache, pos);
|
let current_light = get_block_light(cache, pos);
|
||||||
if current_light > 0 {
|
if current_light > 0 {
|
||||||
@ -485,10 +780,13 @@ impl LightEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increase Logic
|
|
||||||
if new_luminance > 0 {
|
if new_luminance > 0 {
|
||||||
set_block_light(cache, pos, new_luminance);
|
set_block_light(cache, pos, new_luminance);
|
||||||
if self.block_light.visited.insert(pos) {
|
if self
|
||||||
|
.block_light
|
||||||
|
.visited
|
||||||
|
.test_and_set(pos.0.x, pos.0.y, pos.0.z)
|
||||||
|
{
|
||||||
self.block_light.queue.push_back(PropagationEntry {
|
self.block_light.queue.push_back(PropagationEntry {
|
||||||
pos,
|
pos,
|
||||||
skip_direction: None,
|
skip_direction: None,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::chunk_system::generation_cache::Cache;
|
|||||||
use crate::generation::height_limit::HeightLimitView;
|
use crate::generation::height_limit::HeightLimitView;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
const fn get_chunk_index(cache: &Cache, chunk_x: i32, chunk_z: i32) -> Option<usize> {
|
const fn get_chunk_index(cache: &Cache, chunk_x: i32, chunk_z: i32) -> Option<usize> {
|
||||||
let rel_x = chunk_x - cache.x;
|
let rel_x = chunk_x - cache.x;
|
||||||
let rel_z = chunk_z - cache.z;
|
let rel_z = chunk_z - cache.z;
|
||||||
@ -12,6 +13,7 @@ const fn get_chunk_index(cache: &Cache, chunk_x: i32, chunk_z: i32) -> Option<us
|
|||||||
Some((rel_x * cache.size + rel_z) as usize)
|
Some((rel_x * cache.size + rel_z) as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> {
|
fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> {
|
||||||
let bottom = cache.bottom_y() as i32;
|
let bottom = cache.bottom_y() as i32;
|
||||||
if pos_y < bottom {
|
if pos_y < bottom {
|
||||||
@ -21,6 +23,7 @@ fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> {
|
|||||||
Some(section)
|
Some(section)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_block_light(cache: &Cache, pos: BlockPos) -> u8 {
|
pub fn get_block_light(cache: &Cache, pos: BlockPos) -> u8 {
|
||||||
let chunk_x = pos.0.x >> 4;
|
let chunk_x = pos.0.x >> 4;
|
||||||
@ -58,6 +61,7 @@ pub fn get_block_light(cache: &Cache, pos: BlockPos) -> u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
||||||
let chunk_x = pos.0.x >> 4;
|
let chunk_x = pos.0.x >> 4;
|
||||||
let chunk_z = pos.0.z >> 4;
|
let chunk_z = pos.0.z >> 4;
|
||||||
@ -99,6 +103,7 @@ pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 {
|
pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 {
|
||||||
let chunk_x = pos.0.x >> 4;
|
let chunk_x = pos.0.x >> 4;
|
||||||
@ -136,6 +141,7 @@ pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) {
|
||||||
let chunk_x = pos.0.x >> 4;
|
let chunk_x = pos.0.x >> 4;
|
||||||
let chunk_z = pos.0.z >> 4;
|
let chunk_z = pos.0.z >> 4;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
@ -70,16 +70,16 @@ pub struct PoiSectionData {
|
|||||||
pub struct PoiChunkData {
|
pub struct PoiChunkData {
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
/// Sections keyed by Y section coordinate (e.g., "-1", "0", "1", "4")
|
/// Sections keyed by Y section coordinate (e.g., "-1", "0", "1", "4")
|
||||||
pub sections: HashMap<String, PoiSectionData>,
|
pub sections: FxHashMap<String, PoiSectionData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// POI data for a single region (32x32 chunks) using MCA format
|
/// POI data for a single region (32x32 chunks) using MCA format
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PoiRegion {
|
pub struct PoiRegion {
|
||||||
/// Entries indexed by position
|
/// Entries indexed by position
|
||||||
entries: HashMap<(i32, i32, i32), PoiEntry>,
|
entries: FxHashMap<(i32, i32, i32), PoiEntry>,
|
||||||
/// Track which chunks are dirty
|
/// Track which chunks are dirty
|
||||||
dirty_chunks: std::collections::HashSet<(i32, i32)>,
|
dirty_chunks: rustc_hash::FxHashSet<(i32, i32)>,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ impl PoiRegion {
|
|||||||
|
|
||||||
/// Group entries by chunk, then create chunk NBT data
|
/// Group entries by chunk, then create chunk NBT data
|
||||||
fn get_chunk_data(&self, chunk_x: i32, chunk_z: i32) -> Option<PoiChunkData> {
|
fn get_chunk_data(&self, chunk_x: i32, chunk_z: i32) -> Option<PoiChunkData> {
|
||||||
let mut sections: HashMap<String, PoiSectionData> = HashMap::new();
|
let mut sections: FxHashMap<String, PoiSectionData> = FxHashMap::default();
|
||||||
|
|
||||||
for entry in self.entries.values() {
|
for entry in self.entries.values() {
|
||||||
let entry_chunk_x = entry.x >> 4;
|
let entry_chunk_x = entry.x >> 4;
|
||||||
@ -218,7 +218,7 @@ impl PoiRegion {
|
|||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||||
|
|
||||||
let data_version = nbt.get_int("DataVersion").unwrap_or(DATA_VERSION);
|
let data_version = nbt.get_int("DataVersion").unwrap_or(DATA_VERSION);
|
||||||
let mut sections = HashMap::new();
|
let mut sections = FxHashMap::default();
|
||||||
|
|
||||||
if let Some(sec_tag) = nbt.get_compound("Sections") {
|
if let Some(sec_tag) = nbt.get_compound("Sections") {
|
||||||
for (sec_key, tag) in &sec_tag.child_tags {
|
for (sec_key, tag) in &sec_tag.child_tags {
|
||||||
@ -269,11 +269,11 @@ impl PoiRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build all chunk data
|
// Build all chunk data
|
||||||
let mut chunk_data_map: HashMap<usize, Vec<u8>> = HashMap::new();
|
let mut chunk_data_map: FxHashMap<usize, Vec<u8>> = FxHashMap::default();
|
||||||
|
|
||||||
// Collect all unique chunks that have entries
|
// Collect all unique chunks that have entries
|
||||||
let mut chunks_with_data: std::collections::HashSet<(i32, i32)> =
|
let mut chunks_with_data: rustc_hash::FxHashSet<(i32, i32)> =
|
||||||
std::collections::HashSet::new();
|
rustc_hash::FxHashSet::default();
|
||||||
for entry in self.entries.values() {
|
for entry in self.entries.values() {
|
||||||
chunks_with_data.insert((entry.x >> 4, entry.z >> 4));
|
chunks_with_data.insert((entry.x >> 4, entry.z >> 4));
|
||||||
}
|
}
|
||||||
@ -429,7 +429,7 @@ pub struct PoiStorage {
|
|||||||
/// Path to the poi folder
|
/// Path to the poi folder
|
||||||
folder: PathBuf,
|
folder: PathBuf,
|
||||||
/// Loaded regions, keyed by (`region_x`, `region_z`)
|
/// Loaded regions, keyed by (`region_x`, `region_z`)
|
||||||
regions: HashMap<(i32, i32), PoiRegion>,
|
regions: FxHashMap<(i32, i32), PoiRegion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoiStorage {
|
impl PoiStorage {
|
||||||
@ -437,7 +437,7 @@ impl PoiStorage {
|
|||||||
pub fn new(poi_folder: PathBuf) -> Self {
|
pub fn new(poi_folder: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
folder: poi_folder,
|
folder: poi_folder,
|
||||||
regions: HashMap::new(),
|
regions: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@ wasmparser.workspace = true
|
|||||||
wasm-encoder = "0.257"
|
wasm-encoder = "0.257"
|
||||||
|
|
||||||
# authentication
|
# authentication
|
||||||
ureq = { workspace = true, features = ["json"] }
|
reqwest = { workspace = true, features = ["json"] }
|
||||||
|
|
||||||
sha1.workspace = true
|
sha1.workspace = true
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::block::{
|
use crate::block::{
|
||||||
BlockBehaviour, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs, CanUpdateAtArgs,
|
BlockBehaviour, BlockIsReplacing, BlockMetadata, CanPlaceAtArgs, CanUpdateAtArgs,
|
||||||
@ -154,7 +154,7 @@ fn get_attach_direction(
|
|||||||
replacing_block,
|
replacing_block,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
HashSet::new()
|
FxHashSet::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(player) = player_wrapper {
|
if let Some(player) = player_wrapper {
|
||||||
@ -183,8 +183,8 @@ const fn is_solid_face(block: &Block) -> bool {
|
|||||||
block.default_state.is_full_cube()
|
block.default_state.is_full_cube()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_directions(props: GlowLichenLikeProperties) -> HashSet<BlockDirection> {
|
fn active_directions(props: GlowLichenLikeProperties) -> FxHashSet<BlockDirection> {
|
||||||
let mut set = HashSet::new();
|
let mut set = FxHashSet::default();
|
||||||
if props.down {
|
if props.down {
|
||||||
set.insert(BlockDirection::Down);
|
set.insert(BlockDirection::Down);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::block_properties::{
|
|
||||||
BlockProperties, TrialSpawnerLikeProperties, TrialSpawnerState,
|
|
||||||
};
|
|
||||||
use pumpkin_data::item::Item;
|
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
|
||||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
|
||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
use pumpkin_world::world::BlockFlags;
|
|
||||||
|
|
||||||
use crate::block::entities::trial_spawner::TrialSpawnerBlockEntity;
|
use crate::block::entities::trial_spawner::TrialSpawnerBlockEntity;
|
||||||
use crate::block::registry::BlockActionResult;
|
use crate::block::{BlockBehaviour, PlacedArgs};
|
||||||
use crate::block::{BlockBehaviour, NormalUseArgs, PlacedArgs, UseWithItemArgs};
|
|
||||||
|
|
||||||
#[pumpkin_block("minecraft:trial_spawner")]
|
#[pumpkin_block("minecraft:trial_spawner")]
|
||||||
pub struct TrialSpawnerBlock;
|
pub struct TrialSpawnerBlock;
|
||||||
@ -21,95 +13,4 @@ impl BlockBehaviour for TrialSpawnerBlock {
|
|||||||
let entity = TrialSpawnerBlockEntity::new(*args.position);
|
let entity = TrialSpawnerBlockEntity::new(*args.position);
|
||||||
args.world.add_block_entity(Arc::new(entity));
|
args.world.add_block_entity(Arc::new(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
|
|
||||||
let state_id = args.world.get_block_state_id(args.position);
|
|
||||||
let mut props = TrialSpawnerLikeProperties::from_state_id(state_id, args.block);
|
|
||||||
|
|
||||||
match props.trial_spawner_state {
|
|
||||||
TrialSpawnerState::Inactive | TrialSpawnerState::WaitingForPlayers => {
|
|
||||||
props.trial_spawner_state = TrialSpawnerState::Active;
|
|
||||||
args.world.set_block_state(
|
|
||||||
args.position,
|
|
||||||
props.to_state_id(args.block),
|
|
||||||
BlockFlags::NOTIFY_ALL,
|
|
||||||
);
|
|
||||||
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerDetectPlayer,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerOpenShutter,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TrialSpawnerState::Active => {
|
|
||||||
// Eject trial rewards & key drop upon trial wave completion
|
|
||||||
props.trial_spawner_state = TrialSpawnerState::WaitingForRewardEjection;
|
|
||||||
args.world.set_block_state(
|
|
||||||
args.position,
|
|
||||||
props.to_state_id(args.block),
|
|
||||||
BlockFlags::NOTIFY_ALL,
|
|
||||||
);
|
|
||||||
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerSpawnItemBegin,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerEjectItem,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let key_stack = ItemStack::new(1, &Item::TRIAL_KEY);
|
|
||||||
args.world.drop_stack(args.position, key_stack);
|
|
||||||
|
|
||||||
props.trial_spawner_state = TrialSpawnerState::Cooldown;
|
|
||||||
args.world.set_block_state(
|
|
||||||
args.position,
|
|
||||||
props.to_state_id(args.block),
|
|
||||||
BlockFlags::NOTIFY_ALL,
|
|
||||||
);
|
|
||||||
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerCloseShutter,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TrialSpawnerState::Cooldown => {
|
|
||||||
args.world.play_sound(
|
|
||||||
Sound::BlockTrialSpawnerAmbient,
|
|
||||||
SoundCategory::Blocks,
|
|
||||||
&args.position.to_f64(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TrialSpawnerState::WaitingForRewardEjection | TrialSpawnerState::EjectingReward => {
|
|
||||||
props.trial_spawner_state = TrialSpawnerState::Cooldown;
|
|
||||||
args.world.set_block_state(
|
|
||||||
args.position,
|
|
||||||
props.to_state_id(args.block),
|
|
||||||
BlockFlags::NOTIFY_ALL,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockActionResult::Success
|
|
||||||
}
|
|
||||||
|
|
||||||
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
|
|
||||||
self.normal_use(NormalUseArgs {
|
|
||||||
server: args.server,
|
|
||||||
world: args.world,
|
|
||||||
block: args.block,
|
|
||||||
position: args.position,
|
|
||||||
player: args.player,
|
|
||||||
hit: args.hit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,7 @@ use std::sync::{
|
|||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use pumpkin_data::{entity::EntityType, world::WorldEvent};
|
use pumpkin_data::{entity::EntityType, world::WorldEvent};
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::math::{
|
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
|
||||||
boundingbox::{BoundingBox, EntityDimensions},
|
|
||||||
position::BlockPos,
|
|
||||||
vector3::Vector3,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{block::entities::BlockEntity, entity::EntityBase, world::World};
|
use crate::{block::entities::BlockEntity, entity::EntityBase, world::World};
|
||||||
|
|
||||||
@ -161,16 +157,10 @@ impl BlockEntity for MobSpawnerBlockEntity {
|
|||||||
+ (rand::random::<f64>() - rand::random::<f64>()) * spawn_range as f64
|
+ (rand::random::<f64>() - rand::random::<f64>()) * spawn_range as f64
|
||||||
+ 0.5,
|
+ 0.5,
|
||||||
);
|
);
|
||||||
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
|
if !world.is_space_empty(entity_type.get_spawn_bounding_box(
|
||||||
if !world.is_space_empty(BoundingBox::new_from_pos(
|
|
||||||
spawn_pos.x,
|
spawn_pos.x,
|
||||||
spawn_pos.y,
|
spawn_pos.y,
|
||||||
spawn_pos.z,
|
spawn_pos.z,
|
||||||
&EntityDimensions {
|
|
||||||
width: entity_type.dimension[0],
|
|
||||||
height: entity_type.dimension[1],
|
|
||||||
eye_height: entity_type.eye_height,
|
|
||||||
},
|
|
||||||
)) {
|
)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
use super::BlockEntity;
|
use super::BlockEntity;
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use std::collections::HashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ pub struct VaultBlockEntity {
|
|||||||
pub position: BlockPos,
|
pub position: BlockPos,
|
||||||
pub config: Mutex<Option<NbtCompound>>,
|
pub config: Mutex<Option<NbtCompound>>,
|
||||||
pub server_data: Mutex<Option<NbtCompound>>,
|
pub server_data: Mutex<Option<NbtCompound>>,
|
||||||
pub rewarded_players: Mutex<HashSet<Uuid>>,
|
pub rewarded_players: Mutex<FxHashSet<Uuid>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockEntity for VaultBlockEntity {
|
impl BlockEntity for VaultBlockEntity {
|
||||||
@ -29,7 +29,7 @@ impl BlockEntity for VaultBlockEntity {
|
|||||||
position,
|
position,
|
||||||
config: Mutex::new(nbt.get_compound("config").cloned()),
|
config: Mutex::new(nbt.get_compound("config").cloned()),
|
||||||
server_data: Mutex::new(nbt.get_compound("server_data").cloned()),
|
server_data: Mutex::new(nbt.get_compound("server_data").cloned()),
|
||||||
rewarded_players: Mutex::new(HashSet::new()),
|
rewarded_players: Mutex::new(FxHashSet::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ impl VaultBlockEntity {
|
|||||||
position,
|
position,
|
||||||
config: Mutex::new(None),
|
config: Mutex::new(None),
|
||||||
server_data: Mutex::new(None),
|
server_data: Mutex::new(None),
|
||||||
rewarded_players: Mutex::new(HashSet::new()),
|
rewarded_players: Mutex::new(FxHashSet::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ use crate::{
|
|||||||
dispatcher::CommandError,
|
dispatcher::CommandError,
|
||||||
tree::{RawArg, RawArgs},
|
tree::{RawArg, RawArgs},
|
||||||
},
|
},
|
||||||
net::authentication::lookup_profile_by_name,
|
net::authentication::lookup_profile_by_name_blocking,
|
||||||
net::{GameProfile, offline_uuid},
|
net::{GameProfile, offline_uuid},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
@ -254,7 +254,7 @@ fn resolve_profiles_from_token(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if server.advanced_config.networking.java.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
match lookup_profile_by_name(
|
match lookup_profile_by_name_blocking(
|
||||||
raw_arg.value,
|
raw_arg.value,
|
||||||
&server.advanced_config.networking.java.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
|||||||
use crate::command::errors::error_types::CommandErrorType;
|
use crate::command::errors::error_types::CommandErrorType;
|
||||||
use crate::command::string_reader::StringReader;
|
use crate::command::string_reader::StringReader;
|
||||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||||
use crate::net::authentication::lookup_profile_by_name;
|
use crate::net::authentication::lookup_profile_by_name_blocking;
|
||||||
use crate::net::{GameProfile, offline_uuid};
|
use crate::net::{GameProfile, offline_uuid};
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
@ -72,7 +72,7 @@ impl GameProfileResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if server.advanced_config.networking.java.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
return match lookup_profile_by_name(
|
return match lookup_profile_by_name_blocking(
|
||||||
name,
|
name,
|
||||||
&server.advanced_config.networking.java.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -142,7 +142,7 @@ fn report_resolved_profile(
|
|||||||
source.send_feedback(msg, false);
|
source.send_feedback(msg, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfile> {
|
async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfile> {
|
||||||
if let Some(player) = server.get_player_by_name(name) {
|
if let Some(player) = server.get_player_by_name(name) {
|
||||||
return Some(player.gameprofile.clone());
|
return Some(player.gameprofile.clone());
|
||||||
}
|
}
|
||||||
@ -155,7 +155,10 @@ fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfi
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let mojang_res = lookup_profile_by_name(name, &auth_config).ok().flatten();
|
let mojang_res = lookup_profile_by_name(name, &auth_config)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
if let Some((uuid, resolved_name)) = mojang_res {
|
if let Some((uuid, resolved_name)) = mojang_res {
|
||||||
server
|
server
|
||||||
@ -171,6 +174,7 @@ fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfi
|
|||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let full_profile = fetch_profile_by_uuid(uuid, &auth_config_clone)
|
let full_profile = fetch_profile_by_uuid(uuid, &auth_config_clone)
|
||||||
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
@ -212,7 +216,7 @@ fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfi
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GameProfile> {
|
async fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GameProfile> {
|
||||||
if let Some(player) = server.get_player_by_uuid(id) {
|
if let Some(player) = server.get_player_by_uuid(id) {
|
||||||
return Some(player.gameprofile.clone());
|
return Some(player.gameprofile.clone());
|
||||||
}
|
}
|
||||||
@ -223,7 +227,7 @@ fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GameProfile>
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let mojang_res = fetch_profile_by_uuid(id, &auth_config).ok().flatten();
|
let mojang_res = fetch_profile_by_uuid(id, &auth_config).await.ok().flatten();
|
||||||
|
|
||||||
if let Some(profile) = mojang_res {
|
if let Some(profile) = mojang_res {
|
||||||
server
|
server
|
||||||
@ -258,8 +262,8 @@ impl CommandExecutor for ResolveNameExecutor {
|
|||||||
let name_owned = name.to_string();
|
let name_owned = name.to_string();
|
||||||
|
|
||||||
let name_component = TextComponent::text(name_owned.clone());
|
let name_component = TextComponent::text(name_owned.clone());
|
||||||
rayon::spawn(move || {
|
tokio::spawn(async move {
|
||||||
let result = fetch_profile_by_name_helper(&server, &name_owned);
|
let result = fetch_profile_by_name_helper(&server, &name_owned).await;
|
||||||
match result {
|
match result {
|
||||||
Some(profile) => {
|
Some(profile) => {
|
||||||
report_resolved_profile(
|
report_resolved_profile(
|
||||||
@ -292,8 +296,8 @@ impl CommandExecutor for ResolveIdExecutor {
|
|||||||
let source = context.source.clone();
|
let source = context.source.clone();
|
||||||
|
|
||||||
let id_component = TextComponent::text(id.to_string());
|
let id_component = TextComponent::text(id.to_string());
|
||||||
rayon::spawn(move || {
|
tokio::spawn(async move {
|
||||||
let result = fetch_profile_by_id_helper(&server, id);
|
let result = fetch_profile_by_id_helper(&server, id).await;
|
||||||
match result {
|
match result {
|
||||||
Some(profile) => {
|
Some(profile) => {
|
||||||
report_resolved_profile(
|
report_resolved_profile(
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use pumpkin_util::PermissionLvl;
|
|||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
const DESCRIPTION: &str = "Changes biomes of an area.";
|
const DESCRIPTION: &str = "Changes biomes of an area.";
|
||||||
const PERMISSION: &str = "minecraft:command.fillbiome";
|
const PERMISSION: &str = "minecraft:command.fillbiome";
|
||||||
@ -72,8 +72,8 @@ impl CommandExecutor for FillBiomeExecutor {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut chunk_modifications: HashMap<Vector2<i32>, Vec<(usize, usize, usize)>> =
|
let mut chunk_modifications: FxHashMap<Vector2<i32>, Vec<(usize, usize, usize)>> =
|
||||||
HashMap::new();
|
FxHashMap::default();
|
||||||
for y in biome_min_y..=biome_max_y {
|
for y in biome_min_y..=biome_max_y {
|
||||||
for z in biome_min_z..=biome_max_z {
|
for z in biome_min_z..=biome_max_z {
|
||||||
for x in biome_min_x..=biome_max_x {
|
for x in biome_min_x..=biome_max_x {
|
||||||
|
|||||||
@ -51,7 +51,14 @@ fn fetch_all_contributors_cached() -> Vec<Contributor> {
|
|||||||
return cache.data.clone();
|
return cache.data.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let contributors = fetch_all_contributors();
|
let contributors = tokio::runtime::Handle::try_current().map_or_else(
|
||||||
|
|_| {
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.map_or_else(|_| Vec::new(), |rt| rt.block_on(fetch_all_contributors()))
|
||||||
|
},
|
||||||
|
|handle| tokio::task::block_in_place(|| handle.block_on(fetch_all_contributors())),
|
||||||
|
);
|
||||||
|
|
||||||
if !contributors.is_empty() {
|
if !contributors.is_empty() {
|
||||||
if let Ok(mut guard) = CONTRIBUTORS_CACHE.lock() {
|
if let Ok(mut guard) = CONTRIBUTORS_CACHE.lock() {
|
||||||
*guard = Some(ContributorCache {
|
*guard = Some(ContributorCache {
|
||||||
@ -68,25 +75,35 @@ fn fetch_all_contributors_cached() -> Vec<Contributor> {
|
|||||||
contributors
|
contributors
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_all_contributors() -> Vec<Contributor> {
|
async fn fetch_all_contributors() -> Vec<Contributor> {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.user_agent("Pumpkin-MC")
|
||||||
|
.build()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut all_contributors = Vec::new();
|
let mut all_contributors = Vec::new();
|
||||||
let mut next_url = Some(
|
let mut next_url = Some(
|
||||||
"https://api.github.com/repos/Pumpkin-MC/Pumpkin/contributors?per_page=100".to_string(),
|
"https://api.github.com/repos/Pumpkin-MC/Pumpkin/contributors?per_page=100".to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
while let Some(url) = next_url {
|
while let Some(url) = next_url {
|
||||||
let response = ureq::get(&url).header("User-Agent", "Pumpkin-MC").call();
|
let response = client.get(&url).send().await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(mut res) => {
|
Ok(res) => {
|
||||||
if let Ok(contributors) = res.body_mut().read_json::<Vec<Contributor>>() {
|
let link_header = res
|
||||||
|
.headers()
|
||||||
|
.get("link")
|
||||||
|
.and_then(|s| s.to_str().ok())
|
||||||
|
.map(String::from);
|
||||||
|
|
||||||
|
if let Ok(contributors) = res.json::<Vec<Contributor>>().await {
|
||||||
all_contributors.extend(contributors);
|
all_contributors.extend(contributors);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let link_header = res.headers().get("link").map(|s| s.to_str().unwrap_or(""));
|
|
||||||
|
|
||||||
next_url = link_header.and_then(extract_next_url);
|
next_url = link_header.as_deref().and_then(extract_next_url);
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
@ -172,14 +189,18 @@ fn tier_color(tier_slug: &str, tier_name: &str) -> NamedColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_donators_hover() -> TextComponent {
|
async fn fetch_donators_hover() -> TextComponent {
|
||||||
let url = "https://market.pumpkinmc.org/api/v1/rest/donators";
|
let url = "https://market.pumpkinmc.org/api/v1/rest/donators";
|
||||||
let response = ureq::get(url).header("User-Agent", "Pumpkin-MC").call();
|
let client = reqwest::Client::builder()
|
||||||
|
.user_agent("Pumpkin-MC")
|
||||||
|
.build()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let response = client.get(url).send().await;
|
||||||
|
|
||||||
let mut donators_text = TextComponent::text("Click to open Donate\n\nDonators:\n");
|
let mut donators_text = TextComponent::text("Click to open Donate\n\nDonators:\n");
|
||||||
|
|
||||||
if let Ok(mut res) = response
|
if let Ok(res) = response
|
||||||
&& let Ok(data) = res.body_mut().read_json::<DonatorResponse>()
|
&& let Ok(data) = res.json::<DonatorResponse>().await
|
||||||
{
|
{
|
||||||
let mut current = data.current;
|
let mut current = data.current;
|
||||||
current.sort_by(|a, b| {
|
current.sort_by(|a, b| {
|
||||||
@ -225,7 +246,16 @@ fn fetch_donators_hover_cached() -> TextComponent {
|
|||||||
return cache.data.clone();
|
return cache.data.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let donators = fetch_donators_hover();
|
let donators = tokio::runtime::Handle::try_current().map_or_else(
|
||||||
|
|_| {
|
||||||
|
tokio::runtime::Runtime::new().map_or_else(
|
||||||
|
|_| TextComponent::text("Unable to load donators"),
|
||||||
|
|rt| rt.block_on(fetch_donators_hover()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|handle| tokio::task::block_in_place(|| handle.block_on(fetch_donators_hover())),
|
||||||
|
);
|
||||||
|
|
||||||
if let Ok(mut guard) = DONATORS_CACHE.lock() {
|
if let Ok(mut guard) = DONATORS_CACHE.lock() {
|
||||||
*guard = Some(DonatorCache {
|
*guard = Some(DonatorCache {
|
||||||
fetched_at: Instant::now(),
|
fetched_at: Instant::now(),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -29,8 +29,8 @@ struct UserCacheEntryDisk {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UserCache {
|
pub struct UserCache {
|
||||||
profiles_by_name: HashMap<String, UserCacheEntry>,
|
profiles_by_name: FxHashMap<String, UserCacheEntry>,
|
||||||
profiles_by_uuid: HashMap<Uuid, UserCacheEntry>,
|
profiles_by_uuid: FxHashMap<Uuid, UserCacheEntry>,
|
||||||
operation_count: u64,
|
operation_count: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use pumpkin_data::attributes::Attributes;
|
use pumpkin_data::attributes::Attributes;
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
@ -193,10 +193,10 @@ impl Clone for AttributeInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Registry storing per-entity-type base attribute overrides.
|
/// Registry storing per-entity-type base attribute overrides.
|
||||||
/// Internally stores a map from `entity_type.id` -> `HashMap`<attribute.id, f64> for O(1) lookup.
|
/// Internally stores a map from `entity_type.id` -> `FxHashMap`<attribute.id, f64> for O(1) lookup.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AttributeRegistry {
|
pub struct AttributeRegistry {
|
||||||
map: HashMap<u16, HashMap<u8, f64>>,
|
map: FxHashMap<u16, FxHashMap<u8, f64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttributeRegistry {
|
impl AttributeRegistry {
|
||||||
@ -278,7 +278,7 @@ mod tests {
|
|||||||
.find(|(attr, _)| attr.id == Attributes::MOVEMENT_SPEED.id);
|
.find(|(attr, _)| attr.id == Attributes::MOVEMENT_SPEED.id);
|
||||||
assert!(speed_attr.is_some());
|
assert!(speed_attr.is_some());
|
||||||
let (_, base_speed) = speed_attr.unwrap();
|
let (_, base_speed) = speed_attr.unwrap();
|
||||||
assert!((base_speed - 0.1).abs() < f64::EPSILON);
|
assert!((base_speed - 0.1).abs() < 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use pumpkin_data::Block;
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_data::world::WorldEvent;
|
use pumpkin_data::world::WorldEvent;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use pumpkin_world::world::BlockFlags;
|
use pumpkin_world::world::BlockFlags;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::entity::effect::MobEffect;
|
use crate::entity::effect::MobEffect;
|
||||||
use crate::entity::living::LivingEntity;
|
use crate::entity::living::LivingEntity;
|
||||||
@ -25,7 +24,7 @@ impl MobEffect for WeavingMobEffect {
|
|||||||
let center_pos = living.entity.block_pos.load();
|
let center_pos = living.entity.block_pos.load();
|
||||||
let cobweb_count = (rand::random::<u32>() % 2 + 2) as usize; // 2 to 3 cobwebs
|
let cobweb_count = (rand::random::<u32>() % 2 + 2) as usize; // 2 to 3 cobwebs
|
||||||
|
|
||||||
let mut positions_to_transform = HashSet::new();
|
let mut positions_to_transform = FxHashSet::default();
|
||||||
|
|
||||||
// Sample up to 15 random positions in a cube of radius 1
|
// Sample up to 15 random positions in a cube of radius 1
|
||||||
for _ in 0..15 {
|
for _ in 0..15 {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering},
|
atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering},
|
||||||
@ -27,7 +27,7 @@ pub struct LightningBoltEntity {
|
|||||||
flashes: AtomicI32,
|
flashes: AtomicI32,
|
||||||
visual_only: AtomicBool,
|
visual_only: AtomicBool,
|
||||||
cause: Mutex<Option<Arc<Player>>>,
|
cause: Mutex<Option<Arc<Player>>>,
|
||||||
hit_entities: Mutex<HashSet<i32>>,
|
hit_entities: Mutex<FxHashSet<i32>>,
|
||||||
blocks_set_on_fire: AtomicI32,
|
blocks_set_on_fire: AtomicI32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ impl LightningBoltEntity {
|
|||||||
flashes: AtomicI32::new(flashes),
|
flashes: AtomicI32::new(flashes),
|
||||||
visual_only: AtomicBool::new(false),
|
visual_only: AtomicBool::new(false),
|
||||||
cause: Mutex::new(None),
|
cause: Mutex::new(None),
|
||||||
hit_entities: Mutex::new(HashSet::new()),
|
hit_entities: Mutex::new(FxHashSet::default()),
|
||||||
blocks_set_on_fire: AtomicI32::new(0),
|
blocks_set_on_fire: AtomicI32::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,13 +12,14 @@ use pumpkin_protocol::codec::var_ulong::VarULong;
|
|||||||
use pumpkin_util::GameMode;
|
use pumpkin_util::GameMode;
|
||||||
use pumpkin_util::Hand;
|
use pumpkin_util::Hand;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicI32;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::atomic::{
|
use std::sync::atomic::{
|
||||||
AtomicBool, AtomicU8,
|
AtomicBool, AtomicU8,
|
||||||
Ordering::{Relaxed, SeqCst},
|
Ordering::{Relaxed, SeqCst},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, sync::atomic::AtomicI32};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use super::experience_orb::ExperienceOrbEntity;
|
use super::experience_orb::ExperienceOrbEntity;
|
||||||
@ -59,7 +60,7 @@ use pumpkin_protocol::java::client::play::{
|
|||||||
};
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
codec::item_stack_seralizer::ItemStackSerializer,
|
codec::item_stack_seralizer::ItemStackSerializer,
|
||||||
java::client::play::{CDamageEvent, CSetEquipment, Metadata, MetadataSerializer},
|
java::client::play::{CSetEquipment, Metadata, MetadataSerializer},
|
||||||
ser::{NetworkWriteExt, WritingError},
|
ser::{NetworkWriteExt, WritingError},
|
||||||
};
|
};
|
||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||||
@ -90,11 +91,11 @@ pub struct LivingEntity {
|
|||||||
pub dead: AtomicBool,
|
pub dead: AtomicBool,
|
||||||
/// The distance the entity has been falling.
|
/// The distance the entity has been falling.
|
||||||
pub fall_distance: AtomicCell<f32>,
|
pub fall_distance: AtomicCell<f32>,
|
||||||
pub active_effects: std::sync::Mutex<HashMap<&'static StatusEffect, Effect>>,
|
pub active_effects: std::sync::Mutex<FxHashMap<&'static StatusEffect, Effect>>,
|
||||||
pub entity_equipment: Arc<std::sync::Mutex<EntityEquipment>>,
|
pub entity_equipment: Arc<std::sync::Mutex<EntityEquipment>>,
|
||||||
pub equipment_drop_chances: Arc<std::sync::Mutex<HashMap<EquipmentSlot, f32>>>,
|
pub equipment_drop_chances: Arc<std::sync::Mutex<FxHashMap<EquipmentSlot, f32>>>,
|
||||||
pub movement_input: AtomicCell<Vector3<f64>>,
|
pub movement_input: AtomicCell<Vector3<f64>>,
|
||||||
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
pub equipment_slots: Arc<FxHashMap<usize, EquipmentSlot>>,
|
||||||
|
|
||||||
pub jumping: AtomicBool,
|
pub jumping: AtomicBool,
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ pub struct LivingEntity {
|
|||||||
livings_flags: AtomicU8,
|
livings_flags: AtomicU8,
|
||||||
|
|
||||||
/// The attributes of the entity
|
/// The attributes of the entity
|
||||||
pub attributes: RwLock<HashMap<u8, AttributeInstance>>,
|
pub attributes: RwLock<FxHashMap<u8, AttributeInstance>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EffectParticle {
|
struct EffectParticle {
|
||||||
@ -186,7 +187,7 @@ impl LivingEntity {
|
|||||||
Self {
|
Self {
|
||||||
// Populate local attribute instances from the default registry and get initial vars
|
// Populate local attribute instances from the default registry and get initial vars
|
||||||
attributes: {
|
attributes: {
|
||||||
let mut m = std::collections::HashMap::new();
|
let mut m = FxHashMap::default();
|
||||||
|
|
||||||
for (attr, base) in entity.entity_type.attributes {
|
for (attr, base) in entity.entity_type.attributes {
|
||||||
if attr.id == Attributes::MAX_HEALTH.id {
|
if attr.id == Attributes::MAX_HEALTH.id {
|
||||||
@ -208,9 +209,9 @@ impl LivingEntity {
|
|||||||
item_in_use: std::sync::Mutex::new(None),
|
item_in_use: std::sync::Mutex::new(None),
|
||||||
active_hand: std::sync::Mutex::new(None),
|
active_hand: std::sync::Mutex::new(None),
|
||||||
livings_flags: AtomicU8::new(0),
|
livings_flags: AtomicU8::new(0),
|
||||||
active_effects: std::sync::Mutex::new(HashMap::new()),
|
active_effects: std::sync::Mutex::new(FxHashMap::default()),
|
||||||
entity_equipment: Arc::new(std::sync::Mutex::new(EntityEquipment::new())),
|
entity_equipment: Arc::new(std::sync::Mutex::new(EntityEquipment::new())),
|
||||||
equipment_drop_chances: Arc::new(std::sync::Mutex::new(HashMap::new())),
|
equipment_drop_chances: Arc::new(std::sync::Mutex::new(FxHashMap::default())),
|
||||||
equipment_slots: Arc::new(build_equipment_slots()),
|
equipment_slots: Arc::new(build_equipment_slots()),
|
||||||
jumping: AtomicBool::new(false),
|
jumping: AtomicBool::new(false),
|
||||||
jumping_cooldown: AtomicU8::new(0),
|
jumping_cooldown: AtomicU8::new(0),
|
||||||
@ -258,8 +259,8 @@ impl LivingEntity {
|
|||||||
selected_slot: 0,
|
selected_slot: 0,
|
||||||
container_id: window_id,
|
container_id: window_id,
|
||||||
};
|
};
|
||||||
self.entity.world.load().broadcast_packet_except_editioned(
|
self.entity.world.load().send_to_tracking_players_editioned(
|
||||||
&[self.entity.entity_uuid],
|
&self.entity,
|
||||||
&je_packet,
|
&je_packet,
|
||||||
&be_packet,
|
&be_packet,
|
||||||
);
|
);
|
||||||
@ -271,7 +272,7 @@ impl LivingEntity {
|
|||||||
self.entity
|
self.entity
|
||||||
.world
|
.world
|
||||||
.load()
|
.load()
|
||||||
.broadcast_packet_except(&[self.entity.entity_uuid], &je_packet);
|
.send_to_tracking_players(&self.entity, &je_packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2485,20 +2486,21 @@ impl LivingEntity {
|
|||||||
data: VarInt(0),
|
data: VarInt(0),
|
||||||
fire_at_position: None,
|
fire_at_position: None,
|
||||||
};
|
};
|
||||||
world.broadcast_to_chunk(
|
let hurt_animation = CHurtAnimation::new(entity_id.into(), hurt_yaw);
|
||||||
self.entity.chunk_pos.load(),
|
world.send_to_tracking_players_and_self_editioned(
|
||||||
&CHurtAnimation::new(entity_id.into(), hurt_yaw),
|
&self.entity,
|
||||||
|
&hurt_animation,
|
||||||
|
&hurt_event,
|
||||||
);
|
);
|
||||||
world.broadcast_to_chunk_bedrock(self.entity.chunk_pos.load(), &hurt_event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
world.broadcast_packet_all(&CDamageEvent::new(
|
world.broadcast_damage_event(
|
||||||
self.entity.entity_id.into(),
|
&self.entity,
|
||||||
damage_type.id.into(),
|
i32::from(damage_type.id),
|
||||||
source.map(|e| e.get_entity().entity_id.into()),
|
source.map(|e| e.get_entity().entity_id),
|
||||||
cause.map(|e| e.get_entity().entity_id.into()),
|
cause.map(|e| e.get_entity().entity_id),
|
||||||
position,
|
position,
|
||||||
));
|
);
|
||||||
|
|
||||||
if play_sound {
|
if play_sound {
|
||||||
world.play_sound(
|
world.play_sound(
|
||||||
|
|||||||
@ -2989,20 +2989,33 @@ impl Entity {
|
|||||||
bedrock_meta: Option<&SyncedActorDataList>,
|
bedrock_meta: Option<&SyncedActorDataList>,
|
||||||
) {
|
) {
|
||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
let chunk_pos = self.chunk_pos.load();
|
|
||||||
let players = world.players.load();
|
let players = world.players.load();
|
||||||
|
|
||||||
let mut java_recipients = Vec::new();
|
let mut java_recipients = Vec::new();
|
||||||
let mut bedrock_recipients = Vec::new();
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
|
||||||
for player in players.iter() {
|
if let Some(tracked) = world.entity_tracker.get_tracked_entity(self.entity_id) {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
for player in players.iter() {
|
||||||
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
if tracked.seen_by.contains(&player.gameprofile.id)
|
||||||
|
|| player.entity_id() == self.entity_id
|
||||||
|
{
|
||||||
|
match player.client.as_ref() {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(player),
|
||||||
|
ClientPlatform::Bedrock(client) => bedrock_recipients.push(client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let chunk_pos = self.chunk_pos.load();
|
||||||
|
for player in players.iter() {
|
||||||
|
let center = player.get_entity().chunk_pos.load();
|
||||||
|
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
||||||
|
|
||||||
if is_within_view_distance(chunk_pos, center, view_distance) {
|
if is_within_view_distance(chunk_pos, center, view_distance) {
|
||||||
match player.client.as_ref() {
|
match player.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(player),
|
ClientPlatform::Java(_) => java_recipients.push(player),
|
||||||
ClientPlatform::Bedrock(client) => bedrock_recipients.push(client),
|
ClientPlatform::Bedrock(client) => bedrock_recipients.push(client),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -264,7 +264,7 @@ pub struct VillagerEntity {
|
|||||||
pub restocks_today: AtomicI32,
|
pub restocks_today: AtomicI32,
|
||||||
pub last_gossip_decay_time: AtomicI64,
|
pub last_gossip_decay_time: AtomicI64,
|
||||||
pub last_gossip_share_time: AtomicI64,
|
pub last_gossip_share_time: AtomicI64,
|
||||||
pub gossips: std::sync::Mutex<HashMap<Uuid, HashMap<GossipType, i32>>>,
|
pub gossips: std::sync::Mutex<FxHashMap<Uuid, FxHashMap<GossipType, i32>>>,
|
||||||
pub inventory: std::sync::Mutex<Vec<ItemStack>>,
|
pub inventory: std::sync::Mutex<Vec<ItemStack>>,
|
||||||
pub merchant_inventory: Arc<SimpleInventory>,
|
pub merchant_inventory: Arc<SimpleInventory>,
|
||||||
pub offers: std::sync::Mutex<Vec<pumpkin_protocol::java::client::play::MerchantOffer>>,
|
pub offers: std::sync::Mutex<Vec<pumpkin_protocol::java::client::play::MerchantOffer>>,
|
||||||
@ -333,7 +333,7 @@ impl VillagerEntity {
|
|||||||
restocks_today: AtomicI32::new(0),
|
restocks_today: AtomicI32::new(0),
|
||||||
last_gossip_decay_time: AtomicI64::new(0),
|
last_gossip_decay_time: AtomicI64::new(0),
|
||||||
last_gossip_share_time: AtomicI64::new(0),
|
last_gossip_share_time: AtomicI64::new(0),
|
||||||
gossips: std::sync::Mutex::new(HashMap::new()),
|
gossips: std::sync::Mutex::new(FxHashMap::default()),
|
||||||
inventory,
|
inventory,
|
||||||
merchant_inventory: Arc::new(SimpleInventory::new(3)),
|
merchant_inventory: Arc::new(SimpleInventory::new(3)),
|
||||||
offers: std::sync::Mutex::new(Vec::new()),
|
offers: std::sync::Mutex::new(Vec::new()),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ pub mod advancement;
|
|||||||
pub mod statistics;
|
pub mod statistics;
|
||||||
|
|
||||||
use core::f32;
|
use core::f32;
|
||||||
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -41,7 +41,7 @@ use pumpkin_protocol::bedrock::server::{
|
|||||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||||
use pumpkin_util::translation::Locale;
|
use pumpkin_util::translation::Locale;
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
use pumpkin_world::chunk::ChunkData;
|
||||||
use pumpkin_world::inventory::Inventory;
|
use pumpkin_world::inventory::Inventory;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
@ -284,7 +284,6 @@ use pumpkin_util::text::hover::HoverEvent;
|
|||||||
use pumpkin_util::{GameMode, Hand};
|
use pumpkin_util::{GameMode, Hand};
|
||||||
use pumpkin_world::biome;
|
use pumpkin_world::biome;
|
||||||
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
|
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
|
||||||
use pumpkin_world::level::{Level, SyncChunk, SyncEntityChunk};
|
|
||||||
|
|
||||||
use crate::block;
|
use crate::block;
|
||||||
use crate::block::blocks::bed::BedBlock;
|
use crate::block::blocks::bed::BedBlock;
|
||||||
@ -312,7 +311,6 @@ use super::item::ItemEntity;
|
|||||||
use super::living::LivingEntity;
|
use super::living::LivingEntity;
|
||||||
use super::{Entity, EntityBase, NBTStorage, NBTStorageInit};
|
use super::{Entity, EntityBase, NBTStorage, NBTStorageInit};
|
||||||
use pumpkin_data::potion::Effect;
|
use pumpkin_data::potion::Effect;
|
||||||
use pumpkin_world::chunk_system::ChunkLoading;
|
|
||||||
const MAX_CACHED_SIGNATURES: u8 = 128; // Vanilla: 128
|
const MAX_CACHED_SIGNATURES: u8 = 128; // Vanilla: 128
|
||||||
const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
|
const MAX_PREVIOUS_MESSAGES: u8 = 20; // Vanilla: 20
|
||||||
|
|
||||||
@ -373,324 +371,6 @@ const fn bedrock_inventory_slot(player_screen_slot: i16) -> Option<u32> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HeapNode(i32, Vector2<i32>, Weak<ChunkData>);
|
|
||||||
|
|
||||||
impl Eq for HeapNode {}
|
|
||||||
|
|
||||||
impl PartialEq<Self> for HeapNode {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0 == other.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<Self> for HeapNode {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for HeapNode {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.0.cmp(&other.0).reverse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChunkManager {
|
|
||||||
chunks_per_tick: usize,
|
|
||||||
center: Vector2<i32>,
|
|
||||||
view_distance: u8,
|
|
||||||
chunk_listener: Receiver<(Vector2<i32>, Weak<ChunkData>)>,
|
|
||||||
chunk_sent: HashMap<Vector2<i32>, Weak<ChunkData>>,
|
|
||||||
chunk_queue: BinaryHeap<HeapNode>,
|
|
||||||
entity_chunk_queue: VecDeque<(Vector2<i32>, Weak<ChunkEntityData>)>,
|
|
||||||
batches_sent_since_ack: u8,
|
|
||||||
last_chunk_batch_sent_at: Instant,
|
|
||||||
/// The (view, simulation) chunk loading ticket levels currently held at `center`.
|
|
||||||
held_tickets: Option<(i8, i8)>,
|
|
||||||
/// The current world for chunk loading. Updated on dimension change.
|
|
||||||
world: Arc<World>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChunkManager {
|
|
||||||
pub const NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE: u8 = 16;
|
|
||||||
const ACK_STALL_FALLBACK_DELAY: Duration = Duration::from_millis(100);
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(
|
|
||||||
chunks_per_tick: usize,
|
|
||||||
chunk_listener: Receiver<(Vector2<i32>, Weak<ChunkData>)>,
|
|
||||||
world: Arc<World>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
chunks_per_tick,
|
|
||||||
center: Vector2::<i32>::new(0, 0),
|
|
||||||
view_distance: 0,
|
|
||||||
chunk_listener,
|
|
||||||
chunk_sent: HashMap::new(),
|
|
||||||
chunk_queue: BinaryHeap::new(),
|
|
||||||
entity_chunk_queue: VecDeque::new(),
|
|
||||||
batches_sent_since_ack: 0,
|
|
||||||
last_chunk_batch_sent_at: Instant::now(),
|
|
||||||
held_tickets: None,
|
|
||||||
world,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current world for chunk loading.
|
|
||||||
#[must_use]
|
|
||||||
pub const fn world(&self) -> &Arc<World> {
|
|
||||||
&self.world
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn sent_chunks_count(&self) -> usize {
|
|
||||||
self.chunk_sent.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_sent_chunks(&mut self) {
|
|
||||||
self.chunk_sent.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn set_view_distance(&mut self, view_distance: u8) {
|
|
||||||
self.view_distance = view_distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean(&mut self) {
|
|
||||||
let (_rx, tx) = crossbeam::channel::unbounded();
|
|
||||||
// drop old channel
|
|
||||||
self.chunk_listener = tx;
|
|
||||||
|
|
||||||
// Drop any held chunk references to allow chunks to be unloaded.
|
|
||||||
self.chunk_sent.clear();
|
|
||||||
self.chunk_queue.clear();
|
|
||||||
self.entity_chunk_queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_enqueue_chunk(&mut self, position: Vector2<i32>, chunk: &SyncChunk) -> bool {
|
|
||||||
self.chunk_sent
|
|
||||||
.insert(position, Arc::downgrade(chunk))
|
|
||||||
.and_then(|old_chunk| old_chunk.upgrade())
|
|
||||||
.is_none_or(|old_chunk| !Arc::ptr_eq(&old_chunk, chunk))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
const fn ack_window_open(&self) -> bool {
|
|
||||||
self.batches_sent_since_ack < Self::NOTCHIAN_BATCHES_WITHOUT_ACK_UNTIL_PAUSE
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn ack_fallback_ready(&self) -> bool {
|
|
||||||
!self.ack_window_open()
|
|
||||||
&& self.last_chunk_batch_sent_at.elapsed() >= Self::ACK_STALL_FALLBACK_DELAY
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pull_new_chunks(&mut self) {
|
|
||||||
while let Ok((pos, chunk_weak)) = self.chunk_listener.try_recv() {
|
|
||||||
let dst = Self::chebyshev(pos, self.center);
|
|
||||||
if dst > i32::from(self.view_distance) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(chunk) = chunk_weak.upgrade()
|
|
||||||
&& self.should_enqueue_chunk(pos, &chunk)
|
|
||||||
{
|
|
||||||
self.chunk_queue.push(HeapNode(dst, pos, chunk_weak));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chebyshev(a: Vector2<i32>, b: Vector2<i32>) -> i32 {
|
|
||||||
(a.x - b.x).abs().max((a.y - b.y).abs())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_center_and_view_distance(
|
|
||||||
&mut self,
|
|
||||||
center: Vector2<i32>,
|
|
||||||
mut view_distance: u8,
|
|
||||||
level: &Arc<Level>,
|
|
||||||
loading_chunks: &[Vector2<i32>],
|
|
||||||
unloading_chunks: &[Vector2<i32>],
|
|
||||||
) {
|
|
||||||
view_distance += 1; // Margin for loading
|
|
||||||
let old_center = self.center;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut lock = level
|
|
||||||
.chunk_loading
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
||||||
let new_level = ChunkLoading::get_level_from_view_distance(view_distance);
|
|
||||||
lock.add_ticket(center, new_level);
|
|
||||||
|
|
||||||
let sim_dist = self.world.server.upgrade().map_or(10, |s| {
|
|
||||||
s.advanced_config.networking.java.simulation_distance.get()
|
|
||||||
});
|
|
||||||
let sim_level = ChunkLoading::get_level_from_simulation_distance(sim_dist);
|
|
||||||
lock.add_ticket(center, sim_level);
|
|
||||||
|
|
||||||
// Drop exactly the pair we last added rather than recomputing it. The
|
|
||||||
// simulation level is derived from live config and the view level from the
|
|
||||||
// previous view distance, so recomputing them can release a ticket we never
|
|
||||||
// took and strand the one we did.
|
|
||||||
if let Some((held_view, held_sim)) = self.held_tickets.replace((new_level, sim_level)) {
|
|
||||||
lock.remove_ticket(old_center, held_view);
|
|
||||||
lock.remove_ticket(old_center, held_sim);
|
|
||||||
}
|
|
||||||
lock.send_change();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.center = center;
|
|
||||||
self.view_distance = view_distance;
|
|
||||||
let view_distance_i32 = i32::from(view_distance);
|
|
||||||
let unloading_chunks: HashSet<Vector2<i32>> = unloading_chunks.iter().copied().collect();
|
|
||||||
|
|
||||||
self.chunk_sent.retain(|pos, _| {
|
|
||||||
(pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance_i32
|
|
||||||
&& !unloading_chunks.contains(pos)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.entity_chunk_queue.retain(|(pos, _)| {
|
|
||||||
(pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance_i32
|
|
||||||
&& !unloading_chunks.contains(pos)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut tasks: Vec<_> = self
|
|
||||||
.chunk_queue
|
|
||||||
.drain()
|
|
||||||
.filter_map(|node| {
|
|
||||||
let dst = Self::chebyshev(node.1, center);
|
|
||||||
(dst <= view_distance_i32 && !unloading_chunks.contains(&node.1))
|
|
||||||
.then(|| HeapNode(dst, node.1, node.2))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for pos in loading_chunks {
|
|
||||||
if !self.chunk_sent.contains_key(pos)
|
|
||||||
&& let Some(chunk) = level.loaded_chunks.get(pos)
|
|
||||||
{
|
|
||||||
let chunk = chunk.value().clone();
|
|
||||||
if self.should_enqueue_chunk(*pos, &chunk) {
|
|
||||||
let dst = (pos.x - center.x).abs().max((pos.y - center.y).abs());
|
|
||||||
tasks.push(HeapNode(dst, *pos, Arc::downgrade(&chunk)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.chunk_queue = BinaryHeap::from(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean_up(&mut self, level: &Arc<Level>) {
|
|
||||||
let mut lock = level
|
|
||||||
.chunk_loading
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
||||||
if let Some((view_level, sim_level)) = self.held_tickets.take() {
|
|
||||||
lock.remove_ticket(self.center, view_level);
|
|
||||||
lock.remove_ticket(self.center, sim_level);
|
|
||||||
}
|
|
||||||
lock.send_change();
|
|
||||||
level.should_unload.store(true, Ordering::Relaxed);
|
|
||||||
level.level_channel.notify();
|
|
||||||
let (_rx, tx) = crossbeam::channel::unbounded();
|
|
||||||
// drop old channel
|
|
||||||
self.chunk_listener = tx;
|
|
||||||
|
|
||||||
// Drop any held chunk references to allow chunks to be unloaded.
|
|
||||||
self.chunk_sent.clear();
|
|
||||||
self.chunk_queue.clear();
|
|
||||||
self.entity_chunk_queue.clear();
|
|
||||||
self.batches_sent_since_ack = 0;
|
|
||||||
self.last_chunk_batch_sent_at = Instant::now();
|
|
||||||
self.view_distance = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_world(&mut self, old_level: &Arc<Level>, new_world: Arc<World>) {
|
|
||||||
let mut lock = old_level
|
|
||||||
.chunk_loading
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
||||||
if let Some((view_level, sim_level)) = self.held_tickets.take() {
|
|
||||||
lock.remove_ticket(self.center, view_level);
|
|
||||||
lock.remove_ticket(self.center, sim_level);
|
|
||||||
}
|
|
||||||
lock.send_change();
|
|
||||||
old_level.should_unload.store(true, Ordering::Relaxed);
|
|
||||||
old_level.level_channel.notify();
|
|
||||||
drop(lock);
|
|
||||||
self.chunk_listener = new_world.level.chunk_listener.add_global_chunk_listener();
|
|
||||||
self.chunk_sent.clear();
|
|
||||||
self.chunk_queue.clear();
|
|
||||||
self.entity_chunk_queue.clear();
|
|
||||||
self.view_distance = 0;
|
|
||||||
self.world = new_world;
|
|
||||||
// Reset batch state so chunks can be sent immediately in the new dimension
|
|
||||||
self.batches_sent_since_ack = 0;
|
|
||||||
self.last_chunk_batch_sent_at = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn handle_acknowledge(&mut self, chunks_per_tick: f32) {
|
|
||||||
self.batches_sent_since_ack = 0;
|
|
||||||
self.chunks_per_tick = chunks_per_tick.ceil() as usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_chunk(&mut self, position: Vector2<i32>, chunk: &SyncChunk) {
|
|
||||||
if self.should_enqueue_chunk(position, chunk) {
|
|
||||||
let dst = (position.x - self.center.x)
|
|
||||||
.abs()
|
|
||||||
.max((position.y - self.center.y).abs());
|
|
||||||
self.chunk_queue
|
|
||||||
.push(HeapNode(dst, position, Arc::downgrade(chunk)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_entity(&mut self, position: Vector2<i32>, chunk: &SyncEntityChunk) {
|
|
||||||
self.entity_chunk_queue
|
|
||||||
.push_back((position, Arc::downgrade(chunk)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn can_send_chunk(&self) -> bool {
|
|
||||||
let state_available = self.ack_window_open() || self.ack_fallback_ready();
|
|
||||||
|
|
||||||
state_available && !self.chunk_queue.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_chunk(&mut self) -> Box<[SyncChunk]> {
|
|
||||||
let take = self.chunk_queue.len().min(self.chunks_per_tick.max(1));
|
|
||||||
let mut chunks = Vec::with_capacity(take);
|
|
||||||
while chunks.len() < take
|
|
||||||
&& let Some(node) = self.chunk_queue.pop()
|
|
||||||
{
|
|
||||||
if let Some(chunk) = node.2.upgrade() {
|
|
||||||
chunks.push(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.batches_sent_since_ack = self.batches_sent_since_ack.saturating_add(1);
|
|
||||||
self.last_chunk_batch_sent_at = Instant::now();
|
|
||||||
|
|
||||||
chunks.into_boxed_slice()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_entity(&mut self) -> Box<[SyncEntityChunk]> {
|
|
||||||
let chunk_size = self
|
|
||||||
.entity_chunk_queue
|
|
||||||
.len()
|
|
||||||
.min(self.chunks_per_tick.max(1));
|
|
||||||
|
|
||||||
let mut chunks = Vec::with_capacity(chunk_size);
|
|
||||||
while chunks.len() < chunk_size
|
|
||||||
&& let Some((_, weak_chunk)) = self.entity_chunk_queue.pop_front()
|
|
||||||
{
|
|
||||||
if let Some(chunk) = weak_chunk.upgrade() {
|
|
||||||
chunks.push(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.batches_sent_since_ack = self.batches_sent_since_ack.saturating_add(1);
|
|
||||||
self.last_chunk_batch_sent_at = Instant::now();
|
|
||||||
|
|
||||||
chunks.into_boxed_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a Minecraft player entity.
|
/// Represents a Minecraft player entity.
|
||||||
///
|
///
|
||||||
/// A `Player` is a special type of entity that represents a human player connected to the server.
|
/// A `Player` is a special type of entity that represents a human player connected to the server.
|
||||||
@ -798,7 +478,10 @@ pub struct Player {
|
|||||||
pub experience_points: AtomicI32,
|
pub experience_points: AtomicI32,
|
||||||
pub item_cooldowns: std::sync::Mutex<HashMap<String, ItemCooldown>>,
|
pub item_cooldowns: std::sync::Mutex<HashMap<String, ItemCooldown>>,
|
||||||
pub experience_pick_up_delay: Mutex<u32>,
|
pub experience_pick_up_delay: Mutex<u32>,
|
||||||
pub chunk_manager: Mutex<ChunkManager>,
|
pub chunk_sender: Mutex<crate::net::ChunkSender>,
|
||||||
|
pub chunk_listener: Mutex<Receiver<(Vector2<i32>, Weak<ChunkData>)>>,
|
||||||
|
pub held_chunk_tickets: Mutex<Option<(i8, i8)>>,
|
||||||
|
pub chunk_send_epoch: AtomicU32,
|
||||||
pub has_played_before: AtomicBool,
|
pub has_played_before: AtomicBool,
|
||||||
root_vehicle_uuid: AtomicCell<Option<Uuid>>,
|
root_vehicle_uuid: AtomicCell<Option<Uuid>>,
|
||||||
pub chat_session: Arc<Mutex<ChatSession>>,
|
pub chat_session: Arc<Mutex<ChatSession>>,
|
||||||
@ -836,7 +519,6 @@ pub struct Player {
|
|||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use pumpkin_protocol::Property;
|
use pumpkin_protocol::Property;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
// Bit masks for the Java skin pixels that Bedrock requires to be opaque.
|
// Bit masks for the Java skin pixels that Bedrock requires to be opaque.
|
||||||
// Adapted from Geyser's SkinProvider under the MIT License.
|
// Adapted from Geyser's SkinProvider under the MIT License.
|
||||||
@ -883,10 +565,20 @@ impl Player {
|
|||||||
.and_then(|m| m.model.as_deref())
|
.and_then(|m| m.model.as_deref())
|
||||||
.is_some_and(|model| model == "slim");
|
.is_some_and(|model| model == "slim");
|
||||||
|
|
||||||
let resp = ureq::get(&url).call().ok()?;
|
let bytes = if let Ok(handle) = tokio::runtime::Handle::try_current() {
|
||||||
let mut buf = Vec::new();
|
tokio::task::block_in_place(|| {
|
||||||
resp.into_body().into_reader().read_to_end(&mut buf).ok()?;
|
handle.block_on(async {
|
||||||
let img = image::load_from_memory(&buf).ok()?;
|
let client = reqwest::Client::new();
|
||||||
|
client.get(&url).send().await.ok()?.bytes().await.ok()
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
tokio::runtime::Runtime::new().ok()?.block_on(async {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
client.get(&url).send().await.ok()?.bytes().await.ok()
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
let img = image::load_from_memory(&bytes).ok()?;
|
||||||
|
|
||||||
let width = img.width();
|
let width = img.width();
|
||||||
let height = img.height();
|
let height = img.height();
|
||||||
@ -1081,12 +773,10 @@ impl Player {
|
|||||||
experience_progress: AtomicCell::new(0.0),
|
experience_progress: AtomicCell::new(0.0),
|
||||||
experience_points: AtomicI32::new(0),
|
experience_points: AtomicI32::new(0),
|
||||||
item_cooldowns: std::sync::Mutex::new(HashMap::new()),
|
item_cooldowns: std::sync::Mutex::new(HashMap::new()),
|
||||||
// Default to sending 16 chunks per tick.
|
chunk_sender: Mutex::new(crate::net::ChunkSender::new()),
|
||||||
chunk_manager: Mutex::new(ChunkManager::new(
|
chunk_listener: Mutex::new(world.level.chunk_listener.add_global_chunk_listener()),
|
||||||
16,
|
held_chunk_tickets: Mutex::new(None),
|
||||||
world.level.chunk_listener.add_global_chunk_listener(),
|
chunk_send_epoch: AtomicU32::new(0),
|
||||||
world.clone(),
|
|
||||||
)),
|
|
||||||
last_sent_xp: AtomicI32::new(-1),
|
last_sent_xp: AtomicI32::new(-1),
|
||||||
last_sent_health: AtomicI32::new(-1),
|
last_sent_health: AtomicI32::new(-1),
|
||||||
last_sent_food: AtomicU8::new(0),
|
last_sent_food: AtomicU8::new(0),
|
||||||
@ -1341,10 +1031,10 @@ impl Player {
|
|||||||
world.remove_player(self, true).await;
|
world.remove_player(self, true).await;
|
||||||
|
|
||||||
let cylindrical = self.watched_section.load();
|
let cylindrical = self.watched_section.load();
|
||||||
self.chunk_manager
|
self.clean_up_chunk_tickets(&world.level);
|
||||||
.lock()
|
if let Ok(mut sender) = self.chunk_sender.lock() {
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
sender.reset();
|
||||||
.clean_up(&world.level);
|
}
|
||||||
|
|
||||||
// Radial chunks are all of the chunks the player is theoretically viewing.
|
// Radial chunks are all of the chunks the player is theoretically viewing.
|
||||||
// Given enough time, all of these chunks will be in memory.
|
// Given enough time, all of these chunks will be in memory.
|
||||||
@ -1396,6 +1086,41 @@ impl Player {
|
|||||||
.add_passenger(vehicle.clone(), self.clone());
|
.add_passenger(vehicle.clone(), self.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clean_up_chunk_tickets(&self, level: &Arc<pumpkin_world::level::Level>) {
|
||||||
|
let mut lock = level
|
||||||
|
.chunk_loading
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
let held = self
|
||||||
|
.held_chunk_tickets
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.take();
|
||||||
|
if let Some((view_level, sim_level)) = held {
|
||||||
|
let center = self.get_entity().chunk_pos.load();
|
||||||
|
lock.remove_ticket(center, view_level);
|
||||||
|
lock.remove_ticket(center, sim_level);
|
||||||
|
}
|
||||||
|
lock.send_change();
|
||||||
|
level.should_unload.store(true, Ordering::Relaxed);
|
||||||
|
level.level_channel.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_world_chunks(
|
||||||
|
&self,
|
||||||
|
old_level: &Arc<pumpkin_world::level::Level>,
|
||||||
|
new_world: &Arc<crate::world::World>,
|
||||||
|
) {
|
||||||
|
self.clean_up_chunk_tickets(old_level);
|
||||||
|
if let Ok(mut listener) = self.chunk_listener.lock() {
|
||||||
|
*listener = new_world.level.chunk_listener.add_global_chunk_listener();
|
||||||
|
}
|
||||||
|
self.chunk_send_epoch.fetch_add(1, Ordering::Relaxed);
|
||||||
|
if let Ok(mut sender) = self.chunk_sender.lock() {
|
||||||
|
sender.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub fn attack(&self, victim: &Arc<dyn EntityBase>) {
|
pub fn attack(&self, victim: &Arc<dyn EntityBase>) {
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
@ -2602,48 +2327,72 @@ impl Player {
|
|||||||
{
|
{
|
||||||
*xp -= 1;
|
*xp -= 1;
|
||||||
}
|
}
|
||||||
let (chunk_of_chunks, total_sent_chunks) = self.chunk_manager.try_lock().map_or_else(
|
if let Ok(listener) = self.chunk_listener.try_lock()
|
||||||
|_| (None, 0),
|
&& let Ok(mut sender) = self.chunk_sender.try_lock()
|
||||||
|mut chunk_manager| {
|
|
||||||
chunk_manager.pull_new_chunks();
|
|
||||||
let chunks = if let ClientPlatform::Java(java_client) = self.client.as_ref() {
|
|
||||||
if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 {
|
|
||||||
// Java clients (1.20.2+) use the chunk batching protocol.
|
|
||||||
// If we have sent too many chunks without receiving an ack, we stop sending chunks.
|
|
||||||
chunk_manager
|
|
||||||
.can_send_chunk()
|
|
||||||
.then(|| chunk_manager.next_chunk())
|
|
||||||
} else {
|
|
||||||
// Java clients < 1.20.2 do not have chunk batching/ack packets.
|
|
||||||
// Send chunks every tick directly based on chunks_per_tick.
|
|
||||||
(!chunk_manager.chunk_queue.is_empty()).then(|| chunk_manager.next_chunk())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(!chunk_manager.chunk_queue.is_empty()).then(|| chunk_manager.next_chunk())
|
|
||||||
};
|
|
||||||
(chunks, chunk_manager.sent_chunks_count())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if let Some(chunk_of_chunks) = chunk_of_chunks
|
|
||||||
&& !chunk_of_chunks.is_empty()
|
|
||||||
{
|
{
|
||||||
let client = self.client.clone();
|
let center = self.get_entity().chunk_pos.load();
|
||||||
self.spawn_task(async move {
|
let view_dist =
|
||||||
client.send_chunks(&chunk_of_chunks).await;
|
std::num::NonZeroI32::from(self.watched_section.load().view_distance).get();
|
||||||
});
|
while let Ok((pos, _)) = listener.try_recv() {
|
||||||
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
if (pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_dist {
|
||||||
&& !self.bedrock_spawned.load(Ordering::Relaxed)
|
sender.enqueue_chunk(pos);
|
||||||
&& total_sent_chunks > 4
|
|
||||||
{
|
|
||||||
if let Ok(data) = bedrock_client.serialize_packet(&CPlayStatus::PlayerSpawn) {
|
|
||||||
bedrock_client.try_enqueue_packet(data);
|
|
||||||
}
|
}
|
||||||
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
}
|
||||||
self.set_client_loaded(true);
|
}
|
||||||
self.send_health();
|
|
||||||
if self.living_entity.health.load() <= 0.0 {
|
let world = self.world();
|
||||||
self.send_bedrock_respawn_state(RespawnState::SearchingForSpawn);
|
let player_chunk = self.get_entity().chunk_pos.load();
|
||||||
|
let epoch = self.chunk_send_epoch.load(Ordering::Relaxed);
|
||||||
|
let version = match self.client.as_ref() {
|
||||||
|
ClientPlatform::Java(java_client) => java_client.version.load(),
|
||||||
|
ClientPlatform::Bedrock(_) => JavaMinecraftVersion::V_1_20_2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let prepared_batch = self.chunk_sender.try_lock().ok().and_then(|mut sender| {
|
||||||
|
sender.prepare_batch(&world.level, player_chunk, epoch, version)
|
||||||
|
});
|
||||||
|
|
||||||
|
let total_sent_chunks = if let Some(batch) = prepared_batch {
|
||||||
|
match self.client.as_ref() {
|
||||||
|
ClientPlatform::Java(_) => {
|
||||||
|
let mut per_player_cache = rustc_hash::FxHashMap::default();
|
||||||
|
let encoded =
|
||||||
|
crate::net::ChunkSender::encode_batch(&batch, &mut per_player_cache);
|
||||||
|
let current_epoch = self.chunk_send_epoch.load(Ordering::Relaxed);
|
||||||
|
self.chunk_sender.try_lock().map_or(0, |mut sender| {
|
||||||
|
sender.commit_batch(&batch, &encoded, &self.client, current_epoch);
|
||||||
|
sender.sent_chunks_count()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
ClientPlatform::Bedrock(_) => {
|
||||||
|
let chunks: Vec<_> = batch.chunks.into_iter().map(|c| c.chunk).collect();
|
||||||
|
let client = self.client.clone();
|
||||||
|
self.spawn_task(async move {
|
||||||
|
client.send_chunks(&chunks).await;
|
||||||
|
});
|
||||||
|
self.chunk_sender
|
||||||
|
.try_lock()
|
||||||
|
.map_or(0, |s| s.sent_chunks_count())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.chunk_sender
|
||||||
|
.try_lock()
|
||||||
|
.map_or(0, |s| s.sent_chunks_count())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
||||||
|
&& !self.bedrock_spawned.load(Ordering::Relaxed)
|
||||||
|
&& total_sent_chunks > 4
|
||||||
|
{
|
||||||
|
if let Ok(data) = bedrock_client.serialize_packet(&CPlayStatus::PlayerSpawn) {
|
||||||
|
bedrock_client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
||||||
|
self.set_client_loaded(true);
|
||||||
|
self.send_health();
|
||||||
|
if self.living_entity.health.load() <= 0.0 {
|
||||||
|
self.send_bedrock_respawn_state(RespawnState::SearchingForSpawn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
@ -3656,10 +3405,7 @@ impl Player {
|
|||||||
});
|
});
|
||||||
self.unload_watched_chunks(¤t_world).await;
|
self.unload_watched_chunks(¤t_world).await;
|
||||||
|
|
||||||
self.chunk_manager
|
self.change_world_chunks(¤t_world.level, &new_world);
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.change_world(¤t_world.level, new_world.clone());
|
|
||||||
self.living_entity.entity.set_world(new_world.clone());
|
self.living_entity.entity.set_world(new_world.clone());
|
||||||
|
|
||||||
if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
if new_world.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
||||||
@ -3779,6 +3525,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let i = self.teleport_id_count.fetch_add(1, Ordering::Relaxed);
|
let i = self.teleport_id_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
self.chunk_send_epoch.fetch_add(1, Ordering::Relaxed);
|
||||||
let teleport_id = i + 1;
|
let teleport_id = i + 1;
|
||||||
self.living_entity.entity.set_pos(position);
|
self.living_entity.entity.set_pos(position);
|
||||||
let entity = &self.living_entity.entity;
|
let entity = &self.living_entity.entity;
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
pub use pumpkin_data::statistic::{CustomStatistic, StatisticCategory};
|
pub use pumpkin_data::statistic::{CustomStatistic, StatisticCategory};
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_nbt::tag::NbtTag;
|
use pumpkin_nbt::tag::NbtTag;
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Statistics {
|
pub struct Statistics {
|
||||||
/// (Category ID, Statistic ID) -> Value
|
/// (Category ID, Statistic ID) -> Value
|
||||||
pub stats: HashMap<(i32, i32), i32>,
|
pub stats: FxHashMap<(i32, i32), i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Statistics {
|
impl Statistics {
|
||||||
|
|||||||
@ -3,11 +3,11 @@ use std::{collections::HashMap, net::IpAddr};
|
|||||||
use base64::{Engine, engine::general_purpose};
|
use base64::{Engine, engine::general_purpose};
|
||||||
use pumpkin_config::{AuthenticationConfig, networking::auth::TextureConfig};
|
use pumpkin_config::{AuthenticationConfig, networking::auth::TextureConfig};
|
||||||
use pumpkin_protocol::Property;
|
use pumpkin_protocol::Property;
|
||||||
|
use reqwest::{StatusCode, Url};
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
use rsa::pkcs8::DecodePublicKey;
|
use rsa::pkcs8::DecodePublicKey;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use ureq::http::{StatusCode, Uri};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::GameProfile;
|
use super::GameProfile;
|
||||||
@ -54,16 +54,16 @@ const MOJANG_PROFILE_BY_NAME_URL: &str =
|
|||||||
const MOJANG_PROFILE_BY_UUID_URL: &str =
|
const MOJANG_PROFILE_BY_UUID_URL: &str =
|
||||||
"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
|
"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
|
||||||
|
|
||||||
fn create_agent(auth_config: &AuthenticationConfig) -> ureq::Agent {
|
fn create_client(auth_config: &AuthenticationConfig) -> reqwest::Client {
|
||||||
let config = ureq::Agent::config_builder()
|
reqwest::Client::builder()
|
||||||
.timeout_connect(Some(std::time::Duration::from_millis(
|
.connect_timeout(std::time::Duration::from_millis(
|
||||||
auth_config.connect_timeout as u64,
|
auth_config.connect_timeout as u64,
|
||||||
)))
|
))
|
||||||
.timeout_recv_response(Some(std::time::Duration::from_millis(
|
.timeout(std::time::Duration::from_millis(
|
||||||
auth_config.read_timeout as u64,
|
auth_config.read_timeout as u64,
|
||||||
)))
|
))
|
||||||
.build();
|
.build()
|
||||||
config.into()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_auth_url(url_template: &str, username: &str, server_hash: &str, ip: &IpAddr) -> String {
|
fn format_auth_url(url_template: &str, username: &str, server_hash: &str, ip: &IpAddr) -> String {
|
||||||
@ -87,7 +87,7 @@ fn format_auth_url(url_template: &str, username: &str, server_hash: &str, ip: &I
|
|||||||
/// 3. Pumpkin attempts to authenticate the player against the primary auth server, falling back to secondary auth servers if the primary is down.
|
/// 3. Pumpkin attempts to authenticate the player against the primary auth server, falling back to secondary auth servers if the primary is down.
|
||||||
///
|
///
|
||||||
/// See <https://pumpkinmc.org/developer/networking/authentication>
|
/// See <https://pumpkinmc.org/developer/networking/authentication>
|
||||||
pub fn authenticate(
|
pub async fn authenticate(
|
||||||
username: &str,
|
username: &str,
|
||||||
server_hash: &str,
|
server_hash: &str,
|
||||||
ip: &IpAddr,
|
ip: &IpAddr,
|
||||||
@ -111,7 +111,7 @@ pub fn authenticate(
|
|||||||
candidate_urls.push(fallback.as_str());
|
candidate_urls.push(fallback.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let agent = create_agent(auth_config);
|
let client = create_client(auth_config);
|
||||||
|
|
||||||
let mut unverified_count = 0;
|
let mut unverified_count = 0;
|
||||||
let mut last_unknown_status = None;
|
let mut last_unknown_status = None;
|
||||||
@ -119,7 +119,7 @@ pub fn authenticate(
|
|||||||
for url_template in candidate_urls {
|
for url_template in candidate_urls {
|
||||||
let address = format_auth_url(url_template, username, server_hash, ip);
|
let address = format_auth_url(url_template, username, server_hash, ip);
|
||||||
|
|
||||||
let mut response = match agent.get(&address).call() {
|
let response = match client.get(&address).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@ -136,7 +136,7 @@ pub fn authenticate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
StatusCode::OK => match response.body_mut().read_json::<GameProfile>() {
|
StatusCode::OK => match response.json::<GameProfile>().await {
|
||||||
Ok(profile) => return Ok(profile),
|
Ok(profile) => return Ok(profile),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
||||||
@ -167,41 +167,35 @@ pub fn validate_textures(property: &Property, config: &TextureConfig) -> Result<
|
|||||||
let textures: ProfileTextures =
|
let textures: ProfileTextures =
|
||||||
serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?;
|
serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?;
|
||||||
for texture in textures.textures {
|
for texture in textures.textures {
|
||||||
let url = texture
|
let url = Url::parse(&texture.1.url).map_err(|_| TextureError::InvalidURL)?;
|
||||||
.1
|
|
||||||
.url
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| TextureError::InvalidURL)?;
|
|
||||||
is_texture_url_valid(&url, config)?;
|
is_texture_url_valid(&url, config)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_texture_url_valid(url: &Uri, config: &TextureConfig) -> Result<(), TextureError> {
|
pub fn is_texture_url_valid(url: &Url, config: &TextureConfig) -> Result<(), TextureError> {
|
||||||
let Some(scheme) = url.scheme() else {
|
let scheme = url.scheme();
|
||||||
return Err(TextureError::InvalidURL);
|
|
||||||
};
|
|
||||||
if !config
|
if !config
|
||||||
.allowed_url_schemes
|
.allowed_url_schemes
|
||||||
.iter()
|
.iter()
|
||||||
.any(|allowed_scheme| scheme.as_str().ends_with(allowed_scheme))
|
.any(|allowed_scheme| scheme.ends_with(allowed_scheme))
|
||||||
{
|
{
|
||||||
return Err(TextureError::DisallowedUrlScheme(scheme.to_string()));
|
return Err(TextureError::DisallowedUrlScheme(scheme.to_string()));
|
||||||
}
|
}
|
||||||
let Some(domain) = url.authority() else {
|
let Some(domain) = url.domain() else {
|
||||||
return Err(TextureError::InvalidURL);
|
return Err(TextureError::InvalidURL);
|
||||||
};
|
};
|
||||||
if !config
|
if !config
|
||||||
.allowed_url_domains
|
.allowed_url_domains
|
||||||
.iter()
|
.iter()
|
||||||
.any(|allowed_domain| domain.as_str().ends_with(allowed_domain))
|
.any(|allowed_domain| domain.ends_with(allowed_domain))
|
||||||
{
|
{
|
||||||
return Err(TextureError::DisallowedUrlDomain(domain.to_string()));
|
return Err(TextureError::DisallowedUrlDomain(domain.to_string()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_mojang_public_keys(
|
pub async fn fetch_mojang_public_keys(
|
||||||
auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<Vec<RsaPublicKey>, AuthError> {
|
) -> Result<Vec<RsaPublicKey>, AuthError> {
|
||||||
let services_url = auth_config
|
let services_url = auth_config
|
||||||
@ -211,10 +205,11 @@ pub fn fetch_mojang_public_keys(
|
|||||||
|
|
||||||
let url = format!("{services_url}/publickeys");
|
let url = format!("{services_url}/publickeys");
|
||||||
|
|
||||||
let agent = create_agent(auth_config);
|
let client = create_client(auth_config);
|
||||||
let mut response = agent
|
let response = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.call()
|
.send()
|
||||||
|
.await
|
||||||
.map_err(|_| AuthError::FailedResponse)?;
|
.map_err(|_| AuthError::FailedResponse)?;
|
||||||
|
|
||||||
match response.status() {
|
match response.status() {
|
||||||
@ -223,10 +218,8 @@ pub fn fetch_mojang_public_keys(
|
|||||||
other => Err(AuthError::UnknownStatusCode(other))?,
|
other => Err(AuthError::UnknownStatusCode(other))?,
|
||||||
}
|
}
|
||||||
|
|
||||||
let public_keys: MojangPublicKeys = response
|
let public_keys: MojangPublicKeys =
|
||||||
.body_mut()
|
response.json().await.map_err(|_| AuthError::FailedParse)?;
|
||||||
.read_json()
|
|
||||||
.map_err(|_| AuthError::FailedParse)?;
|
|
||||||
|
|
||||||
let as_rsa_keys = public_keys
|
let as_rsa_keys = public_keys
|
||||||
.player_certificate_keys
|
.player_certificate_keys
|
||||||
@ -248,7 +241,7 @@ struct MojangProfileByNameResponse {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_profile_by_name(
|
pub async fn lookup_profile_by_name(
|
||||||
name: &str,
|
name: &str,
|
||||||
auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<Option<(Uuid, String)>, AuthError> {
|
) -> Result<Option<(Uuid, String)>, AuthError> {
|
||||||
@ -263,7 +256,7 @@ pub fn lookup_profile_by_name(
|
|||||||
candidate_urls.push(fallback.as_str());
|
candidate_urls.push(fallback.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let agent = create_agent(auth_config);
|
let client = create_client(auth_config);
|
||||||
|
|
||||||
let mut not_found_count = 0;
|
let mut not_found_count = 0;
|
||||||
let mut last_unknown_status = None;
|
let mut last_unknown_status = None;
|
||||||
@ -271,7 +264,7 @@ pub fn lookup_profile_by_name(
|
|||||||
for url_template in candidate_urls {
|
for url_template in candidate_urls {
|
||||||
let address = url_template.replace("{username}", name);
|
let address = url_template.replace("{username}", name);
|
||||||
|
|
||||||
let mut response = match agent.get(&address).call() {
|
let response = match client.get(&address).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@ -288,10 +281,7 @@ pub fn lookup_profile_by_name(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
StatusCode::OK => match response
|
StatusCode::OK => match response.json::<MojangProfileByNameResponse>().await {
|
||||||
.body_mut()
|
|
||||||
.read_json::<MojangProfileByNameResponse>()
|
|
||||||
{
|
|
||||||
Ok(profile) => {
|
Ok(profile) => {
|
||||||
let parsed_uuid =
|
let parsed_uuid =
|
||||||
Uuid::parse_str(&profile.id).map_err(|_| AuthError::FailedParse)?;
|
Uuid::parse_str(&profile.id).map_err(|_| AuthError::FailedParse)?;
|
||||||
@ -321,7 +311,20 @@ pub fn lookup_profile_by_name(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_profile_by_uuid(
|
pub fn lookup_profile_by_name_blocking(
|
||||||
|
name: &str,
|
||||||
|
auth_config: &AuthenticationConfig,
|
||||||
|
) -> Result<Option<(Uuid, String)>, AuthError> {
|
||||||
|
if let Ok(handle) = tokio::runtime::Handle::try_current() {
|
||||||
|
tokio::task::block_in_place(|| handle.block_on(lookup_profile_by_name(name, auth_config)))
|
||||||
|
} else {
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.map_err(|_e| AuthError::FailedParse)?
|
||||||
|
.block_on(lookup_profile_by_name(name, auth_config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_profile_by_uuid(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<Option<GameProfile>, AuthError> {
|
) -> Result<Option<GameProfile>, AuthError> {
|
||||||
@ -336,7 +339,7 @@ pub fn fetch_profile_by_uuid(
|
|||||||
candidate_urls.push(fallback.as_str());
|
candidate_urls.push(fallback.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let agent = create_agent(auth_config);
|
let client = create_client(auth_config);
|
||||||
|
|
||||||
let mut not_found_count = 0;
|
let mut not_found_count = 0;
|
||||||
let mut last_unknown_status = None;
|
let mut last_unknown_status = None;
|
||||||
@ -348,7 +351,7 @@ pub fn fetch_profile_by_uuid(
|
|||||||
.replace("{uuid}", &uuid_simple)
|
.replace("{uuid}", &uuid_simple)
|
||||||
.replace("{uuid_hyphenated}", &uuid.to_string());
|
.replace("{uuid_hyphenated}", &uuid.to_string());
|
||||||
|
|
||||||
let mut response = match agent.get(&address).call() {
|
let response = match client.get(&address).send().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!("Profile fetch server at '{address}' is down or unreachable: {err}");
|
tracing::warn!("Profile fetch server at '{address}' is down or unreachable: {err}");
|
||||||
@ -363,7 +366,7 @@ pub fn fetch_profile_by_uuid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
StatusCode::OK => match response.body_mut().read_json::<GameProfile>() {
|
StatusCode::OK => match response.json::<GameProfile>().await {
|
||||||
Ok(profile) => return Ok(Some(profile)),
|
Ok(profile) => return Ok(Some(profile)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
||||||
|
|||||||
311
crates/pumpkin/src/net/chunk_sender.rs
Normal file
311
crates/pumpkin/src/net/chunk_sender.rs
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
use pumpkin_protocol::java::client::play::{
|
||||||
|
CChunkBatchEnd, CChunkBatchStart, CChunkData, CLightUpdate, CUnloadChunk,
|
||||||
|
};
|
||||||
|
use pumpkin_protocol::ser::NetworkWriteExt;
|
||||||
|
use pumpkin_protocol::{ClientPacket, MultiVersionJavaPacket};
|
||||||
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
use pumpkin_world::chunk::ChunkData;
|
||||||
|
use pumpkin_world::level::{Level, SyncChunk};
|
||||||
|
|
||||||
|
use crate::net::ClientPlatform;
|
||||||
|
|
||||||
|
const MIN_CHUNKS_PER_TICK: f32 = 0.1;
|
||||||
|
const MAX_CHUNKS_PER_TICK: f32 = 500.0;
|
||||||
|
const INITIAL_CHUNKS_PER_TICK: f32 = 9.0;
|
||||||
|
const MAX_CONCURRENT_BATCHES: u16 = 10;
|
||||||
|
|
||||||
|
pub struct PreparedChunk {
|
||||||
|
pub position: Vector2<i32>,
|
||||||
|
pub chunk: SyncChunk,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PreparedBatch {
|
||||||
|
pub chunks: Vec<PreparedChunk>,
|
||||||
|
pub epoch_snapshot: u32,
|
||||||
|
pub target_version: JavaMinecraftVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EncodedChunk {
|
||||||
|
pub position: Vector2<i32>,
|
||||||
|
pub payload: Bytes,
|
||||||
|
pub light_payload: Option<Bytes>,
|
||||||
|
pub chunk_ref: Weak<ChunkData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodedChunk {
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_fresh_for(&self, candidate: &PreparedChunk) -> bool {
|
||||||
|
let Some(held) = self.chunk_ref.upgrade() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.position == candidate.position && Arc::ptr_eq(&held, &candidate.chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ChunkSender {
|
||||||
|
pub pending_chunks: FxHashSet<Vector2<i32>>,
|
||||||
|
sent_chunks: FxHashSet<Vector2<i32>>,
|
||||||
|
pub in_flight_batches: u16,
|
||||||
|
pub desired_rate: f32,
|
||||||
|
pub send_quota: f32,
|
||||||
|
pub max_in_flight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkSender {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pending_chunks: FxHashSet::default(),
|
||||||
|
sent_chunks: FxHashSet::default(),
|
||||||
|
in_flight_batches: 0,
|
||||||
|
desired_rate: INITIAL_CHUNKS_PER_TICK,
|
||||||
|
send_quota: 0.0,
|
||||||
|
max_in_flight: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.pending_chunks.clear();
|
||||||
|
self.sent_chunks.clear();
|
||||||
|
self.in_flight_batches = 0;
|
||||||
|
self.send_quota = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_chunk_sent(&self, pos: &Vector2<i32>) -> bool {
|
||||||
|
self.sent_chunks.contains(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn sent_chunks_count(&self) -> usize {
|
||||||
|
self.sent_chunks.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn on_batch_acknowledged(&mut self, client_requested_rate: f32) -> bool {
|
||||||
|
if self.in_flight_batches == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.in_flight_batches = self.in_flight_batches.saturating_sub(1);
|
||||||
|
self.desired_rate = if client_requested_rate.is_nan() {
|
||||||
|
MIN_CHUNKS_PER_TICK
|
||||||
|
} else {
|
||||||
|
client_requested_rate.clamp(MIN_CHUNKS_PER_TICK, MAX_CHUNKS_PER_TICK)
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.in_flight_batches == 0 {
|
||||||
|
self.send_quota = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_in_flight = MAX_CONCURRENT_BATCHES;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue_chunk(&mut self, pos: Vector2<i32>) {
|
||||||
|
self.sent_chunks.remove(&pos);
|
||||||
|
self.pending_chunks.insert(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload_chunk(&mut self, client: &ClientPlatform, pos: Vector2<i32>) {
|
||||||
|
self.pending_chunks.remove(&pos);
|
||||||
|
if self.sent_chunks.remove(&pos)
|
||||||
|
&& let ClientPlatform::Java(java_client) = client
|
||||||
|
&& !java_client.is_closed()
|
||||||
|
{
|
||||||
|
java_client.try_send_packet(&CUnloadChunk::new(pos.x, pos.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sorted_candidates(&self, level: &Level, center: Vector2<i32>) -> Vec<PreparedChunk> {
|
||||||
|
let quota_limit = self.send_quota.floor() as usize;
|
||||||
|
let mut sorted: Vec<Vector2<i32>> = self.pending_chunks.iter().copied().collect();
|
||||||
|
|
||||||
|
sorted.sort_by_key(|pos| {
|
||||||
|
let dx = (pos.x - center.x).unsigned_abs() as u64;
|
||||||
|
let dz = (pos.y - center.y).unsigned_abs() as u64;
|
||||||
|
dx * dx + dz * dz
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ready = Vec::with_capacity(quota_limit);
|
||||||
|
for pos in sorted {
|
||||||
|
if ready.len() >= quota_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(chunk) = level.loaded_chunks.get(&pos) {
|
||||||
|
ready.push(PreparedChunk {
|
||||||
|
position: pos,
|
||||||
|
chunk: chunk.value().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_batch(
|
||||||
|
&mut self,
|
||||||
|
level: &Level,
|
||||||
|
player_chunk: Vector2<i32>,
|
||||||
|
epoch: u32,
|
||||||
|
version: JavaMinecraftVersion,
|
||||||
|
) -> Option<PreparedBatch> {
|
||||||
|
if self.in_flight_batches >= self.max_in_flight {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_batch = self.desired_rate.max(1.0);
|
||||||
|
self.send_quota = (self.send_quota + self.desired_rate).min(max_batch);
|
||||||
|
|
||||||
|
if self.send_quota < 1.0 || self.pending_chunks.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidates = self.collect_sorted_candidates(level, player_chunk);
|
||||||
|
if candidates.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(PreparedBatch {
|
||||||
|
chunks: candidates,
|
||||||
|
epoch_snapshot: epoch,
|
||||||
|
target_version: version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_batch(
|
||||||
|
batch: &PreparedBatch,
|
||||||
|
cache: &mut FxHashMap<Vector2<i32>, EncodedChunk>,
|
||||||
|
) -> Vec<EncodedChunk> {
|
||||||
|
let version = batch.target_version;
|
||||||
|
let cached_map = &*cache;
|
||||||
|
|
||||||
|
let encoded_results: Vec<Option<EncodedChunk>> = batch
|
||||||
|
.chunks
|
||||||
|
.par_iter()
|
||||||
|
.map(|candidate| {
|
||||||
|
let pos = candidate.position;
|
||||||
|
if let Some(cached) = cached_map.get(&pos)
|
||||||
|
&& cached.is_fresh_for(candidate)
|
||||||
|
{
|
||||||
|
return Some(cached.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = &candidate.chunk;
|
||||||
|
let mut chunk_buf = Vec::with_capacity(32 * 1024);
|
||||||
|
if chunk_buf
|
||||||
|
.write_var_int(&VarInt(CChunkData::to_id(version)))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if CChunkData(chunk)
|
||||||
|
.write_packet_data(&mut chunk_buf, &version)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let light_payload = if version >= JavaMinecraftVersion::V_1_14
|
||||||
|
&& version < JavaMinecraftVersion::V_1_18
|
||||||
|
{
|
||||||
|
CLightUpdate::from_chunk(chunk, version)
|
||||||
|
.ok()
|
||||||
|
.and_then(|light_packet| {
|
||||||
|
let mut light_buf = Vec::new();
|
||||||
|
(light_buf
|
||||||
|
.write_var_int(&VarInt(CLightUpdate::to_id(version)))
|
||||||
|
.is_ok()
|
||||||
|
&& light_packet
|
||||||
|
.write_packet_data(&mut light_buf, &version)
|
||||||
|
.is_ok())
|
||||||
|
.then(|| Bytes::from(light_buf))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(EncodedChunk {
|
||||||
|
position: pos,
|
||||||
|
payload: Bytes::from(chunk_buf),
|
||||||
|
light_payload,
|
||||||
|
chunk_ref: Arc::downgrade(chunk),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut output = Vec::with_capacity(encoded_results.len());
|
||||||
|
for encoded in encoded_results.into_iter().flatten() {
|
||||||
|
cache.insert(encoded.position, encoded.clone());
|
||||||
|
output.push(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit_batch(
|
||||||
|
&mut self,
|
||||||
|
batch: &PreparedBatch,
|
||||||
|
encoded_chunks: &[EncodedChunk],
|
||||||
|
client: &ClientPlatform,
|
||||||
|
current_epoch: u32,
|
||||||
|
) -> Vec<Vector2<i32>> {
|
||||||
|
if current_epoch != batch.epoch_snapshot || encoded_chunks.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dispatched_positions = Vec::with_capacity(encoded_chunks.len());
|
||||||
|
let version = batch.target_version;
|
||||||
|
|
||||||
|
if version >= JavaMinecraftVersion::V_1_20_2
|
||||||
|
&& let ClientPlatform::Java(java_client) = client
|
||||||
|
{
|
||||||
|
java_client.try_send_packet(&CChunkBatchStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
for chunk in encoded_chunks {
|
||||||
|
if !self.pending_chunks.contains(&chunk.position) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.try_enqueue_packet(chunk.payload.clone());
|
||||||
|
if let Some(ref light) = chunk.light_payload {
|
||||||
|
client.try_enqueue_packet(light.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pending_chunks.remove(&chunk.position);
|
||||||
|
self.sent_chunks.insert(chunk.position);
|
||||||
|
dispatched_positions.push(chunk.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sent_count = dispatched_positions.len();
|
||||||
|
if sent_count > 0 {
|
||||||
|
if version >= JavaMinecraftVersion::V_1_20_2
|
||||||
|
&& let ClientPlatform::Java(java_client) = client
|
||||||
|
{
|
||||||
|
java_client.try_send_packet(&CChunkBatchEnd::new(sent_count as u16));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.in_flight_batches = self.in_flight_batches.saturating_add(1);
|
||||||
|
self.send_quota -= sent_count as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatched_positions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChunkSender {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -66,7 +66,10 @@ impl PendingConnection {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if server.advanced_config.networking.java.online_mode {
|
if server.advanced_config.networking.java.online_mode {
|
||||||
match self.authenticate(server, &shared_secret, &profile_name) {
|
match self
|
||||||
|
.authenticate(server, &shared_secret, &profile_name)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(new_profile) => self.gameprofile = Some(new_profile),
|
Ok(new_profile) => self.gameprofile = Some(new_profile),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.kick(match error {
|
self.kick(match error {
|
||||||
@ -168,7 +171,7 @@ impl PendingConnection {
|
|||||||
Some(PacketHandlerResult::ReadyToPlay(profile.clone(), config))
|
Some(PacketHandlerResult::ReadyToPlay(profile.clone(), config))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn authenticate(
|
async fn authenticate(
|
||||||
&self,
|
&self,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
shared_secret: &[u8],
|
shared_secret: &[u8],
|
||||||
@ -181,7 +184,8 @@ impl PendingConnection {
|
|||||||
&hash,
|
&hash,
|
||||||
&ip,
|
&ip,
|
||||||
&server.advanced_config.networking.java.authentication,
|
&server.advanced_config.networking.java.authentication,
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(actions) = &profile.profile_actions {
|
if let Some(actions) = &profile.profile_actions {
|
||||||
if server
|
if server
|
||||||
|
|||||||
@ -4,10 +4,10 @@ use super::*;
|
|||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub fn handle_chunk_batch(&self, player: &Player, packet: &SChunkBatch) {
|
pub fn handle_chunk_batch(&self, player: &Player, packet: &SChunkBatch) {
|
||||||
player
|
player
|
||||||
.chunk_manager
|
.chunk_sender
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.handle_acknowledge(packet.chunks_per_tick);
|
.on_batch_acknowledged(packet.chunks_per_tick);
|
||||||
trace!(
|
trace!(
|
||||||
"Client requested {} chunks per tick",
|
"Client requested {} chunks per tick",
|
||||||
packet.chunks_per_tick
|
packet.chunks_per_tick
|
||||||
|
|||||||
@ -28,6 +28,8 @@ use thiserror::Error;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
pub mod bedrock;
|
pub mod bedrock;
|
||||||
|
pub mod chunk_sender;
|
||||||
|
pub use chunk_sender::ChunkSender;
|
||||||
pub mod java;
|
pub mod java;
|
||||||
pub mod lan_broadcast;
|
pub mod lan_broadcast;
|
||||||
pub mod packet_limiter;
|
pub mod packet_limiter;
|
||||||
|
|||||||
@ -182,15 +182,43 @@ pub fn fetch_market_public_key() -> Result<String, String> {
|
|||||||
return Ok(cached_key.clone());
|
return Ok(cached_key.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = ureq::get(PUMPKIN_MARKET_PUBLIC_KEY_URL)
|
let body = if let Ok(handle) = tokio::runtime::Handle::try_current() {
|
||||||
.header("User-Agent", "Pumpkin-MC")
|
tokio::task::block_in_place(|| {
|
||||||
.call()
|
handle.block_on(async {
|
||||||
.map_err(|e| format!("Failed to fetch public key from market: {e}"))?;
|
let client = reqwest::Client::builder()
|
||||||
|
.user_agent("Pumpkin-MC")
|
||||||
let body = response
|
.build()
|
||||||
.body_mut()
|
.map_err(|e| format!("Failed to build HTTP client: {e}"))?;
|
||||||
.read_to_string()
|
let response = client
|
||||||
.map_err(|e| format!("Failed to read public key response: {e}"))?;
|
.get(PUMPKIN_MARKET_PUBLIC_KEY_URL)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to fetch public key from market: {e}"))?;
|
||||||
|
response
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to read public key response: {e}"))
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.map_err(|e| format!("Failed to create runtime: {e}"))?
|
||||||
|
.block_on(async {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.user_agent("Pumpkin-MC")
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("Failed to build HTTP client: {e}"))?;
|
||||||
|
let response = client
|
||||||
|
.get(PUMPKIN_MARKET_PUBLIC_KEY_URL)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to fetch public key from market: {e}"))?;
|
||||||
|
response
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to read public key response: {e}"))
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
let key = body.trim().trim_matches('"').to_string();
|
let key = body.trim().trim_matches('"').to_string();
|
||||||
if key.is_empty() {
|
if key.is_empty() {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use pumpkin_data::enchantment::AttributeModifierSlot;
|
use pumpkin_data::enchantment::AttributeModifierSlot;
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -16,7 +16,7 @@ pub struct CustomEnchantmentEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EnchantmentManager {
|
pub struct EnchantmentManager {
|
||||||
custom_enchantments: RwLock<HashMap<String, CustomEnchantmentEntry>>,
|
custom_enchantments: RwLock<FxHashMap<String, CustomEnchantmentEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EnchantmentManager {
|
impl Default for EnchantmentManager {
|
||||||
@ -29,7 +29,7 @@ impl EnchantmentManager {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
custom_enchantments: RwLock::new(HashMap::new()),
|
custom_enchantments: RwLock::new(FxHashMap::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ use pumpkin_world::world_info::{LevelData, WorldInfoError, WorldInfoReader, Worl
|
|||||||
use rand::seq::{IndexedRandom, SliceRandom};
|
use rand::seq::{IndexedRandom, SliceRandom};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
use std::collections::HashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -330,10 +330,12 @@ impl Server {
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let keys = fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| {
|
let keys = fetch_mojang_public_keys(&auth_config)
|
||||||
error!("Failed to fetch Mojang keys: {e}");
|
.await
|
||||||
Vec::new()
|
.unwrap_or_else(|e| {
|
||||||
});
|
error!("Failed to fetch Mojang keys: {e}");
|
||||||
|
Vec::new()
|
||||||
|
});
|
||||||
server_clone.mojang_public_keys.store(Arc::new(keys));
|
server_clone.mojang_public_keys.store(Arc::new(keys));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -358,7 +360,9 @@ impl Server {
|
|||||||
auth.url.as_deref(),
|
auth.url.as_deref(),
|
||||||
auth.connect_timeout,
|
auth.connect_timeout,
|
||||||
auth.read_timeout,
|
auth.read_timeout,
|
||||||
) {
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(keys) => keys,
|
Ok(keys) => keys,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Failed to fetch Bedrock OIDC keys: {error}");
|
error!("Failed to fetch Bedrock OIDC keys: {error}");
|
||||||
@ -1179,7 +1183,7 @@ impl Server {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<FxHashSet<_>>();
|
||||||
let type_excluded = target_selector
|
let type_excluded = target_selector
|
||||||
.conditions
|
.conditions
|
||||||
.iter()
|
.iter()
|
||||||
@ -1190,7 +1194,7 @@ impl Server {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<FxHashSet<_>>();
|
||||||
|
|
||||||
players.retain(|_| {
|
players.retain(|_| {
|
||||||
(type_excluded.is_empty() || !type_excluded.contains(player_type))
|
(type_excluded.is_empty() || !type_excluded.contains(player_type))
|
||||||
@ -1290,7 +1294,7 @@ impl Server {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<FxHashSet<_>>();
|
||||||
let type_excluded = target_selector
|
let type_excluded = target_selector
|
||||||
.conditions
|
.conditions
|
||||||
.iter()
|
.iter()
|
||||||
@ -1301,7 +1305,7 @@ impl Server {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<FxHashSet<_>>();
|
||||||
entities.retain(|entity| {
|
entities.retain(|entity| {
|
||||||
// Filter by entity type
|
// Filter by entity type
|
||||||
(type_excluded.is_empty() || !type_excluded.contains(&entity.get_entity().entity_type))
|
(type_excluded.is_empty() || !type_excluded.contains(&entity.get_entity().entity_type))
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::{num::NonZero, sync::Arc};
|
|||||||
|
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate,
|
bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate,
|
||||||
java::client::play::{CCenterChunk, CUnloadChunk},
|
java::client::play::CCenterChunk,
|
||||||
};
|
};
|
||||||
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
|
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
|
||||||
|
|
||||||
@ -78,30 +78,51 @@ pub fn update_position(player: &Arc<Player>) {
|
|||||||
let loading_chunks: Vec<_> = loading_iter.collect();
|
let loading_chunks: Vec<_> = loading_iter.collect();
|
||||||
let unloading_chunks: Vec<_> = unloading_iter.collect();
|
let unloading_chunks: Vec<_> = unloading_iter.collect();
|
||||||
|
|
||||||
// Use the chunk_manager's world reference, which is updated on dimension change.
|
let world = player.world();
|
||||||
// This ensures we load chunks from the correct world after portal teleportation.
|
let level = &world.level;
|
||||||
let world = {
|
let mut held_tickets = player
|
||||||
let mut chunk_manager = player
|
.held_chunk_tickets
|
||||||
.chunk_manager
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut lock = level
|
||||||
|
.chunk_loading
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let world = chunk_manager.world().clone();
|
let new_level = pumpkin_world::chunk_system::ChunkLoading::get_level_from_view_distance(
|
||||||
chunk_manager.update_center_and_view_distance(
|
u8::from(view_distance) + 1,
|
||||||
new_chunk_center,
|
|
||||||
view_distance.into(),
|
|
||||||
&world.level,
|
|
||||||
&loading_chunks,
|
|
||||||
&unloading_chunks,
|
|
||||||
);
|
);
|
||||||
world
|
lock.add_ticket(new_chunk_center, new_level);
|
||||||
};
|
|
||||||
player.watched_section.store(new_cylindrical);
|
|
||||||
|
|
||||||
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
let sim_dist = world.server.upgrade().map_or(10, |s| {
|
||||||
for chunk in &unloading_chunks {
|
s.advanced_config.networking.java.simulation_distance.get()
|
||||||
client.try_send_packet(&CUnloadChunk::new(chunk.x, chunk.y));
|
});
|
||||||
|
let sim_level =
|
||||||
|
pumpkin_world::chunk_system::ChunkLoading::get_level_from_simulation_distance(sim_dist);
|
||||||
|
lock.add_ticket(new_chunk_center, sim_level);
|
||||||
|
|
||||||
|
if let Some((held_view, held_sim)) = held_tickets.replace((new_level, sim_level)) {
|
||||||
|
lock.remove_ticket(old_cylindrical.center, held_view);
|
||||||
|
lock.remove_ticket(old_cylindrical.center, held_sim);
|
||||||
|
}
|
||||||
|
lock.send_change();
|
||||||
|
};
|
||||||
|
drop(held_tickets);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut sender = player
|
||||||
|
.chunk_sender
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
|
for pos in &unloading_chunks {
|
||||||
|
sender.unload_chunk(&player.client, *pos);
|
||||||
|
}
|
||||||
|
for pos in &loading_chunks {
|
||||||
|
sender.enqueue_chunk(*pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
player.watched_section.store(new_cylindrical);
|
||||||
|
|
||||||
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
|
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
|
||||||
// ensure what we unload when the player disconnects is correct.
|
// ensure what we unload when the player disconnects is correct.
|
||||||
@ -133,4 +154,5 @@ pub fn update_position(player: &Arc<Player>) {
|
|||||||
if !loading_chunks.is_empty() {
|
if !loading_chunks.is_empty() {
|
||||||
world.spawn_world_entity_chunks(player.clone(), loading_chunks, new_chunk_center);
|
world.spawn_world_entity_chunks(player.clone(), loading_chunks, new_chunk_center);
|
||||||
}
|
}
|
||||||
|
world.entity_tracker.update_player_position(player, &world);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::entity::player::Player;
|
|||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions};
|
use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions};
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -43,7 +43,7 @@ impl CustomBossbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct CustomBossbars {
|
pub struct CustomBossbars {
|
||||||
pub custom_bossbars: HashMap<String, CustomBossbar>,
|
pub custom_bossbars: FxHashMap<String, CustomBossbar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CustomBossbars {
|
impl Default for CustomBossbars {
|
||||||
@ -56,7 +56,7 @@ impl CustomBossbars {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
custom_bossbars: HashMap::new(),
|
custom_bossbars: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
461
crates/pumpkin/src/world/entity_tracker.rs
Normal file
461
crates/pumpkin/src/world/entity_tracker.rs
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytes::BufMut;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use dashmap::DashSet;
|
||||||
|
use pumpkin_protocol::bedrock::client::remove_actor::CRemoveActor;
|
||||||
|
use pumpkin_protocol::codec::var_long::VarLong;
|
||||||
|
use pumpkin_protocol::java::client::play::{
|
||||||
|
CEntityVelocity, CHeadRot, CRemoveEntities, CSetEntityMetadata, Metadata,
|
||||||
|
};
|
||||||
|
use pumpkin_protocol::{BClientPacket, ClientPacket};
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::entity::EntityBase;
|
||||||
|
use crate::entity::player::Player;
|
||||||
|
use crate::net::ClientPlatform;
|
||||||
|
use crate::net::java::JavaClient;
|
||||||
|
use crate::world::World;
|
||||||
|
use crate::world::chunker::{get_view_distance, is_within_view_distance};
|
||||||
|
|
||||||
|
/// Represents an entity tracked in the world and its current watchers,
|
||||||
|
/// corresponding to Vanilla's `ChunkMap.TrackedEntity`.
|
||||||
|
pub struct TrackedEntity {
|
||||||
|
pub entity: Arc<dyn EntityBase>,
|
||||||
|
pub entity_id: i32,
|
||||||
|
pub tracking_range: u32,
|
||||||
|
pub update_interval: u32,
|
||||||
|
pub track_deltas: bool,
|
||||||
|
pub seen_by: DashSet<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackedEntity {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
entity: Arc<dyn EntityBase>,
|
||||||
|
range: u32,
|
||||||
|
update_interval: u32,
|
||||||
|
track_deltas: bool,
|
||||||
|
) -> Self {
|
||||||
|
let entity_id = entity.get_entity().entity_id;
|
||||||
|
Self {
|
||||||
|
entity,
|
||||||
|
entity_id,
|
||||||
|
tracking_range: range,
|
||||||
|
update_interval,
|
||||||
|
track_deltas,
|
||||||
|
seen_by: DashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the effective tracking range in chunks, taking passengers into account.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_effective_range(&self) -> u32 {
|
||||||
|
let mut effective_range = self.tracking_range;
|
||||||
|
if let Ok(passengers) = self.entity.get_entity().passengers.try_lock() {
|
||||||
|
for passenger in passengers.iter() {
|
||||||
|
let passenger_range = passenger.get_entity().entity_type.client_tracking_range;
|
||||||
|
if passenger_range > effective_range {
|
||||||
|
effective_range = passenger_range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
effective_range
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates visibility for a single player.
|
||||||
|
pub fn update_player(&self, player: &Arc<Player>, _world: &World) {
|
||||||
|
if player.get_entity().entity_id == self.entity_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player_entity = player.get_entity();
|
||||||
|
let player_pos = player_entity.pos.load();
|
||||||
|
let entity_pos = self.entity.get_entity().pos.load();
|
||||||
|
let dx = player_pos.x - entity_pos.x;
|
||||||
|
let dz = player_pos.z - entity_pos.z;
|
||||||
|
let dist_sq = dx.mul_add(dx, dz * dz);
|
||||||
|
|
||||||
|
let player_vd = get_view_distance(player).get() as i32;
|
||||||
|
let effective_range = self.get_effective_range();
|
||||||
|
let visible_range_blocks = f64::from((effective_range as i32).min(player_vd) * 16);
|
||||||
|
let range_sq = visible_range_blocks * visible_range_blocks;
|
||||||
|
|
||||||
|
let entity_chunk = self.entity.get_entity().chunk_pos.load();
|
||||||
|
let player_chunk = player_entity.chunk_pos.load();
|
||||||
|
let in_view = is_within_view_distance(entity_chunk, player_chunk, player_vd);
|
||||||
|
|
||||||
|
let is_visible = dist_sq <= range_sq && in_view;
|
||||||
|
|
||||||
|
if is_visible {
|
||||||
|
if self.seen_by.insert(player.gameprofile.id) {
|
||||||
|
self.add_pairing(player);
|
||||||
|
}
|
||||||
|
} else if self.seen_by.remove(&player.gameprofile.id).is_some() {
|
||||||
|
self.remove_pairing(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates visibility for a list of players.
|
||||||
|
pub fn update_players(&self, players: &[Arc<Player>], world: &World) {
|
||||||
|
for player in players {
|
||||||
|
self.update_player(player, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends spawn and initial state packets to the new watcher.
|
||||||
|
pub fn add_pairing(&self, player: &Arc<Player>) {
|
||||||
|
player.client.try_enqueue_spawn_packet(&self.entity);
|
||||||
|
|
||||||
|
if let Some(target_player) = self.entity.get_player() {
|
||||||
|
let skin_parts = target_player.config.load().skin_parts;
|
||||||
|
let target_entity = target_player.get_entity();
|
||||||
|
let target_id = target_entity.entity_id;
|
||||||
|
|
||||||
|
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
|
let version = client.version.load();
|
||||||
|
if version >= JavaMinecraftVersion::V_1_21 {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
for meta in [
|
||||||
|
Metadata::new(
|
||||||
|
pumpkin_data::tracked_data::player::PLAYER_MODE_CUSTOMISATION,
|
||||||
|
skin_parts,
|
||||||
|
),
|
||||||
|
Metadata::new(
|
||||||
|
pumpkin_data::tracked_data::player::PLAYER_MODE_CUSTOMIZATION_ID,
|
||||||
|
skin_parts,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let _ = meta.write(&mut buf, &version);
|
||||||
|
}
|
||||||
|
buf.put_u8(255);
|
||||||
|
let meta_packet = CSetEntityMetadata::new(target_id.into(), buf.into());
|
||||||
|
if let Ok(packet_data) =
|
||||||
|
JavaClient::serialize_packet_for_version(&meta_packet, version)
|
||||||
|
{
|
||||||
|
client.try_enqueue_packet(packet_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let head_yaw = target_entity.head_yaw.load();
|
||||||
|
let head_rot_packet = CHeadRot::new(
|
||||||
|
target_id.into(),
|
||||||
|
(head_yaw * 256.0 / 360.0).rem_euclid(256.0) as u8,
|
||||||
|
);
|
||||||
|
if let Ok(data) = client.serialize_packet(&head_rot_packet) {
|
||||||
|
client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if self.entity.get_living_entity().is_some()
|
||||||
|
&& let ClientPlatform::Java(client) = player.client.as_ref()
|
||||||
|
{
|
||||||
|
let head_yaw = self.entity.get_entity().head_yaw.load();
|
||||||
|
let head_rot_packet = CHeadRot::new(
|
||||||
|
self.entity_id.into(),
|
||||||
|
(head_yaw * 256.0 / 360.0).rem_euclid(256.0) as u8,
|
||||||
|
);
|
||||||
|
if let Ok(data) = client.serialize_packet(&head_rot_packet) {
|
||||||
|
client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vel = self.entity.get_entity().velocity.load();
|
||||||
|
if vel.length_squared() > 1e-4
|
||||||
|
&& let ClientPlatform::Java(client) = player.client.as_ref()
|
||||||
|
{
|
||||||
|
let motion = CEntityVelocity::new(self.entity_id.into(), vel);
|
||||||
|
if let Ok(data) = client.serialize_packet(&motion) {
|
||||||
|
client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends despawn packet to a player leaving visibility range.
|
||||||
|
pub fn remove_pairing(&self, player: &Arc<Player>) {
|
||||||
|
let entity_ids = [self.entity_id.into()];
|
||||||
|
match player.client.as_ref() {
|
||||||
|
ClientPlatform::Java(client) => {
|
||||||
|
let packet = CRemoveEntities::new(&entity_ids);
|
||||||
|
if let Ok(data) = client.serialize_packet(&packet) {
|
||||||
|
client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientPlatform::Bedrock(client) => {
|
||||||
|
let packet = CRemoveActor::new(VarLong(i64::from(self.entity_id)));
|
||||||
|
if let Ok(data) = client.serialize_packet(&packet) {
|
||||||
|
client.try_enqueue_packet(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Broadcasts removal of this entity to all current watchers.
|
||||||
|
pub fn broadcast_removed(&self, world: &World) {
|
||||||
|
let entity_ids = [self.entity_id.into()];
|
||||||
|
let je_packet = CRemoveEntities::new(&entity_ids);
|
||||||
|
let be_packet = CRemoveActor::new(VarLong(i64::from(self.entity_id)));
|
||||||
|
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| self.seen_by.contains(&p.gameprofile.id));
|
||||||
|
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
for p in recipients {
|
||||||
|
match p.client.as_ref() {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recipients_by_version =
|
||||||
|
World::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
World::broadcast_java_grouped(&je_packet, recipients_by_version);
|
||||||
|
World::broadcast_bedrock_grouped(&be_packet, bedrock_recipients.into_iter());
|
||||||
|
|
||||||
|
self.seen_by.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a player by UUID without sending despawn packet (used on disconnect).
|
||||||
|
pub fn remove_player(&self, player_uuid: &Uuid) {
|
||||||
|
self.seen_by.remove(player_uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a Java client packet to all tracking players.
|
||||||
|
pub fn send_to_tracking_players<P: ClientPacket + Sync>(&self, packet: &P, world: &World) {
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| self.seen_by.contains(&p.gameprofile.id));
|
||||||
|
let recipients_by_version = World::collect_java_recipients_by_version(recipients);
|
||||||
|
World::broadcast_java_grouped(packet, recipients_by_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a Bedrock client packet to all tracking players.
|
||||||
|
pub fn send_to_tracking_players_bedrock<P: BClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
packet: &P,
|
||||||
|
world: &World,
|
||||||
|
) {
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players.iter().filter_map(|p| {
|
||||||
|
if self.seen_by.contains(&p.gameprofile.id)
|
||||||
|
&& let ClientPlatform::Bedrock(client) = p.client.as_ref()
|
||||||
|
{
|
||||||
|
return Some(client);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
World::broadcast_bedrock_grouped(packet, recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends an editioned packet (Java + Bedrock) to all tracking players.
|
||||||
|
pub fn send_to_tracking_players_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
world: &World,
|
||||||
|
) {
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| self.seen_by.contains(&p.gameprofile.id));
|
||||||
|
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
for p in recipients {
|
||||||
|
match p.client.as_ref() {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recipients_by_version =
|
||||||
|
World::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
World::broadcast_java_grouped(je_packet, recipients_by_version);
|
||||||
|
World::broadcast_bedrock_grouped(be_packet, bedrock_recipients.into_iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a packet to all tracking players and the entity itself if it is a player.
|
||||||
|
pub fn send_to_tracking_players_and_self<P: ClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
packet: &P,
|
||||||
|
world: &World,
|
||||||
|
) {
|
||||||
|
self.send_to_tracking_players(packet, world);
|
||||||
|
if let Some(player) = self.entity.get_player() {
|
||||||
|
player.try_send_client_packet(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends an editioned packet to all tracking players and the entity itself if it is a player.
|
||||||
|
pub fn send_to_tracking_players_and_self_editioned<
|
||||||
|
J: ClientPacket + Sync,
|
||||||
|
B: BClientPacket + Sync,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
world: &World,
|
||||||
|
) {
|
||||||
|
self.send_to_tracking_players_editioned(je_packet, be_packet, world);
|
||||||
|
if let Some(player) = self.entity.get_player() {
|
||||||
|
player.try_enqueue_packet_editioned(je_packet, be_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a packet to tracking players filtered by predicate.
|
||||||
|
pub fn send_to_tracking_players_filtered<P: ClientPacket + Sync, F: Fn(&Player) -> bool>(
|
||||||
|
&self,
|
||||||
|
packet: &P,
|
||||||
|
world: &World,
|
||||||
|
filter: F,
|
||||||
|
) {
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| self.seen_by.contains(&p.gameprofile.id) && filter(p));
|
||||||
|
let recipients_by_version = World::collect_java_recipients_by_version(recipients);
|
||||||
|
World::broadcast_java_grouped(packet, recipients_by_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends an editioned packet to tracking players filtered by predicate.
|
||||||
|
pub fn send_to_tracking_players_filtered_editioned<
|
||||||
|
J: ClientPacket + Sync,
|
||||||
|
B: BClientPacket + Sync,
|
||||||
|
F: Fn(&Player) -> bool,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
world: &World,
|
||||||
|
filter: F,
|
||||||
|
) {
|
||||||
|
let players = world.players.load();
|
||||||
|
let recipients = players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| self.seen_by.contains(&p.gameprofile.id) && filter(p));
|
||||||
|
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
for p in recipients {
|
||||||
|
match p.client.as_ref() {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recipients_by_version =
|
||||||
|
World::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
World::broadcast_java_grouped(je_packet, recipients_by_version);
|
||||||
|
World::broadcast_bedrock_grouped(be_packet, bedrock_recipients.into_iter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The entity tracking system for a world, corresponding to Vanilla's `ChunkMap.entityMap`.
|
||||||
|
pub struct EntityTracker {
|
||||||
|
pub entity_map: DashMap<i32, Arc<TrackedEntity>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EntityTracker {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityTracker {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entity_map: DashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_tracked_entity(&self, entity_id: i32) -> Option<Arc<TrackedEntity>> {
|
||||||
|
self.entity_map.get(&entity_id).map(|r| r.value().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_entity_with_id(&self, entity_id: i32) -> bool {
|
||||||
|
self.entity_map.contains_key(&entity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_tracked_by_any_player(&self, entity_id: i32) -> bool {
|
||||||
|
self.entity_map
|
||||||
|
.get(&entity_id)
|
||||||
|
.is_some_and(|t| !t.seen_by.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entity(&self, entity: &Arc<dyn EntityBase>, world: &World) {
|
||||||
|
let entity_type = entity.get_entity().entity_type;
|
||||||
|
let range = entity_type.client_tracking_range;
|
||||||
|
if range == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let update_interval = entity_type.update_interval;
|
||||||
|
let track_deltas = entity_type.track_deltas;
|
||||||
|
let entity_id = entity.get_entity().entity_id;
|
||||||
|
|
||||||
|
let tracked = Arc::new(TrackedEntity::new(
|
||||||
|
entity.clone(),
|
||||||
|
range,
|
||||||
|
update_interval,
|
||||||
|
track_deltas,
|
||||||
|
));
|
||||||
|
self.entity_map.insert(entity_id, tracked.clone());
|
||||||
|
|
||||||
|
let players = world.players.load();
|
||||||
|
tracked.update_players(players.as_ref(), world);
|
||||||
|
|
||||||
|
// If the newly added entity is a player, update all other tracked entities for this player
|
||||||
|
if let Some(player) = entity.get_player()
|
||||||
|
&& let Some(player_arc) = world.get_player_by_uuid(player.gameprofile.id)
|
||||||
|
{
|
||||||
|
for entry in &self.entity_map {
|
||||||
|
if *entry.key() != entity_id {
|
||||||
|
entry.value().update_player(&player_arc, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_entity(&self, entity: &dyn EntityBase, world: &World) {
|
||||||
|
let entity_id = entity.get_entity().entity_id;
|
||||||
|
if let Some(player) = entity.get_player() {
|
||||||
|
let player_id = player.gameprofile.id;
|
||||||
|
for entry in &self.entity_map {
|
||||||
|
entry.value().remove_player(&player_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, tracked)) = self.entity_map.remove(&entity_id) {
|
||||||
|
tracked.broadcast_removed(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_player_position(&self, player: &Arc<Player>, world: &World) {
|
||||||
|
for entry in &self.entity_map {
|
||||||
|
if *entry.key() == player.get_entity().entity_id {
|
||||||
|
let players = world.players.load();
|
||||||
|
entry.value().update_players(players.as_ref(), world);
|
||||||
|
} else {
|
||||||
|
entry.value().update_player(player, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entity_position(&self, entity: &dyn EntityBase, world: &World) {
|
||||||
|
if let Some(tracked) = self.entity_map.get(&entity.get_entity().entity_id) {
|
||||||
|
let players = world.players.load();
|
||||||
|
tracked.update_players(players.as_ref(), world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_all(&self, world: &World) {
|
||||||
|
let players = world.players.load();
|
||||||
|
for entry in &self.entity_map {
|
||||||
|
entry.value().update_players(players.as_ref(), world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -116,7 +116,7 @@ use pumpkin_protocol::{
|
|||||||
java::{
|
java::{
|
||||||
self,
|
self,
|
||||||
client::play::{
|
client::play::{
|
||||||
CBlockEntityData, CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate,
|
CBlockEntityData, CDamageEvent, CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate,
|
||||||
CPlayerChatMessage, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
|
CPlayerChatMessage, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
|
||||||
CSetSelectedSlot, CSoundEffect, CSpawnEntity, FilterType, GameEvent, InitChat,
|
CSetSelectedSlot, CSoundEffect, CSpawnEntity, FilterType, GameEvent, InitChat,
|
||||||
PlayerAction, PlayerInfoFlags,
|
PlayerAction, PlayerInfoFlags,
|
||||||
@ -160,6 +160,7 @@ pub mod bossbar;
|
|||||||
pub mod custom_bossbar;
|
pub mod custom_bossbar;
|
||||||
pub mod dragon_fight;
|
pub mod dragon_fight;
|
||||||
pub mod end_podium;
|
pub mod end_podium;
|
||||||
|
pub mod entity_tracker;
|
||||||
pub mod natural_spawner;
|
pub mod natural_spawner;
|
||||||
pub mod scoreboard;
|
pub mod scoreboard;
|
||||||
pub mod weather;
|
pub mod weather;
|
||||||
@ -280,6 +281,8 @@ pub struct World {
|
|||||||
pub custom_data: std::sync::Mutex<NbtCompound>,
|
pub custom_data: std::sync::Mutex<NbtCompound>,
|
||||||
/// Persistent custom data for block entities at specific positions
|
/// Persistent custom data for block entities at specific positions
|
||||||
pub custom_block_entity_data: DashMap<BlockPos, NbtCompound>,
|
pub custom_block_entity_data: DashMap<BlockPos, NbtCompound>,
|
||||||
|
/// Entity tracker responsible for tracking entity visibility and sending delta/status packets to watchers.
|
||||||
|
pub entity_tracker: entity_tracker::EntityTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -403,6 +406,7 @@ impl World {
|
|||||||
block_entities: DashMap::new(),
|
block_entities: DashMap::new(),
|
||||||
custom_data: std::sync::Mutex::new(custom_data),
|
custom_data: std::sync::Mutex::new(custom_data),
|
||||||
custom_block_entity_data: DashMap::new(),
|
custom_block_entity_data: DashMap::new(),
|
||||||
|
entity_tracker: entity_tracker::EntityTracker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,14 +550,15 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an entity status update to all players tracking the specified entity.
|
/// Broadcasts an entity status update / event to all players tracking the specified entity,
|
||||||
pub fn send_entity_status(
|
/// and to the entity itself if it is a player.
|
||||||
|
/// Matching Vanilla's `ServerLevel.broadcastEntityEvent(entity, event)`.
|
||||||
|
pub fn broadcast_entity_event(
|
||||||
&self,
|
&self,
|
||||||
entity: &Entity,
|
entity: &Entity,
|
||||||
java_status: EntityStatus,
|
java_status: EntityStatus,
|
||||||
bedrock_status: Option<ActorEventID>,
|
bedrock_status: Option<ActorEventID>,
|
||||||
) {
|
) {
|
||||||
let chunk_pos = entity.chunk_pos.load();
|
|
||||||
let je_packet = CEntityStatus::new(entity.entity_id, java_status as i8);
|
let je_packet = CEntityStatus::new(entity.entity_id, java_status as i8);
|
||||||
if let Some(be_event) = bedrock_status {
|
if let Some(be_event) = bedrock_status {
|
||||||
let be_packet = SActorEvent {
|
let be_packet = SActorEvent {
|
||||||
@ -562,14 +567,44 @@ impl World {
|
|||||||
data: VarInt(0),
|
data: VarInt(0),
|
||||||
fire_at_position: None,
|
fire_at_position: None,
|
||||||
};
|
};
|
||||||
self.broadcast_to_chunk_editioned(chunk_pos, &je_packet, &be_packet);
|
self.send_to_tracking_players_and_self_editioned(entity, &je_packet, &be_packet);
|
||||||
} else {
|
} else {
|
||||||
self.broadcast_to_chunk(chunk_pos, &je_packet);
|
self.send_to_tracking_players_and_self(entity, &je_packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Broadcasts a damage event to all players tracking the specified entity,
|
||||||
|
/// and to the entity itself if it is a player.
|
||||||
|
/// Matching Vanilla's `ServerLevel.broadcastDamageEvent(entity, source)`.
|
||||||
|
pub fn broadcast_damage_event(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
damage_type_id: i32,
|
||||||
|
source_entity_id: Option<i32>,
|
||||||
|
cause_entity_id: Option<i32>,
|
||||||
|
position: Option<Vector3<f64>>,
|
||||||
|
) {
|
||||||
|
let je_packet = CDamageEvent::new(
|
||||||
|
entity.entity_id.into(),
|
||||||
|
damage_type_id.into(),
|
||||||
|
source_entity_id.map(Into::into),
|
||||||
|
cause_entity_id.map(Into::into),
|
||||||
|
position,
|
||||||
|
);
|
||||||
|
self.send_to_tracking_players_and_self(entity, &je_packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends an entity status update to all players tracking the specified entity.
|
||||||
|
pub fn send_entity_status(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
java_status: EntityStatus,
|
||||||
|
bedrock_status: Option<ActorEventID>,
|
||||||
|
) {
|
||||||
|
self.broadcast_entity_event(entity, java_status, bedrock_status);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) {
|
pub fn send_remove_mob_effect(&self, entity: &Entity, effect_type: &'static StatusEffect) {
|
||||||
let chunk_pos = entity.chunk_pos.load();
|
|
||||||
let je_packet =
|
let je_packet =
|
||||||
CRemoveMobEffect::new(entity.entity_id.into(), VarInt(i32::from(effect_type.id)));
|
CRemoveMobEffect::new(entity.entity_id.into(), VarInt(i32::from(effect_type.id)));
|
||||||
|
|
||||||
@ -583,11 +618,10 @@ impl World {
|
|||||||
tick: VarULong(0),
|
tick: VarULong(0),
|
||||||
ambient: false,
|
ambient: false,
|
||||||
};
|
};
|
||||||
self.broadcast_to_chunk_editioned(chunk_pos, &je_packet, &be_packet);
|
self.send_to_tracking_players_and_self_editioned(entity, &je_packet, &be_packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_add_mob_effect(&self, entity: &Entity, effect: &pumpkin_data::potion::Effect) {
|
pub fn send_add_mob_effect(&self, entity: &Entity, effect: &pumpkin_data::potion::Effect) {
|
||||||
let chunk_pos = entity.chunk_pos.load();
|
|
||||||
let mut flags: i8 = 0;
|
let mut flags: i8 = 0;
|
||||||
if effect.ambient {
|
if effect.ambient {
|
||||||
flags |= 0x01;
|
flags |= 0x01;
|
||||||
@ -618,7 +652,91 @@ impl World {
|
|||||||
ambient: effect.ambient,
|
ambient: effect.ambient,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.broadcast_to_chunk_editioned(chunk_pos, &je_packet, &be_packet);
|
self.send_to_tracking_players_and_self_editioned(entity, &je_packet, &be_packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players<P: ClientPacket + Sync>(&self, entity: &Entity, packet: &P) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players(packet, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_bedrock<P: BClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
packet: &P,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_bedrock(packet, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_editioned(je_packet, be_packet, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_and_self<P: ClientPacket + Sync>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
packet: &P,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_and_self(packet, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_and_self_editioned<
|
||||||
|
J: ClientPacket + Sync,
|
||||||
|
B: BClientPacket + Sync,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_and_self_editioned(je_packet, be_packet, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_filtered<P: ClientPacket + Sync, F: Fn(&Player) -> bool>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
packet: &P,
|
||||||
|
filter: F,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_filtered(packet, self, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_to_tracking_players_filtered_editioned<
|
||||||
|
J: ClientPacket + Sync,
|
||||||
|
B: BClientPacket + Sync,
|
||||||
|
F: Fn(&Player) -> bool,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
entity: &Entity,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
filter: F,
|
||||||
|
) {
|
||||||
|
if let Some(tracked) = self.entity_tracker.get_tracked_entity(entity.entity_id) {
|
||||||
|
tracked.send_to_tracking_players_filtered_editioned(je_packet, be_packet, self, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_tracked_by_any_player(&self, entity: &Entity) -> bool {
|
||||||
|
self.entity_tracker
|
||||||
|
.is_tracked_by_any_player(entity.entity_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_difficulty(&self, difficulty: Difficulty) {
|
pub fn set_difficulty(&self, difficulty: Difficulty) {
|
||||||
@ -1323,6 +1441,8 @@ impl World {
|
|||||||
});
|
});
|
||||||
let entity_elapsed = t_entities.elapsed();
|
let entity_elapsed = t_entities.elapsed();
|
||||||
|
|
||||||
|
self.entity_tracker.update_all(self);
|
||||||
|
|
||||||
let mut block_entities: Vec<Arc<dyn BlockEntity>> = Vec::new();
|
let mut block_entities: Vec<Arc<dyn BlockEntity>> = Vec::new();
|
||||||
for chunk_pos in active_chunks.iter() {
|
for chunk_pos in active_chunks.iter() {
|
||||||
if let Some(chunk_block_entities) = self.block_entities.get(chunk_pos) {
|
if let Some(chunk_block_entities) = self.block_entities.get(chunk_pos) {
|
||||||
@ -3788,13 +3908,7 @@ impl World {
|
|||||||
container_id: 0,
|
container_id: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunk_pos = from.get_entity().chunk_pos.load();
|
self.send_to_tracking_players_editioned(from.get_entity(), &je_packet, &be_mob_equipment);
|
||||||
self.broadcast_to_chunk_except_editioned(
|
|
||||||
chunk_pos,
|
|
||||||
&[from.get_entity().entity_uuid],
|
|
||||||
&je_packet,
|
|
||||||
&be_mob_equipment,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_world_info(
|
pub fn send_world_info(
|
||||||
@ -4095,11 +4209,7 @@ impl World {
|
|||||||
// observer sees the player in a world whose chunk manager doesn't match.
|
// observer sees the player in a world whose chunk manager doesn't match.
|
||||||
self.remove_player(player, false).await;
|
self.remove_player(player, false).await;
|
||||||
player.unload_watched_chunks(self).await;
|
player.unload_watched_chunks(self).await;
|
||||||
player
|
player.change_world_chunks(&self.level, &destination);
|
||||||
.chunk_manager
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.change_world(&self.level, destination.clone());
|
|
||||||
player.living_entity.entity.set_world(destination.clone());
|
player.living_entity.entity.set_world(destination.clone());
|
||||||
destination.players.rcu(|current_list| {
|
destination.players.rcu(|current_list| {
|
||||||
let mut new_list = (**current_list).clone();
|
let mut new_list = (**current_list).clone();
|
||||||
@ -4710,6 +4820,8 @@ impl World {
|
|||||||
new_list.push(player.clone());
|
new_list.push(player.clone());
|
||||||
new_list
|
new_list
|
||||||
});
|
});
|
||||||
|
self.entity_tracker
|
||||||
|
.add_entity(&(player.clone() as Arc<dyn EntityBase>), self);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4751,6 +4863,8 @@ impl World {
|
|||||||
new_list
|
new_list
|
||||||
});
|
});
|
||||||
if let Some(ref player) = removed_player {
|
if let Some(ref player) = removed_player {
|
||||||
|
self.entity_tracker
|
||||||
|
.remove_entity(player.as_ref() as &dyn EntityBase, self);
|
||||||
let uuid = player.gameprofile.id;
|
let uuid = player.gameprofile.id;
|
||||||
let entity_id = player.entity_id();
|
let entity_id = player.entity_id();
|
||||||
|
|
||||||
@ -4805,7 +4919,7 @@ impl World {
|
|||||||
#[expect(clippy::needless_pass_by_value)]
|
#[expect(clippy::needless_pass_by_value)]
|
||||||
pub fn spawn_entity_non_save(&self, entity: Arc<dyn EntityBase>) {
|
pub fn spawn_entity_non_save(&self, entity: Arc<dyn EntityBase>) {
|
||||||
let _base_entity = entity.get_entity();
|
let _base_entity = entity.get_entity();
|
||||||
self.broadcast_entity_spawn(&entity);
|
self.entity_tracker.add_entity(&entity, self);
|
||||||
self.spawn_state.load().add_entity(self, entity.as_ref());
|
self.spawn_state.load().add_entity(self, entity.as_ref());
|
||||||
|
|
||||||
self.entities.rcu(|current_entities| {
|
self.entities.rcu(|current_entities| {
|
||||||
@ -4829,7 +4943,6 @@ impl World {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.broadcast_entity_spawn(&entity);
|
|
||||||
entity.init_data_tracker();
|
entity.init_data_tracker();
|
||||||
self.add_entity_silent(entity);
|
self.add_entity_silent(entity);
|
||||||
}
|
}
|
||||||
@ -4869,6 +4982,7 @@ impl World {
|
|||||||
// unload (see `save_entity`), never at spawn, so it can't be both live and
|
// unload (see `save_entity`), never at spawn, so it can't be both live and
|
||||||
// serialized at once (which would double it on the next reload).
|
// serialized at once (which would double it on the next reload).
|
||||||
self.spawn_state.load().add_entity(self, entity.as_ref());
|
self.spawn_state.load().add_entity(self, entity.as_ref());
|
||||||
|
self.entity_tracker.add_entity(&entity, self);
|
||||||
|
|
||||||
self.entities.rcu(|current_entities| {
|
self.entities.rcu(|current_entities| {
|
||||||
let mut new_entities = (**current_entities).clone();
|
let mut new_entities = (**current_entities).clone();
|
||||||
@ -4889,18 +5003,12 @@ impl World {
|
|||||||
base_entity.removed.store(true, Ordering::Release);
|
base_entity.removed.store(true, Ordering::Release);
|
||||||
|
|
||||||
self.spawn_state.load().remove_entity(self, entity);
|
self.spawn_state.load().remove_entity(self, entity);
|
||||||
|
self.entity_tracker.remove_entity(entity, self);
|
||||||
self.entities.rcu(|current_entities| {
|
self.entities.rcu(|current_entities| {
|
||||||
let mut new_entities = (**current_entities).clone();
|
let mut new_entities = (**current_entities).clone();
|
||||||
new_entities.retain(|e| e.get_entity().entity_uuid != base_entity.entity_uuid);
|
new_entities.retain(|e| e.get_entity().entity_uuid != base_entity.entity_uuid);
|
||||||
new_entities
|
new_entities
|
||||||
});
|
});
|
||||||
|
|
||||||
let chunk_pos = base_entity.chunk_pos.load();
|
|
||||||
self.broadcast_to_chunk_editioned(
|
|
||||||
chunk_pos,
|
|
||||||
&CRemoveEntities::new(&[base_entity.entity_id.into()]),
|
|
||||||
&CRemoveActor::new(VarLong(base_entity.entity_id as i64)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_entities_in_chunks(
|
pub async fn remove_entities_in_chunks(
|
||||||
@ -4929,6 +5037,7 @@ impl World {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for entity in entities_to_remove {
|
for entity in entities_to_remove {
|
||||||
|
self.entity_tracker.remove_entity(entity.as_ref(), self);
|
||||||
self.save_entity(&entity).await;
|
self.save_entity(&entity).await;
|
||||||
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ use pumpkin_data::tag::Taggable;
|
|||||||
use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS;
|
use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS;
|
||||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||||
use pumpkin_util::GameMode;
|
use pumpkin_util::GameMode;
|
||||||
use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
|
|
||||||
use pumpkin_util::math::get_section_cord;
|
use pumpkin_util::math::get_section_cord;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
@ -826,16 +825,10 @@ pub fn is_valid_spawn_position_for_type(
|
|||||||
if !check_spawn_rules(entity_type, world, block_pos, is_thundering) {
|
if !check_spawn_rules(entity_type, world, block_pos, is_thundering) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
|
if !world.is_space_empty(entity_type.get_spawn_bounding_box(
|
||||||
if !world.is_space_empty(BoundingBox::new_from_pos(
|
|
||||||
f64::from(block_pos.0.x) + 0.5,
|
f64::from(block_pos.0.x) + 0.5,
|
||||||
f64::from(block_pos.0.y),
|
f64::from(block_pos.0.y),
|
||||||
f64::from(block_pos.0.z) + 0.5,
|
f64::from(block_pos.0.z) + 0.5,
|
||||||
&EntityDimensions {
|
|
||||||
width: entity_type.dimension[0],
|
|
||||||
height: entity_type.dimension[1],
|
|
||||||
eye_height: entity_type.eye_height,
|
|
||||||
},
|
|
||||||
)) {
|
)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use pumpkin_data::scoreboard::ScoreboardDisplaySlot;
|
use pumpkin_data::scoreboard::ScoreboardDisplaySlot;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
@ -114,10 +114,10 @@ impl ScoreboardTarget for NoTarget {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Scoreboard {
|
pub struct Scoreboard {
|
||||||
objectives: HashMap<String, ScoreboardObjective>,
|
objectives: FxHashMap<String, ScoreboardObjective>,
|
||||||
display_slots: HashMap<ScoreboardDisplaySlot, String>,
|
display_slots: FxHashMap<ScoreboardDisplaySlot, String>,
|
||||||
scores: HashMap<String, HashMap<String, ScoreboardScore>>,
|
scores: FxHashMap<String, FxHashMap<String, ScoreboardScore>>,
|
||||||
teams: HashMap<String, Team>,
|
teams: FxHashMap<String, Team>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scoreboard {
|
impl Scoreboard {
|
||||||
@ -127,7 +127,7 @@ impl Scoreboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_objectives(&self) -> &HashMap<String, ScoreboardObjective> {
|
pub const fn get_objectives(&self) -> &FxHashMap<String, ScoreboardObjective> {
|
||||||
&self.objectives
|
&self.objectives
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ impl Scoreboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_display_slots(&self) -> &HashMap<ScoreboardDisplaySlot, String> {
|
pub const fn get_display_slots(&self) -> &FxHashMap<ScoreboardDisplaySlot, String> {
|
||||||
&self.display_slots
|
&self.display_slots
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ impl Scoreboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_scores(&self) -> &HashMap<String, HashMap<String, ScoreboardScore>> {
|
pub const fn get_scores(&self) -> &FxHashMap<String, FxHashMap<String, ScoreboardScore>> {
|
||||||
&self.scores
|
&self.scores
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,13 +170,13 @@ impl Scoreboard {
|
|||||||
pub fn get_scores_for_objective(
|
pub fn get_scores_for_objective(
|
||||||
&self,
|
&self,
|
||||||
objective_name: &str,
|
objective_name: &str,
|
||||||
) -> Option<&HashMap<String, ScoreboardScore>> {
|
) -> Option<&FxHashMap<String, ScoreboardScore>> {
|
||||||
self.scores.get(objective_name)
|
self.scores.get(objective_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_scores_for_entity(&self, entity_name: &str) -> HashMap<String, &ScoreboardScore> {
|
pub fn get_scores_for_entity(&self, entity_name: &str) -> FxHashMap<String, &ScoreboardScore> {
|
||||||
let mut entity_scores = HashMap::new();
|
let mut entity_scores = FxHashMap::default();
|
||||||
for (obj_name, obj_scores) in &self.scores {
|
for (obj_name, obj_scores) in &self.scores {
|
||||||
if let Some(score) = obj_scores.get(entity_name) {
|
if let Some(score) = obj_scores.get(entity_name) {
|
||||||
entity_scores.insert(obj_name.clone(), score);
|
entity_scores.insert(obj_name.clone(), score);
|
||||||
@ -186,7 +186,7 @@ impl Scoreboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_teams(&self) -> &HashMap<String, Team> {
|
pub const fn get_teams(&self) -> &FxHashMap<String, Team> {
|
||||||
&self.teams
|
&self.teams
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -859,9 +859,9 @@ pub struct BedrockObjective {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct BedrockScoreboard {
|
pub struct BedrockScoreboard {
|
||||||
pub objectives: HashMap<String, BedrockObjective>,
|
pub objectives: FxHashMap<String, BedrockObjective>,
|
||||||
pub display_slots: HashMap<BedrockDisplaySlot, String>,
|
pub display_slots: FxHashMap<BedrockDisplaySlot, String>,
|
||||||
pub scores: HashMap<(String, String), i32>,
|
pub scores: FxHashMap<(String, String), i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BedrockScoreboard {
|
impl BedrockScoreboard {
|
||||||
|
|||||||
@ -36,10 +36,18 @@ pub struct EntityType {
|
|||||||
pub category: MobCategory,
|
pub category: MobCategory,
|
||||||
/// Whether this entity can spawn far from the player (beyond normal spawn range).
|
/// Whether this entity can spawn far from the player (beyond normal spawn range).
|
||||||
pub can_spawn_far_from_player: bool,
|
pub can_spawn_far_from_player: bool,
|
||||||
|
/// Client tracking range in chunks.
|
||||||
|
pub client_tracking_range: u32,
|
||||||
|
/// Update interval in ticks.
|
||||||
|
pub update_interval: u32,
|
||||||
|
/// Whether movement deltas should be tracked.
|
||||||
|
pub track_deltas: bool,
|
||||||
/// Bounding box dimensions as `[width, height]` in blocks.
|
/// Bounding box dimensions as `[width, height]` in blocks.
|
||||||
pub dimension: [f32; 2],
|
pub dimension: [f32; 2],
|
||||||
/// Eye height in blocks, used for line-of-sight calculations.
|
/// Eye height in blocks, used for line-of-sight calculations.
|
||||||
pub eye_height: f32,
|
pub eye_height: f32,
|
||||||
|
/// Scale factor for spawn dimensions (e.g. slimes/magma cubes).
|
||||||
|
pub spawn_dimensions_scale: f32,
|
||||||
/// Spawn location restrictions for natural spawning.
|
/// Spawn location restrictions for natural spawning.
|
||||||
pub spawn_restriction: SpawnRestriction,
|
pub spawn_restriction: SpawnRestriction,
|
||||||
}
|
}
|
||||||
@ -173,6 +181,7 @@ impl ToTokens for NamedEntityType<'_> {
|
|||||||
|
|
||||||
let dimension0 = entity.dimension[0];
|
let dimension0 = entity.dimension[0];
|
||||||
let dimension1 = entity.dimension[1];
|
let dimension1 = entity.dimension[1];
|
||||||
|
let spawn_dimensions_scale = entity.spawn_dimensions_scale;
|
||||||
|
|
||||||
let loot_table = if let Some(table) = &entity.loot_table {
|
let loot_table = if let Some(table) = &entity.loot_table {
|
||||||
let table_tokens = table.to_token_stream();
|
let table_tokens = table.to_token_stream();
|
||||||
@ -182,6 +191,9 @@ impl ToTokens for NamedEntityType<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let experience_reward = entity.experience_reward.unwrap_or(0);
|
let experience_reward = entity.experience_reward.unwrap_or(0);
|
||||||
|
let client_tracking_range = entity.client_tracking_range;
|
||||||
|
let update_interval = entity.update_interval;
|
||||||
|
let track_deltas = entity.track_deltas;
|
||||||
|
|
||||||
tokens.extend(quote! {
|
tokens.extend(quote! {
|
||||||
EntityType {
|
EntityType {
|
||||||
@ -197,9 +209,13 @@ impl ToTokens for NamedEntityType<'_> {
|
|||||||
fire_immune: #fire_immune,
|
fire_immune: #fire_immune,
|
||||||
category: &#spawn_category,
|
category: &#spawn_category,
|
||||||
can_spawn_far_from_player: #can_spawn_far_from_player,
|
can_spawn_far_from_player: #can_spawn_far_from_player,
|
||||||
|
client_tracking_range: #client_tracking_range,
|
||||||
|
update_interval: #update_interval,
|
||||||
|
track_deltas: #track_deltas,
|
||||||
loot_table: #loot_table,
|
loot_table: #loot_table,
|
||||||
dimension: [#dimension0, #dimension1], // Correctly construct the array
|
dimension: [#dimension0, #dimension1], // Correctly construct the array
|
||||||
eye_height: #eye_height,
|
eye_height: #eye_height,
|
||||||
|
spawn_dimensions_scale: #spawn_dimensions_scale,
|
||||||
spawn_restriction: #spawn_restriction,
|
spawn_restriction: #spawn_restriction,
|
||||||
resource_name: #name,
|
resource_name: #name,
|
||||||
}
|
}
|
||||||
@ -250,6 +266,8 @@ pub fn build() -> TokenStream {
|
|||||||
use crate::sound::Sound;
|
use crate::sound::Sound;
|
||||||
use pumpkin_util::loot_table::*;
|
use pumpkin_util::loot_table::*;
|
||||||
use pumpkin_util::HeightMap;
|
use pumpkin_util::HeightMap;
|
||||||
|
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -266,9 +284,13 @@ pub fn build() -> TokenStream {
|
|||||||
pub fire_immune: bool,
|
pub fire_immune: bool,
|
||||||
pub category: &'static MobCategory,
|
pub category: &'static MobCategory,
|
||||||
pub can_spawn_far_from_player: bool,
|
pub can_spawn_far_from_player: bool,
|
||||||
|
pub client_tracking_range: u32,
|
||||||
|
pub update_interval: u32,
|
||||||
|
pub track_deltas: bool,
|
||||||
pub loot_table: Option<LootTable>,
|
pub loot_table: Option<LootTable>,
|
||||||
pub dimension: [f32; 2],
|
pub dimension: [f32; 2],
|
||||||
pub eye_height: f32,
|
pub eye_height: f32,
|
||||||
|
pub spawn_dimensions_scale: f32,
|
||||||
pub spawn_restriction: SpawnRestriction,
|
pub spawn_restriction: SpawnRestriction,
|
||||||
pub resource_name: &'static str,
|
pub resource_name: &'static str,
|
||||||
}
|
}
|
||||||
@ -423,6 +445,31 @@ pub fn build() -> TokenStream {
|
|||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn width(&self) -> f32 {
|
||||||
|
self.dimension[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn height(&self) -> f32 {
|
||||||
|
self.dimension[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_spawn_bounding_box(&self, x: f64, y: f64, z: f64) -> BoundingBox {
|
||||||
|
let half_width = f64::from(self.spawn_dimensions_scale * self.dimension[0] / 2.0);
|
||||||
|
let height = f64::from(self.spawn_dimensions_scale * self.dimension[1]);
|
||||||
|
BoundingBox::new(
|
||||||
|
Vector3::new(x - half_width, y, z - half_width),
|
||||||
|
Vector3::new(x + half_width, y + height, z + half_width),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_spawn_aabb(&self, x: f64, y: f64, z: f64) -> BoundingBox {
|
||||||
|
self.get_spawn_bounding_box(x, y, z)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IDSetContent for EntityType {
|
impl IDSetContent for EntityType {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user