fix: chunk leak

This commit is contained in:
Alexander Medvedev 2026-08-16 14:32:10 +02:00
parent c13c515081
commit a72531a7c2
65 changed files with 991 additions and 655 deletions

283
Cargo.lock generated
View File

@ -544,9 +544,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.2" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -1602,9 +1602,9 @@ dependencies = [
[[package]] [[package]]
name = "error-code" name = "error-code"
version = "3.3.2" version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
[[package]] [[package]]
name = "event-listener" name = "event-listener"
@ -1675,9 +1675,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.10" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -1732,9 +1732,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1747,9 +1747,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -1757,15 +1757,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -1774,38 +1774,38 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.3",
] ]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1840,18 +1840,6 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi 5.3.0",
"wasip2",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.4.3" version = "0.4.3"
@ -1860,7 +1848,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi 6.0.0", "r-efi",
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
@ -2058,9 +2046,9 @@ dependencies = [
[[package]] [[package]]
name = "http-body-util" name = "http-body-util"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -2155,9 +2143,9 @@ dependencies = [
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"potential_utf", "potential_utf",
@ -2169,9 +2157,9 @@ dependencies = [
[[package]] [[package]]
name = "icu_locale_core" name = "icu_locale_core"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"litemap", "litemap",
@ -2182,9 +2170,9 @@ dependencies = [
[[package]] [[package]]
name = "icu_normalizer" name = "icu_normalizer"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [ dependencies = [
"icu_collections", "icu_collections",
"icu_normalizer_data", "icu_normalizer_data",
@ -2196,16 +2184,17 @@ dependencies = [
[[package]] [[package]]
name = "icu_normalizer_data" name = "icu_normalizer_data"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]] [[package]]
name = "icu_properties" name = "icu_properties"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [ dependencies = [
"displaydoc",
"icu_collections", "icu_collections",
"icu_locale_core", "icu_locale_core",
"icu_properties_data", "icu_properties_data",
@ -2216,15 +2205,15 @@ dependencies = [
[[package]] [[package]]
name = "icu_properties_data" name = "icu_properties_data"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]] [[package]]
name = "icu_provider" name = "icu_provider"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"icu_locale_core", "icu_locale_core",
@ -2289,9 +2278,9 @@ dependencies = [
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.11.4" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
"inotify-sys", "inotify-sys",
@ -2470,9 +2459,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.19" 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 = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -2485,9 +2474,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" 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 = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]] [[package]]
name = "litrs" name = "litrs"
@ -2773,9 +2762,9 @@ dependencies = [
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -2993,9 +2982,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.8" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
dependencies = [ dependencies = [
"memchr", "memchr",
"ucd-trie", "ucd-trie",
@ -3003,9 +2992,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.8.8" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@ -3013,9 +3002,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.8.8" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@ -3026,9 +3015,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.8.8" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496"
dependencies = [ dependencies = [
"pest", "pest",
] ]
@ -3134,9 +3123,9 @@ dependencies = [
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.33" version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]] [[package]]
name = "png" name = "png"
@ -3194,9 +3183,9 @@ dependencies = [
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [ dependencies = [
"zerovec", "zerovec",
] ]
@ -3207,15 +3196,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.4.1" version = "1.4.1"
@ -3414,7 +3394,7 @@ dependencies = [
"pumpkin-protocol", "pumpkin-protocol",
"pumpkin-util", "pumpkin-util",
"pumpkin-world", "pumpkin-world",
"rand 0.10.2", "rand",
"rayon", "rayon",
"rsa", "rsa",
"rustc-hash", "rustc-hash",
@ -3503,7 +3483,7 @@ dependencies = [
"phf", "phf",
"pumpkin-nbt", "pumpkin-nbt",
"pumpkin-util", "pumpkin-util",
"rand 0.10.2", "rand",
"serde", "serde",
] ]
@ -3513,7 +3493,7 @@ version = "0.1.0-dev+26.2-26.40"
dependencies = [ dependencies = [
"clap", "clap",
"colored", "colored",
"rand 0.9.5", "rand",
"tokio", "tokio",
] ]
@ -3525,7 +3505,7 @@ dependencies = [
"pumpkin-protocol", "pumpkin-protocol",
"pumpkin-util", "pumpkin-util",
"pumpkin-world", "pumpkin-world",
"rand 0.10.2", "rand",
"thiserror 2.0.20", "thiserror 2.0.20",
"tokio", "tokio",
"tracing", "tracing",
@ -3566,7 +3546,7 @@ dependencies = [
"serde_json", "serde_json",
"tracing", "tracing",
"tracing-serde-structured", "tracing-serde-structured",
"wit-bindgen 0.60.0", "wit-bindgen",
] ]
[[package]] [[package]]
@ -3640,7 +3620,7 @@ dependencies = [
"pumpkin-data", "pumpkin-data",
"pumpkin-nbt", "pumpkin-nbt",
"pumpkin-util", "pumpkin-util",
"rand 0.10.2", "rand",
"rayon", "rayon",
"rustc-hash", "rustc-hash",
"ruzstd", "ruzstd",
@ -3686,12 +3666,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "6.0.0" version = "6.0.0"
@ -3707,16 +3681,6 @@ dependencies = [
"ptr_meta", "ptr_meta",
] ]
[[package]]
name = "rand"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [
"rand_chacha",
"rand_core 0.9.5",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.10.2" version = "0.10.2"
@ -3728,16 +3692,6 @@ dependencies = [
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.6.4"
@ -3747,15 +3701,6 @@ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
] ]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.10.1" version = "0.10.1"
@ -3960,7 +3905,7 @@ dependencies = [
"bytes", "bytes",
"hex", "hex",
"log", "log",
"rand 0.10.2", "rand",
"rcgen", "rcgen",
"ring", "ring",
"rtc-datachannel", "rtc-datachannel",
@ -4017,7 +3962,7 @@ dependencies = [
"log", "log",
"p256", "p256",
"p384 0.13.1", "p384 0.13.1",
"rand 0.10.2", "rand",
"rand_core 0.6.4", "rand_core 0.6.4",
"rcgen", "rcgen",
"ring", "ring",
@ -4041,7 +3986,7 @@ dependencies = [
"bytes", "bytes",
"crc", "crc",
"log", "log",
"rand 0.10.2", "rand",
"rtc-mdns", "rtc-mdns",
"rtc-shared", "rtc-shared",
"rtc-stun", "rtc-stun",
@ -4058,7 +4003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66ea5650a91773d10e4d98729f6fe256681ec1314c7ded5839fa34867305f79" checksum = "f66ea5650a91773d10e4d98729f6fe256681ec1314c7ded5839fa34867305f79"
dependencies = [ dependencies = [
"log", "log",
"rand 0.10.2", "rand",
"rtc-interceptor-derive", "rtc-interceptor-derive",
"rtc-rtcp", "rtc-rtcp",
"rtc-rtp", "rtc-rtp",
@ -4098,7 +4043,7 @@ checksum = "4fbc2f460a321eccdc0907819f8fc7027036edcbd94620c3328c1aefb070dfe5"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"rand 0.10.2", "rand",
"rtc-rtp", "rtc-rtp",
"rtc-shared", "rtc-shared",
"thiserror 2.0.20", "thiserror 2.0.20",
@ -4122,7 +4067,7 @@ checksum = "aac1a05add837d45d562f48bc769c9b264440741aab319fed48ce6a696e6aa0c"
dependencies = [ dependencies = [
"bytes", "bytes",
"memchr", "memchr",
"rand 0.10.2", "rand",
"rtc-shared", "rtc-shared",
"serde", "serde",
] ]
@ -4136,7 +4081,7 @@ dependencies = [
"bytes", "bytes",
"crc32c", "crc32c",
"log", "log",
"rand 0.10.2", "rand",
"rtc-shared", "rtc-shared",
"rustc-hash", "rustc-hash",
"slab", "slab",
@ -4149,7 +4094,7 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0379bb552d4813e042d376db99f2005038017fc7864676fc835334b810db015" checksum = "a0379bb552d4813e042d376db99f2005038017fc7864676fc835334b810db015"
dependencies = [ dependencies = [
"rand 0.10.2", "rand",
"rtc-shared", "rtc-shared",
"url", "url",
] ]
@ -4166,7 +4111,7 @@ dependencies = [
"bytes", "bytes",
"nix", "nix",
"p256", "p256",
"rand 0.10.2", "rand",
"rcgen", "rcgen",
"sec1 0.7.3", "sec1 0.7.3",
"serde", "serde",
@ -4206,7 +4151,7 @@ dependencies = [
"crc", "crc",
"lazy_static", "lazy_static",
"md-5", "md-5",
"rand 0.10.2", "rand",
"ring", "ring",
"rtc-shared", "rtc-shared",
"sansio", "sansio",
@ -4316,9 +4261,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.13" version = "0.103.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@ -4872,9 +4817,9 @@ dependencies = [
[[package]] [[package]]
name = "tinystr" name = "tinystr"
version = "0.8.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"zerovec", "zerovec",
@ -5295,9 +5240,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.24.0" version = "1.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"js-sys", "js-sys",
@ -5343,15 +5288,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.127" version = "0.2.127"
@ -5464,17 +5400,6 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "wasmparser"
version = "0.255.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b"
dependencies = [
"bitflags 2.13.1",
"indexmap",
"semver",
]
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.256.0" version = "0.256.0"
@ -5740,7 +5665,7 @@ dependencies = [
"cfg-if", "cfg-if",
"futures", "futures",
"io-lifetimes 3.0.1", "io-lifetimes 3.0.1",
"rand 0.10.2", "rand",
"rustix", "rustix",
"thiserror 2.0.20", "thiserror 2.0.20",
"tokio", "tokio",
@ -6142,12 +6067,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.60.0" version = "0.60.0"
@ -6220,15 +6139,15 @@ dependencies = [
[[package]] [[package]]
name = "wit-encoder" name = "wit-encoder"
version = "0.255.0" version = "0.256.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cebd2d14c3296dada506c67c1ee5c9440811a2fe88a83367a606ccbd30ebfde" checksum = "b8085a1e724fa8c48f9f5489b9d8f121eb9fc2554ebcdaf6936256d854b24390"
dependencies = [ dependencies = [
"id-arena", "id-arena",
"pretty_assertions", "pretty_assertions",
"semver", "semver",
"serde", "serde",
"wit-parser 0.255.0", "wit-parser 0.256.0",
] ]
[[package]] [[package]]
@ -6271,9 +6190,9 @@ dependencies = [
[[package]] [[package]]
name = "wit-parser" name = "wit-parser"
version = "0.255.0" version = "0.256.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5f6371fc71f15730b756c1dea3562a67adab1a7e519c4ca010173d883695bb" checksum = "fa073dadefba859cb03f689a0c2e3efc51f883bf86b907eaa9709bfd9e3a00a9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@ -6285,7 +6204,7 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"unicode-ident", "unicode-ident",
"wasmparser 0.255.0", "wasmparser 0.256.0",
] ]
[[package]] [[package]]
@ -6301,9 +6220,9 @@ dependencies = [
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]] [[package]]
name = "x25519-dalek" name = "x25519-dalek"
@ -6460,9 +6379,9 @@ dependencies = [
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.4" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"yoke", "yoke",
@ -6471,9 +6390,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.6" version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",
@ -6482,13 +6401,13 @@ dependencies = [
[[package]] [[package]]
name = "zerovec-derive" name = "zerovec-derive"
version = "0.11.3" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.3",
] ]
[[package]] [[package]]

View File

@ -12,7 +12,7 @@ use recipe::RecipeConfig;
use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::path::PathBuf; use std::path::PathBuf;
use std::{fs, num::NonZeroU8, path::Path}; use std::{fs, num::NonZero, path::Path};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
/// Fun and experimental configuration options. /// Fun and experimental configuration options.
@ -87,8 +87,8 @@ impl LoadConfiguration for PumpkinConfig {
self.basic.validate(); self.basic.validate();
self.advanced.validate(); self.advanced.validate();
let min_vd = NonZeroU8::MIN; let min_vd = NonZero::<u8>::MIN;
let Some(max_vd) = NonZeroU8::new(64) else { let Some(max_vd) = NonZero::new(64) else {
return; return;
}; };

View File

@ -1,7 +1,7 @@
use crate::CompressionConfig; use crate::CompressionConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::num::NonZeroU8; use std::num::NonZero;
use std::path::PathBuf; use std::path::PathBuf;
/// Configuration for Bedrock authentication. /// Configuration for Bedrock authentication.
@ -71,9 +71,9 @@ pub struct BedrockConfig {
/// The maximum number of players allowed on the server. Specifying `0` disables the limit. /// The maximum number of players allowed on the server. Specifying `0` disables the limit.
pub max_players: u32, pub max_players: u32,
/// The maximum view distance for players. /// The maximum view distance for players.
pub view_distance: NonZeroU8, pub view_distance: NonZero<u8>,
/// The maximum simulated view distance. /// The maximum simulated view distance.
pub simulation_distance: NonZeroU8, pub simulation_distance: NonZero<u8>,
/// Bedrock Edition packet compression settings. /// Bedrock Edition packet compression settings.
pub compression: CompressionConfig, pub compression: CompressionConfig,
/// Message of the Day; the server's description displayed on the status screen. /// Message of the Day; the server's description displayed on the status screen.
@ -88,8 +88,8 @@ pub struct BedrockConfig {
impl Default for BedrockConfig { impl Default for BedrockConfig {
fn default() -> Self { fn default() -> Self {
let view_distance = NonZeroU8::new(16).unwrap_or(NonZeroU8::MIN); let view_distance = NonZero::new(16).unwrap_or(NonZero::<u8>::MIN);
let simulation_distance = NonZeroU8::new(10).unwrap_or(NonZeroU8::MIN); let simulation_distance = NonZero::new(10).unwrap_or(NonZero::<u8>::MIN);
Self { Self {
enabled: true, enabled: true,
online_mode: true, online_mode: true,

View File

@ -1,7 +1,7 @@
use crate::{AuthenticationConfig, CompressionConfig}; use crate::{AuthenticationConfig, CompressionConfig};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::num::NonZeroU8; use std::num::NonZero;
/// Configuration for Java Edition client connections. /// Configuration for Java Edition client connections.
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
@ -18,9 +18,9 @@ pub struct JavaConfig {
/// The maximum number of players allowed on the server. Specifying `0` disables the limit. /// The maximum number of players allowed on the server. Specifying `0` disables the limit.
pub max_players: u32, pub max_players: u32,
/// The maximum view distance for players. /// The maximum view distance for players.
pub view_distance: NonZeroU8, pub view_distance: NonZero<u8>,
/// The maximum simulated view distance. /// The maximum simulated view distance.
pub simulation_distance: NonZeroU8, pub simulation_distance: NonZero<u8>,
/// Java Edition packet compression settings. /// Java Edition packet compression settings.
pub compression: CompressionConfig, pub compression: CompressionConfig,
/// Message of the Day; the server's description displayed on the status screen. /// Message of the Day; the server's description displayed on the status screen.
@ -34,8 +34,8 @@ impl Default for JavaConfig {
let address = "0.0.0.0:25565" let address = "0.0.0.0:25565"
.parse() .parse()
.unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], 25565))); .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], 25565)));
let view_distance = NonZeroU8::new(16).unwrap_or(NonZeroU8::MIN); let view_distance = NonZero::new(16).unwrap_or(NonZero::<u8>::MIN);
let simulation_distance = NonZeroU8::new(10).unwrap_or(NonZeroU8::MIN); let simulation_distance = NonZero::new(10).unwrap_or(NonZero::<u8>::MIN);
Self { Self {
enabled: true, enabled: true,
address, address,

View File

@ -21,7 +21,7 @@ pub struct TrapezoidHeightProvider {
pub struct VeryBiasedToBottomHeightProvider { pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZeroU32>, pub inner: Option<std::num::NonZero<u32>>,
} }
pub struct CaveCarverConfig { pub struct CaveCarverConfig {
pub horizontal_radius_multiplier: FloatProvider, pub horizontal_radius_multiplier: FloatProvider,

View File

@ -6,7 +6,7 @@ use crate::dimension::Dimension;
use pumpkin_util::random::RandomDeriver; use pumpkin_util::random::RandomDeriver;
use pumpkin_util::y_offset::Absolute; use pumpkin_util::y_offset::Absolute;
use pumpkin_util::y_offset::YOffset; use pumpkin_util::y_offset::YOffset;
use std::{cell::RefCell, num::NonZeroUsize}; use std::cell::RefCell;
pub struct GenerationSettings { pub struct GenerationSettings {
pub aquifers_enabled: bool, pub aquifers_enabled: bool,
pub ore_veins_enabled: bool, pub ore_veins_enabled: bool,

View File

@ -6359,7 +6359,7 @@ fn build_placed_features()
height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider {
min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }), min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }),
max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }), max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }),
inner: std::num::NonZeroU32::new(8u32), inner: std::num::NonZero::new(8u32),
}), }),
}), }),
PlacementModifier::Biome(BiomePlacementModifier), PlacementModifier::Biome(BiomePlacementModifier),
@ -6381,7 +6381,7 @@ fn build_placed_features()
height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider {
min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }), min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }),
max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }), max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }),
inner: std::num::NonZeroU32::new(8u32), inner: std::num::NonZero::new(8u32),
}), }),
}), }),
PlacementModifier::Biome(BiomePlacementModifier), PlacementModifier::Biome(BiomePlacementModifier),

View File

@ -552,6 +552,42 @@ impl TrackedData {
v26_1: 16u8, v26_1: 16u8,
v26_2: 16u8, v26_2: 16u8,
}; };
pub const CAT_COLLAR_COLOR: TrackedId = TrackedId {
v1_21: 22u8,
v1_21_2: 22u8,
v1_21_4: 22u8,
v1_21_5: 22u8,
v1_21_6: 22u8,
v1_21_7: 22u8,
v1_21_9: 22u8,
v1_21_11: 22u8,
v26_1: 23u8,
v26_2: 23u8,
};
pub const CAT_COLLAR_COLOR_ID: TrackedId = TrackedId {
v1_21: 22u8,
v1_21_2: 22u8,
v1_21_4: 22u8,
v1_21_5: 22u8,
v1_21_6: 22u8,
v1_21_7: 22u8,
v1_21_9: 22u8,
v1_21_11: 22u8,
v26_1: 23u8,
v26_2: 23u8,
};
pub const CAT_SOUND_VARIANT_ID: TrackedId = TrackedId {
v1_21: 255u8,
v1_21_2: 255u8,
v1_21_4: 255u8,
v1_21_5: 23u8,
v1_21_6: 23u8,
v1_21_7: 23u8,
v1_21_9: 23u8,
v1_21_11: 23u8,
v26_1: 24u8,
v26_2: 24u8,
};
pub const CAT_VARIANT: TrackedId = TrackedId { pub const CAT_VARIANT: TrackedId = TrackedId {
v1_21: 19u8, v1_21: 19u8,
v1_21_2: 19u8, v1_21_2: 19u8,
@ -561,8 +597,20 @@ impl TrackedData {
v1_21_7: 19u8, v1_21_7: 19u8,
v1_21_9: 19u8, v1_21_9: 19u8,
v1_21_11: 19u8, v1_21_11: 19u8,
v26_1: 255u8, v26_1: 20u8,
v26_2: 255u8, v26_2: 20u8,
};
pub const CAT_VARIANT_ID: TrackedId = TrackedId {
v1_21: 19u8,
v1_21_2: 19u8,
v1_21_4: 19u8,
v1_21_5: 19u8,
v1_21_6: 19u8,
v1_21_7: 19u8,
v1_21_9: 19u8,
v1_21_11: 19u8,
v26_1: 20u8,
v26_2: 20u8,
}; };
pub const CAUGHT_FISH: TrackedId = TrackedId { pub const CAUGHT_FISH: TrackedId = TrackedId {
v1_21: 9u8, v1_21: 9u8,
@ -1326,11 +1374,11 @@ impl TrackedData {
v1_21_4: 21u8, v1_21_4: 21u8,
v1_21_5: 21u8, v1_21_5: 21u8,
v1_21_6: 21u8, v1_21_6: 21u8,
v1_21_7: 255u8, v1_21_7: 21u8,
v1_21_9: 21u8, v1_21_9: 21u8,
v1_21_11: 21u8, v1_21_11: 21u8,
v26_1: 255u8, v26_1: 22u8,
v26_2: 255u8, v26_2: 22u8,
}; };
pub const HEAD_POSE: TrackedId = TrackedId { pub const HEAD_POSE: TrackedId = TrackedId {
v1_21: 255u8, v1_21: 255u8,
@ -1830,11 +1878,11 @@ impl TrackedData {
v1_21_4: 20u8, v1_21_4: 20u8,
v1_21_5: 20u8, v1_21_5: 20u8,
v1_21_6: 20u8, v1_21_6: 20u8,
v1_21_7: 255u8, v1_21_7: 20u8,
v1_21_9: 20u8, v1_21_9: 20u8,
v1_21_11: 20u8, v1_21_11: 20u8,
v26_1: 255u8, v26_1: 21u8,
v26_2: 255u8, v26_2: 21u8,
}; };
pub const IRON_GOLEM_FLAGS: TrackedId = TrackedId { pub const IRON_GOLEM_FLAGS: TrackedId = TrackedId {
v1_21: 16u8, v1_21: 16u8,
@ -1933,14 +1981,14 @@ impl TrackedData {
v26_2: 18u8, v26_2: 18u8,
}; };
pub const IS_LYING: TrackedId = TrackedId { pub const IS_LYING: TrackedId = TrackedId {
v1_21: 255u8, v1_21: 20u8,
v1_21_2: 255u8, v1_21_2: 20u8,
v1_21_4: 255u8, v1_21_4: 20u8,
v1_21_5: 255u8, v1_21_5: 20u8,
v1_21_6: 255u8, v1_21_6: 20u8,
v1_21_7: 255u8, v1_21_7: 20u8,
v1_21_9: 255u8, v1_21_9: 20u8,
v1_21_11: 255u8, v1_21_11: 20u8,
v26_1: 21u8, v26_1: 21u8,
v26_2: 21u8, v26_2: 21u8,
}; };
@ -2701,14 +2749,14 @@ impl TrackedData {
v26_2: 8u8, v26_2: 8u8,
}; };
pub const RELAX_STATE_ONE: TrackedId = TrackedId { pub const RELAX_STATE_ONE: TrackedId = TrackedId {
v1_21: 255u8, v1_21: 21u8,
v1_21_2: 255u8, v1_21_2: 21u8,
v1_21_4: 255u8, v1_21_4: 21u8,
v1_21_5: 255u8, v1_21_5: 21u8,
v1_21_6: 255u8, v1_21_6: 21u8,
v1_21_7: 255u8, v1_21_7: 21u8,
v1_21_9: 255u8, v1_21_9: 21u8,
v1_21_11: 255u8, v1_21_11: 21u8,
v26_1: 22u8, v26_1: 22u8,
v26_2: 22u8, v26_2: 22u8,
}; };
@ -3090,21 +3138,21 @@ impl TrackedData {
v1_21_4: 255u8, v1_21_4: 255u8,
v1_21_5: 23u8, v1_21_5: 23u8,
v1_21_6: 23u8, v1_21_6: 23u8,
v1_21_7: 255u8, v1_21_7: 23u8,
v1_21_9: 23u8, v1_21_9: 23u8,
v1_21_11: 23u8, v1_21_11: 23u8,
v26_1: 255u8, v26_1: 24u8,
v26_2: 255u8, v26_2: 24u8,
}; };
pub const SOUND_VARIANT_ID: TrackedId = TrackedId { pub const SOUND_VARIANT_ID: TrackedId = TrackedId {
v1_21: 255u8, v1_21: 255u8,
v1_21_2: 255u8, v1_21_2: 255u8,
v1_21_4: 255u8, v1_21_4: 255u8,
v1_21_5: 255u8, v1_21_5: 23u8,
v1_21_6: 255u8, v1_21_6: 23u8,
v1_21_7: 255u8, v1_21_7: 23u8,
v1_21_9: 255u8, v1_21_9: 23u8,
v1_21_11: 255u8, v1_21_11: 23u8,
v26_1: 24u8, v26_1: 24u8,
v26_2: 24u8, v26_2: 24u8,
}; };
@ -3960,6 +4008,42 @@ impl TrackedData {
v26_1: 20u8, v26_1: 20u8,
v26_2: 20u8, v26_2: 20u8,
}; };
pub const WOLF_COLLAR_COLOR: TrackedId = TrackedId {
v1_21: 20u8,
v1_21_2: 20u8,
v1_21_4: 20u8,
v1_21_5: 20u8,
v1_21_6: 20u8,
v1_21_7: 20u8,
v1_21_9: 20u8,
v1_21_11: 20u8,
v26_1: 21u8,
v26_2: 21u8,
};
pub const WOLF_COLLAR_COLOR_ID: TrackedId = TrackedId {
v1_21: 20u8,
v1_21_2: 20u8,
v1_21_4: 20u8,
v1_21_5: 20u8,
v1_21_6: 20u8,
v1_21_7: 20u8,
v1_21_9: 20u8,
v1_21_11: 20u8,
v26_1: 21u8,
v26_2: 21u8,
};
pub const WOLF_SOUND_VARIANT_ID: TrackedId = TrackedId {
v1_21: 255u8,
v1_21_2: 255u8,
v1_21_4: 255u8,
v1_21_5: 23u8,
v1_21_6: 23u8,
v1_21_7: 23u8,
v1_21_9: 23u8,
v1_21_11: 23u8,
v26_1: 24u8,
v26_2: 24u8,
};
pub const WOLF_VARIANT_ID: TrackedId = TrackedId { pub const WOLF_VARIANT_ID: TrackedId = TrackedId {
v1_21: 22u8, v1_21: 22u8,
v1_21_2: 22u8, v1_21_2: 22u8,

View File

@ -15,7 +15,7 @@ use pumpkin_util::GameMode;
use rand; use rand;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::num::NonZeroI32; use std::num::NonZero;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
mod categories; mod categories;
@ -42,7 +42,7 @@ pub struct ItemStack {
// unique ID for Bedrock network; don't serialize // unique ID for Bedrock network; don't serialize
// Should always be a positive value for non-empty stacks // Should always be a positive value for non-empty stacks
pub uid: NonZeroI32, pub uid: NonZero<i32>,
} }
// impl Hash for ItemStack { // impl Hash for ItemStack {
@ -71,14 +71,14 @@ impl ItemStackIdGenerator {
} }
} }
pub fn next_id(&self) -> NonZeroI32 { pub fn next_id(&self) -> NonZero<i32> {
// Wraps on overflow, which is what we want. // Wraps on overflow, which is what we want.
let value = self.counter.fetch_add(1, Ordering::Relaxed); let value = self.counter.fetch_add(1, Ordering::Relaxed);
// Negative values are invalid; cycle through the positives // Negative values are invalid; cycle through the positives
let masked = value & 0x7FFFFFFF; let masked = value & 0x7FFFFFFF;
if let Some(id) = NonZeroI32::new(masked as i32) { if let Some(id) = NonZero::new(masked as i32) {
id id
} else { } else {
// If we fetched 0 or 0x80000000, that's masked out as 0 // If we fetched 0 or 0x80000000, that's masked out as 0
@ -133,7 +133,7 @@ impl ItemStack {
item, item,
patch: Vec::new(), patch: Vec::new(),
uid: match NonZeroI32::new(1) { uid: match NonZero::new(1) {
Some(v) => v, Some(v) => v,
None => panic!("1 is non-zero"), None => panic!("1 is non-zero"),
}, },
@ -211,7 +211,7 @@ impl ItemStack {
item: &Item::AIR, item: &Item::AIR,
patch: Vec::new(), patch: Vec::new(),
uid: NonZeroI32::MIN, // white lie - Bedrock `uid` is never sent if the stack is empty uid: NonZero::<i32>::MIN, // white lie - Bedrock `uid` is never sent if the stack is empty
}; };
#[must_use] #[must_use]

View File

@ -1,5 +1,5 @@
use std::io::{Error, ErrorKind, Read, Write}; use std::io::{Error, ErrorKind, Read, Write};
use std::num::NonZeroI32; use std::num::NonZero;
use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping}; use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping};
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
@ -155,7 +155,7 @@ pub struct ItemStackWrapper {
pub place_on_blocks: Vec<String>, pub place_on_blocks: Vec<String>,
pub destroy_blocks: Vec<String>, pub destroy_blocks: Vec<String>,
pub shield_blocking_tick: i64, pub shield_blocking_tick: i64,
pub net_id: Option<NonZeroI32>, pub net_id: Option<NonZero<i32>>,
} }
impl PacketWrite for ItemStackWrapper { impl PacketWrite for ItemStackWrapper {
@ -192,7 +192,7 @@ impl PacketRead for ItemStackWrapper {
let has_net_id = bool::read(buf)?; let has_net_id = bool::read(buf)?;
let net_id = if has_net_id { let net_id = if has_net_id {
let stack_id = VarInt::read(buf)?; let stack_id = VarInt::read(buf)?;
NonZeroI32::new(stack_id.0) NonZero::new(stack_id.0)
} else { } else {
None None
}; };
@ -256,7 +256,7 @@ pub struct NetworkItemStackDescriptor {
pub aux_value: VarUInt, pub aux_value: VarUInt,
pub block_runtime_id: VarUInt, pub block_runtime_id: VarUInt,
pub extra_data: Vec<u8>, pub extra_data: Vec<u8>,
pub net_id: Option<NonZeroI32>, pub net_id: Option<NonZero<i32>>,
} }
impl PacketWrite for NetworkItemStackDescriptor { impl PacketWrite for NetworkItemStackDescriptor {
@ -290,7 +290,7 @@ impl PacketRead for NetworkItemStackDescriptor {
let has_net_id = bool::read(buf)?; let has_net_id = bool::read(buf)?;
let net_id = if has_net_id { let net_id = if has_net_id {
let stack_id = VarInt::read(buf)?; let stack_id = VarInt::read(buf)?;
NonZeroI32::new(stack_id.0) NonZero::new(stack_id.0)
} else { } else {
None None
}; };

View File

@ -1,6 +1,6 @@
use std::{ use std::{
io::{Error, ErrorKind, Read, Write}, io::{Error, ErrorKind, Read, Write},
num::NonZeroUsize, num::NonZero,
ops::Deref, ops::Deref,
}; };
@ -20,9 +20,7 @@ pub type VarIntType = i32;
pub struct VarInt(pub VarIntType); pub struct VarInt(pub VarIntType);
impl VarInt { impl VarInt {
/// The maximum number of bytes a `VarInt` can occupy. pub const MAX_SIZE: NonZero<usize> = NonZero::new(5).expect("5 is non-zero");
#[allow(clippy::unwrap_used)]
pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap();
#[must_use] #[must_use]
#[inline] #[inline]

View File

@ -1,6 +1,6 @@
use std::{ use std::{
io::{Error, Read, Write}, io::{Error, Read, Write},
num::NonZeroUsize, num::NonZero,
ops::Deref, ops::Deref,
}; };
@ -19,9 +19,7 @@ pub type VarLongType = i64;
pub struct VarLong(pub VarLongType); pub struct VarLong(pub VarLongType);
impl VarLong { impl VarLong {
/// The maximum number of bytes a `VarLong` can occupy. pub const MAX_SIZE: NonZero<usize> = NonZero::new(10).expect("10 is non-zero");
#[allow(clippy::unwrap_used)]
pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap();
#[must_use] #[must_use]
#[inline] #[inline]

View File

@ -1,6 +1,6 @@
use std::{ use std::{
io::{Error, ErrorKind, Read, Write}, io::{Error, ErrorKind, Read, Write},
num::NonZeroUsize, num::NonZero,
}; };
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
@ -19,9 +19,7 @@ pub type VarUIntType = u32;
pub struct VarUInt(pub VarUIntType); pub struct VarUInt(pub VarUIntType);
impl VarUInt { impl VarUInt {
/// The maximum number of bytes a `VarUInt` can occupy. pub const MAX_SIZE: NonZero<usize> = NonZero::new(5).expect("5 is non-zero");
#[allow(clippy::unwrap_used)]
pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap();
#[must_use] #[must_use]
#[inline] #[inline]

View File

@ -1,6 +1,6 @@
use std::{ use std::{
io::{Error, Read, Write}, io::{Error, Read, Write},
num::NonZeroUsize, num::NonZero,
ops::Deref, ops::Deref,
}; };
@ -19,9 +19,7 @@ pub type VarULongType = u64;
pub struct VarULong(pub VarULongType); pub struct VarULong(pub VarULongType);
impl VarULong { impl VarULong {
/// The maximum number of bytes a `VarULong` can occupy. pub const MAX_SIZE: NonZero<usize> = NonZero::new(10).expect("10 is non-zero");
#[allow(clippy::unwrap_used)]
pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap();
#[must_use] #[must_use]
#[inline] #[inline]

View File

@ -225,22 +225,40 @@ where
.map(|c| P::file_path(folder, &S::get_chunk_key(c))) .map(|c| P::file_path(folder, &S::get_chunk_key(c)))
.collect(); .collect();
let mut paths_to_evict = Vec::new();
{
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for path in paths { for path in paths {
if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) { if let std::collections::btree_map::Entry::Occupied(mut e) =
watchers.entry(path)
{
let count = e.get_mut(); let count = e.get_mut();
*count = count.saturating_sub(1); *count = count.saturating_sub(1);
if *count == 0 { if *count == 0 {
e.remove(); let (path, _) = e.remove_entry();
paths_to_evict.push(path);
} }
} }
} }
}
for path in paths_to_evict {
self.maybe_evict(&path).await;
}
}) })
} }
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
self.watchers.write().await.clear(); let paths: Vec<PathBuf> = {
let mut watchers = self.watchers.write().await;
let keys: Vec<_> = watchers.keys().cloned().collect();
watchers.clear();
keys
};
for path in paths {
self.maybe_evict(&path).await;
}
}) })
} }
@ -301,6 +319,9 @@ where
}; };
join!(forward, read); join!(forward, read);
// Evict if not watched and references are dropped
self.maybe_evict(&path).await;
} }
}); });

View File

@ -132,10 +132,14 @@ impl LevelChannel {
&& !level.should_save.load(SeqCst) && !level.should_save.load(SeqCst)
&& !level.shut_down_chunk_system.load(SeqCst) && !level.shut_down_chunk_system.load(SeqCst)
{ {
lock = self let (new_lock, timeout_res) = self
.notify .notify
.wait(lock) .wait_timeout(lock, std::time::Duration::from_secs(1))
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
lock = new_lock;
if timeout_res.timed_out() {
break;
}
} }
let mut ret = (None, None); let mut ret = (None, None);
swap(&mut ret, &mut *lock); swap(&mut ret, &mut *lock);

View File

@ -74,4 +74,46 @@ impl DAG {
*edge = self.edges.insert(Edge::new(to, *edge)); *edge = self.edges.insert(Edge::new(to, *edge));
} }
} }
pub fn drop_edge_chain(&mut self, mut head: EdgeKey) {
while !head.is_null() {
if let Some(edge) = self.edges.remove(head) {
head = edge.next;
} else {
break;
}
}
}
pub fn prune_edge_chain(&mut self, head: &mut EdgeKey) -> bool {
let mut cur_edge = *head;
let mut prev_edge = EdgeKey::null();
let mut change_head = None;
let mut has_valid_target = false;
while !cur_edge.is_null() {
let Some(edge) = self.edges.get(cur_edge) else {
break;
};
if self.nodes.contains_key(edge.to) {
prev_edge = cur_edge;
cur_edge = edge.next;
has_valid_target = true;
} else {
let next = edge.next;
self.edges.remove(cur_edge);
cur_edge = next;
if prev_edge.is_null() {
change_head = Some(next);
} else if let Some(prev) = self.edges.get_mut(prev_edge) {
prev.next = next;
}
}
}
if let Some(next) = change_head {
*head = next;
}
has_valid_target
}
} }

View File

@ -508,11 +508,17 @@ impl GenerationSchedule {
} }
} }
} }
if new_stage == StagedChunkEnum::None if new_stage == StagedChunkEnum::None {
&& holder.dependency_stage == StagedChunkEnum::None if holder.dependency_stage != StagedChunkEnum::None {
{ let has_valid_task = self.graph.prune_edge_chain(&mut holder.occupied_by);
if !has_valid_task {
holder.dependency_stage = StagedChunkEnum::None;
}
}
if holder.dependency_stage == StagedChunkEnum::None {
self.unload_chunks.insert(pos); self.unload_chunks.insert(pos);
} }
}
} else { } else {
if old_stage == StagedChunkEnum::None { if old_stage == StagedChunkEnum::None {
self.unload_chunks.remove(&pos); self.unload_chunks.remove(&pos);
@ -588,14 +594,120 @@ impl GenerationSchedule {
true true
} }
fn garbage_collect_dependencies(&mut self) { fn recompute_dependency_stages(&mut self) {
// Garbage collect stranded dependencies let mut required: HashMapType<ChunkPos, StagedChunkEnum> = HashMapType::default();
let mut stranded = Vec::new(); let mut worklist: Vec<(ChunkPos, StagedChunkEnum)> = self
for (pos, holder) in &self.chunk_map { .chunk_map
if holder.target_stage == StagedChunkEnum::None .iter()
&& holder.dependency_stage != StagedChunkEnum::None .filter(|(_, holder)| holder.target_stage != StagedChunkEnum::None)
.map(|(pos, holder)| (*pos, holder.target_stage))
.collect();
while let Some((pos, req)) = worklist.pop() {
let entry = required.entry(pos).or_insert(StagedChunkEnum::None);
if *entry >= req {
continue;
}
let start = *entry as u8 + 1;
*entry = req;
// Only expand the stages that were not already accounted for.
for i in start..=(req as u8) {
let stage = StagedChunkEnum::from(i);
let radius = stage.get_direct_radius();
if radius == 0 {
continue;
}
let dependencies = stage.get_direct_dependencies();
for dx in -radius..=radius {
for dz in -radius..=radius {
if dx == 0 && dz == 0 {
continue;
}
let neighbor = pos.add_raw(dx, dz);
worklist.push((neighbor, dependencies[dx.abs().max(dz.abs()) as usize]));
}
}
}
}
let mut nodes_to_drop = Vec::new();
let mut newly_unused = Vec::new();
for (pos, holder) in &mut self.chunk_map {
let new_dependency = required.get(pos).copied().unwrap_or(StagedChunkEnum::None);
if new_dependency >= holder.dependency_stage {
continue;
}
holder.dependency_stage = new_dependency;
let effective_target = holder.target_stage.max(new_dependency);
for i in (effective_target as usize + 1)..StagedChunkEnum::COUNT {
let task = holder.tasks[i];
if !task.is_null() {
nodes_to_drop.push((*pos, i, task));
}
}
if effective_target == StagedChunkEnum::None {
newly_unused.push(*pos);
}
}
self.unload_chunks.extend(newly_unused);
for (pos, index, task) in nodes_to_drop {
if self
.graph
.nodes
.get(task)
.is_some_and(|node| node.in_flight)
{ {
continue;
}
self.waiting_for_chunks.remove(&task);
self.drop_node(task);
if let Some(holder) = self.chunk_map.get_mut(&pos) {
holder.tasks[index] = NodeKey::null();
}
}
self.purge_dropped_queue_entries();
}
/// Drop heap entries whose node has been cancelled. They are skipped when popped,
/// but a saturated queue is never drained, so without this the heap keeps every
/// cancelled task of every chunk the player has flown past and `sort_queue` gets
/// slower on each level change.
fn purge_dropped_queue_entries(&mut self) {
if self.queue.is_empty() {
return;
}
let graph = &self.graph;
let tasks: Vec<_> = self
.queue
.drain()
.filter(|task| graph.nodes.contains_key(task.1))
.collect();
self.queue = BinaryHeap::from(tasks);
}
fn garbage_collect_dependencies(&mut self) {
self.recompute_dependency_stages();
// Garbage collect stranded dependencies and empty holders
let mut stranded = Vec::new();
let mut empty_holders = Vec::new();
for (pos, holder) in &self.chunk_map {
if holder.target_stage == StagedChunkEnum::None {
if holder.dependency_stage != StagedChunkEnum::None {
stranded.push(*pos); stranded.push(*pos);
} else if holder.current_stage == StagedChunkEnum::None
&& holder.chunk.is_none()
&& holder.occupied.is_null()
&& holder.tasks.iter().all(Key::is_null)
&& !holder.public
{
empty_holders.push(*pos);
}
} }
} }
@ -605,41 +717,19 @@ impl GenerationSchedule {
continue; continue;
} }
let mut cur_edge = holder.occupied_by; let has_valid_task = self.graph.prune_edge_chain(&mut holder.occupied_by);
let mut prev_edge = EdgeKey::null();
let mut change_head = None;
let mut has_valid_task = false;
while !cur_edge.is_null() {
let edge = self.graph.edges.get(cur_edge).expect("edge exists");
if self.graph.nodes.contains_key(edge.to) {
prev_edge = cur_edge;
cur_edge = edge.next;
has_valid_task = true;
} else {
let next = edge.next;
self.graph.edges.remove(cur_edge);
cur_edge = next;
if prev_edge.is_null() {
change_head = Some(next);
} else {
self.graph
.edges
.get_mut(prev_edge)
.expect("edge exists")
.next = next;
}
}
}
if let Some(next) = change_head {
holder.occupied_by = next;
}
if !has_valid_task { if !has_valid_task {
holder.dependency_stage = StagedChunkEnum::None; holder.dependency_stage = StagedChunkEnum::None;
self.unload_chunks.insert(pos); self.unload_chunks.insert(pos);
} }
} }
for pos in empty_holders {
if let Some(mut holder) = self.chunk_map.remove(&pos) {
self.graph.drop_edge_chain(holder.occupied_by);
holder.occupied_by = EdgeKey::null();
}
}
} }
fn process_unload_queue(&mut self) { fn process_unload_queue(&mut self) {
@ -671,6 +761,9 @@ impl GenerationSchedule {
} }
} }
self.graph.drop_edge_chain(holder.occupied_by);
holder.occupied_by = EdgeKey::null();
if holder.public { if holder.public {
self.public_chunk_map.remove(&pos); self.public_chunk_map.remove(&pos);
holder.public = false; holder.public = false;
@ -854,6 +947,15 @@ 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);
@ -1087,13 +1189,15 @@ impl GenerationSchedule {
} }
// 1. Get latest world state (player moves, etc) // 1. Get latest world state (player moves, etc)
self.resort_work(self.send_level.get()); if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
// Process unload queue periodically (every 1 second) to batch writes together // Process unload queue periodically (every 1 second) to batch writes together
// and act as a brief memory cache if a player walks back into the chunk. // and act as a brief memory cache if a player walks back into the chunk.
if !self.unload_chunks.is_empty() // This must run even when the queue is empty: `garbage_collect_dependencies`
&& self.last_unload.elapsed() >= std::time::Duration::from_secs(1) // is what puts stale dependency holders into the queue in the first place.
{ if self.last_unload.elapsed() >= std::time::Duration::from_secs(1) {
self.garbage_collect_dependencies(); self.garbage_collect_dependencies();
self.process_unload_queue(); self.process_unload_queue();
self.last_unload = std::time::Instant::now(); self.last_unload = std::time::Instant::now();
@ -1130,6 +1234,7 @@ impl GenerationSchedule {
self.receive_chunk(pos, data); self.receive_chunk(pos, data);
if self.resort_work(self.send_level.get()) { if self.resort_work(self.send_level.get()) {
// If world state changed, we MUST re-sort before continuing // If world state changed, we MUST re-sort before continuing
self.garbage_collect_dependencies();
self.queue.push(task); self.queue.push(task);
self.queue_dirty = true; self.queue_dirty = true;
break 'out2; break 'out2;
@ -1357,11 +1462,15 @@ impl GenerationSchedule {
match self.recv_chunk.recv_timeout(Duration::from_millis(5)) { match self.recv_chunk.recv_timeout(Duration::from_millis(5)) {
Ok((pos, data)) => { Ok((pos, data)) => {
self.receive_chunk(pos, data); self.receive_chunk(pos, data);
self.resort_work(self.send_level.get()); if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
} }
Err(crossfire::compat::RecvTimeoutError::Timeout) => { Err(crossfire::compat::RecvTimeoutError::Timeout) => {
// Periodically check LevelChannel for new requests // Periodically check LevelChannel for new requests
self.resort_work(self.send_level.get()); if self.resort_work(self.send_level.get()) {
self.garbage_collect_dependencies();
}
} }
Err(crossfire::compat::RecvTimeoutError::Disconnected) => break, Err(crossfire::compat::RecvTimeoutError::Disconnected) => break,
} }
@ -1381,7 +1490,9 @@ impl GenerationSchedule {
} }
debug_assert!(self.debug_check()); debug_assert!(self.debug_check());
debug_assert_eq!(self.running_task_count, 0); debug_assert_eq!(self.running_task_count, 0);
self.resort_work(self.send_level.wait_and_get(level)); if self.resort_work(self.send_level.wait_and_get(level)) {
self.garbage_collect_dependencies();
}
} }
if self.queue_dirty { if self.queue_dirty {
self.sort_queue(); self.sort_queue();
@ -1436,6 +1547,9 @@ impl GenerationSchedule {
nodes_to_drop.push(holder.occupied); nodes_to_drop.push(holder.occupied);
holder.occupied = NodeKey::null(); holder.occupied = NodeKey::null();
} }
self.graph.drop_edge_chain(holder.occupied_by);
holder.occupied_by = EdgeKey::null();
} }
for node_key in nodes_to_drop { for node_key in nodes_to_drop {

View File

@ -355,3 +355,64 @@ fn cancellation_path_decrements_in_degree() {
"The waiting task should have been dropped during cancellation" "The waiting task should have been dropped during cancellation"
); );
} }
#[test]
fn dag_drop_edge_chain_removes_all_edges() {
let mut graph = DAG::default();
let node1 = graph
.nodes
.insert(Node::new(ChunkPos::new(0, 0), StagedChunkEnum::Empty));
let node2 = graph
.nodes
.insert(Node::new(ChunkPos::new(1, 0), StagedChunkEnum::Empty));
let edge1 = graph.edges.insert(crate::chunk_system::dag::Edge::new(
node1,
crate::chunk_system::dag::EdgeKey::null(),
));
let edge2 = graph
.edges
.insert(crate::chunk_system::dag::Edge::new(node2, edge1));
assert_eq!(graph.edges.len(), 2);
graph.drop_edge_chain(edge2);
assert_eq!(graph.edges.len(), 0);
}
#[test]
fn dag_prune_edge_chain_removes_dead_target_edges() {
let mut graph = DAG::default();
let node_alive = graph
.nodes
.insert(Node::new(ChunkPos::new(0, 0), StagedChunkEnum::Empty));
let node_dead = graph
.nodes
.insert(Node::new(ChunkPos::new(1, 0), StagedChunkEnum::Empty));
let edge_alive = graph.edges.insert(crate::chunk_system::dag::Edge::new(
node_alive,
crate::chunk_system::dag::EdgeKey::null(),
));
let edge_dead = graph
.edges
.insert(crate::chunk_system::dag::Edge::new(node_dead, edge_alive));
// Remove node_dead from graph.nodes to simulate completed/dropped task
graph.nodes.remove(node_dead);
let mut head = edge_dead;
let has_valid_task = graph.prune_edge_chain(&mut head);
assert!(has_valid_task);
assert_eq!(head, edge_alive);
assert_eq!(graph.edges.len(), 1);
assert!(graph.edges.contains_key(edge_alive));
assert!(!graph.edges.contains_key(edge_dead));
// Now remove node_alive as well
graph.nodes.remove(node_alive);
let has_valid_task = graph.prune_edge_chain(&mut head);
assert!(!has_valid_task);
assert!(head.is_null());
assert_eq!(graph.edges.len(), 0);
}

View File

@ -38,7 +38,10 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: LightingEngineConfi
return false; return false;
} }
let engine = chunk.light_engine.lock().expect("Mutex poisoned"); let engine = chunk
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
// Scan for any complex lighting data // Scan for any complex lighting data
let has_complex_light = engine.sky_light.iter().any(|lc| match lc { let has_complex_light = engine.sky_light.iter().any(|lc| match lc {
@ -237,7 +240,14 @@ pub fn run_generation(
_settings: &GenerationSettings, _settings: &GenerationSettings,
) -> RecvChunk { ) -> RecvChunk {
let portal = level.world_portal.load_full(); let portal = level.world_portal.load_full();
let portal_ref = portal.as_deref().expect("Portal should be initialized"); let Some(portal_ref) = portal.as_deref() else {
error!("Chunk generation FAILED at {pos:?} ({stage:?}): World portal is not initialized");
return RecvChunk::GenerationFailure {
pos,
stage,
error: "World portal is not initialized".to_string(),
};
};
// Run generation with panic catching // Run generation with panic catching
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cache.advance(stage, &level.world_gen, portal_ref, &level.lighting_config); cache.advance(stage, &level.world_gen, portal_ref, &level.lighting_config);

View File

@ -1,16 +1,16 @@
use std::num::NonZeroU8; use std::num::NonZero;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cylindrical { pub struct Cylindrical {
pub center: Vector2<i32>, pub center: Vector2<i32>,
pub view_distance: NonZeroU8, pub view_distance: NonZero<u8>,
} }
impl Cylindrical { impl Cylindrical {
#[must_use] #[must_use]
pub const fn new(center: Vector2<i32>, view_distance: NonZeroU8) -> Self { pub const fn new(center: Vector2<i32>, view_distance: NonZero<u8>) -> Self {
Self { Self {
center, center,
view_distance, view_distance,
@ -100,17 +100,17 @@ impl Cylindrical {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::num::NonZeroU8; use std::num::NonZero;
use super::Cylindrical; use super::Cylindrical;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
#[test] #[test]
fn bounds() { fn bounds() {
let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap()); let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZero::new(1).unwrap());
for view_distance in 1..=32 { for view_distance in 1..=32 {
cylinder.view_distance = NonZeroU8::new(view_distance).unwrap(); cylinder.view_distance = NonZero::new(view_distance).unwrap();
for chunk in cylinder.all_chunks_within() { for chunk in cylinder.all_chunks_within() {
assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right()); assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right());
@ -130,10 +130,10 @@ mod test {
#[test] #[test]
fn all_chunks_within_capacity_estimation() { fn all_chunks_within_capacity_estimation() {
let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap()); let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZero::new(1).unwrap());
for distance in 1..=64 { for distance in 1..=64 {
cylinder.view_distance = NonZeroU8::new(distance).unwrap(); cylinder.view_distance = NonZero::new(distance).unwrap();
let chunks = cylinder.all_chunks_within(); let chunks = cylinder.all_chunks_within();
let estimated_capacity = ((distance as usize + 3).pow(2) * 3167) >> 10; let estimated_capacity = ((distance as usize + 3).pow(2) * 3167) >> 10;

View File

@ -29,7 +29,10 @@ impl RandomBooleanFeature {
} else { } else {
&self.feature_false &self.feature_false
}; };
feature.get().generate( let Some(feature) = feature.get() else {
return false;
};
feature.generate(
chunk, chunk,
block_registry, block_registry,
min_y, min_y,

View File

@ -32,7 +32,8 @@ impl RandomFeature {
if random.next_f32() >= feature.chance { if random.next_f32() >= feature.chance {
continue; continue;
} }
return feature.feature.get().generate( if let Some(f) = feature.feature.get() {
return f.generate(
chunk, chunk,
block_registry, block_registry,
min_y, min_y,
@ -42,7 +43,9 @@ impl RandomFeature {
pos, pos,
); );
} }
self.default.get().generate( }
self.default.get().is_some_and(|default_feature| {
default_feature.generate(
chunk, chunk,
block_registry, block_registry,
min_y, min_y,
@ -51,5 +54,6 @@ impl RandomFeature {
random, random,
pos, pos,
) )
})
} }
} }

View File

@ -28,11 +28,11 @@ pub enum PlacedFeatureWrapper {
} }
impl PlacedFeatureWrapper { impl PlacedFeatureWrapper {
#[allow(clippy::expect_used)] #[must_use]
pub fn get(&self) -> &PlacedFeature { pub fn get(&self) -> Option<&PlacedFeature> {
match self { match self {
Self::Named(name) => PLACED_FEATURES.get(name).expect("Unknown placed feature"), Self::Named(name) => PLACED_FEATURES.get(name),
Self::Direct(feature) => feature, Self::Direct(feature) => Some(feature),
} }
} }
} }

View File

@ -1,4 +1,4 @@
use std::num::NonZeroU32; use std::num::NonZero;
use pumpkin_util::{ use pumpkin_util::{
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -25,7 +25,7 @@ impl HeightProvider {
pub struct VeryBiasedToBottomHeightProvider { pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub inner: Option<NonZeroU32>, pub inner: Option<NonZero<u32>>,
} }
impl VeryBiasedToBottomHeightProvider { impl VeryBiasedToBottomHeightProvider {

View File

@ -628,6 +628,10 @@ impl Level {
self.chunk_watchers.shrink_to_fit(); self.chunk_watchers.shrink_to_fit();
} }
if self.loaded_chunks.capacity() - self.loaded_chunks.len() >= 4096 {
self.loaded_chunks.shrink_to_fit();
}
if self.loaded_entity_chunks.capacity() - self.loaded_entity_chunks.len() >= 4096 { if self.loaded_entity_chunks.capacity() - self.loaded_entity_chunks.len() >= 4096 {
self.loaded_entity_chunks.shrink_to_fit(); self.loaded_entity_chunks.shrink_to_fit();
} }

View File

@ -118,12 +118,12 @@ macro_rules! impl_block_entity_for_chest {
} }
fn take_loot_table(&self) -> Option<(String, i64)> { fn take_loot_table(&self) -> Option<(String, i64)> {
let mut guard = self.loot_table.lock().expect("Loot table mutex should not be poisoned"); let mut guard = self.loot_table.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take().map(|key| (key, self.loot_table_seed)) guard.take().map(|key| (key, self.loot_table_seed))
} }
fn has_loot_table(&self) -> bool { fn has_loot_table(&self) -> bool {
self.loot_table.lock().expect("Loot table mutex should not be poisoned").is_some() self.loot_table.lock().unwrap_or_else(std::sync::PoisonError::into_inner).is_some()
} }
} }
}; };

View File

@ -147,7 +147,7 @@ macro_rules! impl_cooking_block_entity_base {
.result .result
.id .id
.strip_prefix("minecraft:") .strip_prefix("minecraft:")
.expect("Recipe ID should have minecraft: prefix"), .unwrap_or(&recipe.result.id),
) && !is_top_items_empty ) && !is_top_items_empty
&& recipe_output_item.id == side_item_stack.item.id && recipe_output_item.id == side_item_stack.item.id
&& side_item_stack.item_count < max_count && side_item_stack.item_count < max_count
@ -172,7 +172,7 @@ macro_rules! impl_cooking_block_entity_base {
.result .result
.id .id
.strip_prefix("minecraft:") .strip_prefix("minecraft:")
.expect("Recipe ID should have minecraft: prefix"), .unwrap_or(&recipe.result.id),
) else { ) else {
return false; return false;
}; };

View File

@ -186,7 +186,7 @@ impl TargetSelector {
#[must_use] #[must_use]
pub fn includes_entities(&self) -> bool { pub fn includes_entities(&self) -> bool {
let player_type = EntityType::from_name("player").expect("entity type player must exist"); let player_type = &EntityType::PLAYER;
let mut includes_entities = self.base_includes_entities(); let mut includes_entities = self.base_includes_entities();
for condition in &self.conditions { for condition in &self.conditions {

View File

@ -3,11 +3,14 @@ use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders};
use crate::command::{ use crate::command::{
CommandSender, CommandSender,
args::{ args::{
Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, Arg, ArgumentConsumer, ConsumeResult, ConsumeResultWithSyntax, DefaultNameArgConsumer,
GetClientSideArgParser, FindArg, GetClientSideArgParser,
}, },
argument_types::{argument_type::ArgumentType as _, time::TimeArgumentType},
dispatcher::CommandError, dispatcher::CommandError,
tree::RawArgs, errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext},
string_reader::StringReader,
tree::{RawArg, RawArgs},
}; };
use crate::server::Server; use crate::server::Server;
@ -34,6 +37,23 @@ impl TimeArgumentConsumer {
} }
} }
fn map_local_syntax_error(error: CommandSyntaxError, raw_arg: RawArg<'_>) -> CommandSyntaxError {
let local_cursor = error.context.map_or(0, |context| context.cursor);
let mut clamped_local_cursor = local_cursor.min(raw_arg.value.len());
while clamped_local_cursor > 0 && !raw_arg.value.is_char_boundary(clamped_local_cursor) {
clamped_local_cursor -= 1;
}
CommandSyntaxError {
error_type: error.error_type,
message: error.message,
context: Some(CommandSyntaxErrorContext {
input: raw_arg.input.to_string(),
cursor: raw_arg.start + clamped_local_cursor,
}),
}
}
impl GetClientSideArgParser for TimeArgumentConsumer { impl GetClientSideArgParser for TimeArgumentConsumer {
fn get_client_side_parser(&self) -> ArgumentType { fn get_client_side_parser(&self) -> ArgumentType {
ArgumentType::Time { min: self.min } ArgumentType::Time { min: self.min }
@ -54,27 +74,30 @@ impl ArgumentConsumer for TimeArgumentConsumer {
let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value); let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value);
let result: Option<Arg<'a>> = s_opt.and_then(|s| { let result: Option<Arg<'a>> = s_opt.and_then(|s| {
let (num_str, unit) = s let mut reader = StringReader::new(s);
.find(|c: char| c.is_alphabetic() && c != '-') let parser = TimeArgumentType::new(self.min);
.map_or((s, "t"), |pos| (&s[..pos], &s[pos..])); parser.parse(&mut reader).ok().map(Arg::Time)
});
let number = num_str.parse::<f32>().ok()?; Box::pin(async move { result })
let factor = match unit {
"d" => 24000.0,
"s" => 20.0,
"t" | "" => 1.0,
_ => return None,
};
let ticks = (number * factor).round() as i32;
if ticks < self.min {
return None;
} }
Some(Arg::Time(ticks)) fn consume_with_syntax<'a>(
}); &'a self,
_sender: &'a CommandSender,
_server: &'a Server,
args: &mut RawArgs<'a>,
) -> ConsumeResultWithSyntax<'a> {
let Some(raw_arg) = args.pop() else {
return Box::pin(async { Ok(None) });
};
let mut reader = StringReader::new(raw_arg.value);
let parser = TimeArgumentType::new(self.min);
let result = parser
.parse(&mut reader)
.map(|ticks| Some(Arg::Time(ticks)))
.map_err(|error| map_local_syntax_error(error, raw_arg));
Box::pin(async move { result }) Box::pin(async move { result })
} }

View File

@ -42,7 +42,7 @@ impl ArgumentType for AttributeArgumentType {
fn client_side_parser(&'_ self) -> JavaClientArgumentType { fn client_side_parser(&'_ self) -> JavaClientArgumentType {
JavaClientArgumentType::Resource { JavaClientArgumentType::Resource {
identifier: Identifier::vanilla("attribute").expect("valid identifier"), identifier: Identifier::vanilla_static("attribute"),
} }
} }

View File

@ -82,14 +82,13 @@ pub async fn send_c_commands_packet(
.values() .values()
.copied() .copied()
.map(|id| resolve_node_id(id, node_id_offset, root_node_index)) .map(|id| resolve_node_id(id, node_id_offset, root_node_index))
.map(|i| i.try_into().expect("i32 limit reached for ids")) .map(|i| VarInt(i as i32))
.collect(); .collect();
let redirect_target = node let redirect_target = node
.redirect() .redirect()
.and_then(|redirection| dispatcher.tree.resolve(redirection)) .and_then(|redirection| dispatcher.tree.resolve(redirection))
.map(|id| resolve_node_id(id, node_id_offset, root_node_index)) .map(|id| resolve_node_id(id, node_id_offset, root_node_index) as i32);
.map(|i| i.try_into().expect("i32 limit reached for ids"));
let satisfies_requirements = true; let satisfies_requirements = true;
@ -117,7 +116,7 @@ pub async fn send_c_commands_packet(
!disabled !disabled
}) })
.map(|id| resolve_node_id(id, node_id_offset, root_node_index)) .map(|id| resolve_node_id(id, node_id_offset, root_node_index))
.map(|i| i.try_into().expect("i32 limit reached for ids")) .map(|i| VarInt(i as i32))
.collect(); .collect();
} }
AttachedNode::Literal(literal_attached_node) => { AttachedNode::Literal(literal_attached_node) => {
@ -205,11 +204,7 @@ impl<'a> ProtoNodeBuilder<'a> {
let children: Box<[VarInt]> = self let children: Box<[VarInt]> = self
.child_nodes .child_nodes
.into_iter() .into_iter()
.map(|node| { .map(|node| VarInt(node.build(buffer) as i32))
node.build(buffer)
.try_into()
.expect("Buffer index exceeded i32 bounds")
})
.collect(); .collect();
let i = buffer.len(); let i = buffer.len();
@ -589,15 +584,14 @@ fn collect_overloads_from_attached(
} }
fn ensure_enum_value(enum_values: &mut Vec<String>, value: &str) -> u32 { fn ensure_enum_value(enum_values: &mut Vec<String>, value: &str) -> u32 {
enum_values let index = enum_values
.iter() .iter()
.position(|v| v == value) .position(|v| v == value)
.unwrap_or_else(|| { .unwrap_or_else(|| {
enum_values.push(value.to_string()); enum_values.push(value.to_string());
enum_values.len() - 1 enum_values.len() - 1
}) });
.try_into() index as u32
.unwrap()
} }
fn ensure_command_enum( fn ensure_command_enum(

View File

@ -67,12 +67,7 @@ impl CommandExecutor for LocationExecutor {
Box::pin(async move { Box::pin(async move {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) let amount = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT)??;
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Invalid damage amount",
)));
};
let damage_type = let damage_type =
args.get(ARG_DAMAGE_TYPE) args.get(ARG_DAMAGE_TYPE)
@ -102,12 +97,7 @@ impl CommandExecutor for EntityExecutor {
Box::pin(async move { Box::pin(async move {
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) let amount = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT)??;
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Invalid damage amount",
)));
};
let damage_type = let damage_type =
args.get(ARG_DAMAGE_TYPE) args.get(ARG_DAMAGE_TYPE)

View File

@ -88,16 +88,13 @@ impl CommandExecutor for GiveExecutor {
let mut successes = 0; let mut successes = 0;
for target in targets { for target in targets {
if target.living_entity.has_effect(effect).await let should_skip = target
&& target
.living_entity .living_entity
.get_effect(effect) .get_effect(effect)
.await .await
.expect("Effect should exist because has_effect returned true") .is_some_and(|existing| existing.amplifier >= amplifier);
.amplifier
>= amplifier if !should_skip {
{
} else {
target target
.add_effect(Effect { .add_effect(Effect {
effect_type: effect, effect_type: effect,

View File

@ -1,5 +1,3 @@
use pumpkin_data::data_component::DataComponent::MaxStackSize;
use pumpkin_data::data_component_impl::{MaxStackSizeImpl, get};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::text::hover::HoverEvent;
@ -47,14 +45,7 @@ impl CommandExecutor for Executor {
}; };
for target in targets { for target in targets {
let max_stack = i32::from( let max_stack = i32::from(parsed_stack.get_max_stack_size());
item.components
.iter()
.find_map(|(id, component)| {
(id == &MaxStackSize).then(|| get::<MaxStackSizeImpl>(*component).size)
})
.expect("Item should have MaxStackSize component"),
);
let mut remaining = item_count; let mut remaining = item_count;
while remaining > 0 { while remaining > 0 {

View File

@ -333,7 +333,7 @@ fn register_permissions(registry: &mut PermissionRegistry) {
"Allows a player to use selector variables", "Allows a player to use selector variables",
PermissionDefault::Allow, PermissionDefault::Allow,
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
} }
fn register_level_0_permissions(registry: &mut PermissionRegistry) { fn register_level_0_permissions(registry: &mut PermissionRegistry) {
@ -344,21 +344,21 @@ fn register_level_0_permissions(registry: &mut PermissionRegistry) {
"Shows information about the Pumpkin server", "Shows information about the Pumpkin server",
PermissionDefault::Allow, PermissionDefault::Allow,
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.me", "minecraft:command.me",
"Broadcasts a narrative message about the player", "Broadcasts a narrative message about the player",
PermissionDefault::Allow, PermissionDefault::Allow,
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.msg", "minecraft:command.msg",
"Sends a private message to another player", "Sends a private message to another player",
PermissionDefault::Allow, PermissionDefault::Allow,
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
@ -370,210 +370,210 @@ fn register_level_2_permissions(registry: &mut PermissionRegistry) {
"Manages the world border", "Manages the world border",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.effect", "minecraft:command.effect",
"Adds or removes status effects", "Adds or removes status effects",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.teleport", "minecraft:command.teleport",
"Teleports entities to other locations", "Teleports entities to other locations",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.time", "minecraft:command.time",
"Changes or queries the world's game time", "Changes or queries the world's game time",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.waypoint", "minecraft:command.waypoint",
"List or modify waypoints", "List or modify waypoints",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.give", "minecraft:command.give",
"Gives an item to a player", "Gives an item to a player",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.item", "minecraft:command.item",
"Replace items in inventories", "Replace items in inventories",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.clear", "minecraft:command.clear",
"Clears items from player inventory", "Clears items from player inventory",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.setblock", "minecraft:command.setblock",
"Changes a block to another block", "Changes a block to another block",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.fill", "minecraft:command.fill",
"Fills a region with a specific block", "Fills a region with a specific block",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.playsound", "minecraft:command.playsound",
"Plays a sound to players", "Plays a sound to players",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.tellraw", "minecraft:command.tellraw",
"Displays a JSON message to players", "Displays a JSON message to players",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.title", "minecraft:command.title",
"Controls screen titles displayed to players", "Controls screen titles displayed to players",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.summon", "minecraft:command.summon",
"Summons an entity", "Summons an entity",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.experience", "minecraft:command.experience",
"Adds, removes or queries player experience", "Adds, removes or queries player experience",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.weather", "minecraft:command.weather",
"Sets the weather in the server", "Sets the weather in the server",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.particle", "minecraft:command.particle",
"Creates particles in the world", "Creates particles in the world",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.rotate", "minecraft:command.rotate",
"Changes the rotation of an entity", "Changes the rotation of an entity",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.damage", "minecraft:command.damage",
"Damages entities", "Damages entities",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.bossbar", "minecraft:command.bossbar",
"Creates and manages boss bars", "Creates and manages boss bars",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.say", "minecraft:command.say",
"Broadcasts a message to multiple players", "Broadcasts a message to multiple players",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.gamemode", "minecraft:command.gamemode",
"Sets a player's game mode", "Sets a player's game mode",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.gamerule", "minecraft:command.gamerule",
"Sets a player's game mode", "Sets a player's game mode",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.stopsound", "minecraft:command.stopsound",
"Stops sounds from playing", "Stops sounds from playing",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.defaultgamemode", "minecraft:command.defaultgamemode",
"Sets the default game mode for new players", "Sets the default game mode for new players",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.data", "minecraft:command.data",
"Query and modify data of entities and blocks", "Query and modify data of entities and blocks",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.enchant", "minecraft:command.enchant",
"Adds an enchantment to a player's selected item, subject to the same restrictions as an anvil. Also works on any mob or entity holding a weapon/tool/armor in its main hand.", "Adds an enchantment to a player's selected item, subject to the same restrictions as an anvil. Also works on any mob or entity holding a weapon/tool/armor in its main hand.",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.spawnpoint", "minecraft:command.spawnpoint",
"Sets the spawn point for a player", "Sets the spawn point for a player",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.spectate", "minecraft:command.spectate",
"Allows a player to spectate another entity", "Allows a player to spectate another entity",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"pumpkin:command.tps", "pumpkin:command.tps",
"Displays the server TPS and MSPT", "Displays the server TPS and MSPT",
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
} }
fn register_level_3_permissions(registry: &mut PermissionRegistry) { fn register_level_3_permissions(registry: &mut PermissionRegistry) {
@ -584,77 +584,77 @@ fn register_level_3_permissions(registry: &mut PermissionRegistry) {
"Sets the world spawn point", "Sets the world spawn point",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.deop", "minecraft:command.deop",
"Revokes operator status from a player", "Revokes operator status from a player",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.kick", "minecraft:command.kick",
"Removes players from the server", "Removes players from the server",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"pumpkin:command.plugin", "pumpkin:command.plugin",
"Manages server plugins", "Manages server plugins",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"pumpkin:command.plugins", "pumpkin:command.plugins",
"Lists all plugins loaded on the server", "Lists all plugins loaded on the server",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.ban", "minecraft:command.ban",
"Adds players to banlist", "Adds players to banlist",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.banip", "minecraft:command.banip",
"Adds IP addresses to banlist", "Adds IP addresses to banlist",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.pardon", "minecraft:command.pardon",
"Removes entries from the player banlist", "Removes entries from the player banlist",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.pardonip", "minecraft:command.pardonip",
"Removes entries from the IP banlist", "Removes entries from the IP banlist",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.whitelist", "minecraft:command.whitelist",
"Manages server whitelist", "Manages server whitelist",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
registry registry
.register_permission(Permission::new( .register_permission(Permission::new(
"minecraft:command.transfer", "minecraft:command.transfer",
"Transfers the player to another server", "Transfers the player to another server",
PermissionDefault::Op(PermissionLvl::Three), PermissionDefault::Op(PermissionLvl::Three),
)) ))
.expect("Permission already registered"); .unwrap_or_else(|e| tracing::warn!("{e}"));
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,14 +1,14 @@
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::CommandResult;
use crate::command::args::{ use crate::command::args::{
FindArg, bounded_num::BoundedNumArgumentConsumer, FindArg, bounded_num::BoundedNumArgumentConsumer,
resource_location::ResourceLocationArgumentConsumer, time::TimeArgumentConsumer, resource_location::ResourceLocationArgumentConsumer, time::TimeArgumentConsumer,
}; };
use crate::command::dispatcher::CommandError;
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree}; use crate::command::{
CommandError, CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree,
};
const NAMES: [&str; 1] = ["time"]; const NAMES: [&str; 1] = ["time"];
const DESCRIPTION: &str = "Query or modify the world time and clocks."; const DESCRIPTION: &str = "Query or modify the world time and clocks.";
@ -72,9 +72,7 @@ impl CommandExecutor for QueryExecutor {
.unwrap_or(DEFAULT_CLOCK); .unwrap_or(DEFAULT_CLOCK);
let mode = self.0; let mode = self.0;
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let level_time = world.level_time.lock().await; let level_time = world.level_time.lock().await;
match mode { match mode {
@ -142,21 +140,15 @@ impl CommandExecutor for ActionExecutor {
.unwrap_or(DEFAULT_CLOCK); .unwrap_or(DEFAULT_CLOCK);
let action = self.0; let action = self.0;
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut level_time = world.level_time.lock().await; let mut level_time = world.level_time.lock().await;
match action { match action {
Action::Set(preset) => { Action::Set(preset) => {
let time_count = if let Some(p) = preset { let time_count = if let Some(p) = preset {
p.to_ticks() p.to_ticks()
} else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) {
ticks
} else { } else {
return Err(CommandError::CommandFailed(TextComponent::text( TimeArgumentConsumer::find_arg(args, ARG_TIME)?
"Invalid time specified.",
)));
}; };
level_time.set_time(time_count.into()); level_time.set_time(time_count.into());
level_time.send_time(world).await; level_time.send_time(world).await;

View File

@ -9,7 +9,7 @@ use crate::command::args::{
}; };
use crate::command::tree::builder::{argument, literal}; use crate::command::tree::builder::{argument, literal};
use crate::command::{ use crate::command::{
CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree,
}; };
const NAMES: [&str; 1] = ["waypoint"]; const NAMES: [&str; 1] = ["waypoint"];
@ -29,9 +29,7 @@ impl CommandExecutor for ListExecutor {
) -> CommandResult<'a> { ) -> CommandResult<'a> {
Box::pin(async move { Box::pin(async move {
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let dimension = world.dimension.minecraft_name.to_string(); let dimension = world.dimension.minecraft_name.to_string();
sender sender

View File

@ -4,8 +4,8 @@ use pumpkin_util::{math::vector2::Vector2, text::TextComponent};
use crate::command::{ use crate::command::{
CommandError, CommandExecutor, CommandResult, CommandSender, CommandError, CommandExecutor, CommandResult, CommandSender,
args::{ args::{
ConsumedArgs, DefaultNameArgConsumer, FindArgDefaultName, ConsumedArgs, FindArgDefaultName, bounded_num::BoundedNumArgumentConsumer,
bounded_num::BoundedNumArgumentConsumer, position_2d::Position2DArgumentConsumer, position_2d::Position2DArgumentConsumer,
}, },
tree::{ tree::{
CommandTree, CommandTree,
@ -51,9 +51,7 @@ impl CommandExecutor for GetExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let border = world.worldborder.lock().await; let border = world.worldborder.lock().await;
let diameter = border.new_diameter.round() as i32; let diameter = border.new_diameter.round() as i32;
@ -82,17 +80,10 @@ impl CommandExecutor for SetExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { let distance = distance_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if (distance - border.new_diameter).abs() < f64::EPSILON { if (distance - border.new_diameter).abs() < f64::EPSILON {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
@ -132,23 +123,11 @@ impl CommandExecutor for SetTimeExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { let distance = distance_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!( let time = time_consumer().find_arg_default_name(args)??;
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
let Ok(time) = time_consumer().find_arg_default_name(args)? else {
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
let old_dist = format!("{:.1}", border.new_diameter); let old_dist = format!("{:.1}", border.new_diameter);
match distance.total_cmp(&border.new_diameter) { match distance.total_cmp(&border.new_diameter) {
@ -206,17 +185,10 @@ impl CommandExecutor for AddExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(distance_add) = distance_consumer().find_arg_default_name(args)? else { let distance_add = distance_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if distance_add == 0.0 { if distance_add == 0.0 {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
@ -257,33 +229,23 @@ impl CommandExecutor for AddTimeExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(distance_add) = distance_consumer().find_arg_default_name(args)? else { let distance_add = distance_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!( let time = time_consumer().find_arg_default_name(args)??;
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
let Ok(time) = time_consumer().find_arg_default_name(args)? else {
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
let distance = distance_add + border.new_diameter; let distance = distance_add + border.new_diameter;
let old_dist = format!("{:.1}", border.new_diameter); let old_dist = format!("{:.1}", border.new_diameter);
match distance.total_cmp(&border.new_diameter) { match distance.total_cmp(&border.new_diameter) {
std::cmp::Ordering::Equal => { std::cmp::Ordering::Equal => {
return Err(CommandError::CommandFailed(TextComponent::text(format!( return Err(CommandError::CommandFailed(
"{} is out of bounds.", pumpkin_macros::translate_cross!(
distance_consumer().default_name() translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE,
)))); translation::bedrock::COMMANDS_WORLDBORDER_SET_SUCCESS
),
));
} }
std::cmp::Ordering::Less => { std::cmp::Ordering::Less => {
let dist = format!("{distance:.1}"); let dist = format!("{distance:.1}");
@ -330,9 +292,7 @@ impl CommandExecutor for CenterExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Vector2 { x, y } = Position2DArgumentConsumer.find_arg_default_name(args)?; let Vector2 { x, y } = Position2DArgumentConsumer.find_arg_default_name(args)?;
@ -363,18 +323,10 @@ impl CommandExecutor for DamageAmountExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(damage_per_block) = damage_per_block_consumer().find_arg_default_name(args)? let damage_per_block = damage_per_block_consumer().find_arg_default_name(args)??;
else {
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON { if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
@ -413,17 +365,10 @@ impl CommandExecutor for DamageBufferExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(buffer) = damage_buffer_consumer().find_arg_default_name(args)? else { let buffer = damage_buffer_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if (buffer - border.buffer).abs() < f32::EPSILON { if (buffer - border.buffer).abs() < f32::EPSILON {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
@ -462,17 +407,10 @@ impl CommandExecutor for WarningDistanceExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(distance) = warning_distance_consumer().find_arg_default_name(args)? else { let distance = warning_distance_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if distance == border.warning_blocks { if distance == border.warning_blocks {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
@ -509,17 +447,10 @@ impl CommandExecutor for WarningTimeExecutor {
Box::pin(async move { Box::pin(async move {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
.first()
.expect("There should always be at least one world");
let mut border = world.worldborder.lock().await; let mut border = world.worldborder.lock().await;
let Ok(time) = time_consumer().find_arg_default_name(args)? else { let time = time_consumer().find_arg_default_name(args)??;
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"{} is out of bounds.",
distance_consumer().default_name()
))));
};
if time == border.warning_time { if time == border.warning_time {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(

View File

@ -666,7 +666,10 @@ impl CommandDispatcher {
let mut names = tree.names.iter(); let mut names = tree.names.iter();
let permission = permission.into(); let permission = permission.into();
let primary_name = names.next().expect("at least one name must be provided"); let Some(primary_name) = names.next() else {
tracing::warn!("Command registration skipped: command tree has no names");
return;
};
for name in names { for name in names {
self.commands self.commands

View File

@ -117,7 +117,7 @@ impl CommandSender {
let format = time::macros::format_description!("[hour]:[minute]:[second]"); let format = time::macros::format_description!("[hour]:[minute]:[second]");
let timestamp = now let timestamp = now
.format(&format) .format(&format)
.expect("Failed to format timestamp for command block output"); .unwrap_or_else(|_| "00:00:00".to_string());
*last_output = format!("[{}] {}", timestamp, text.get_text()); *last_output = format!("[{}] {}", timestamp, text.get_text());
} }

View File

@ -196,7 +196,7 @@ impl CatEntity {
let entity = self.get_entity(); let entity = self.get_entity();
entity.send_meta_data( entity.send_meta_data(
&[Metadata::new( &[Metadata::new(
TrackedData::COLLAR_COLOR, TrackedData::CAT_COLLAR_COLOR,
MetaDataType::INT, MetaDataType::INT,
VarInt(color as i32), VarInt(color as i32),
)], )],
@ -443,7 +443,7 @@ impl Mob for CatEntity {
); );
entity.send_meta_data( entity.send_meta_data(
&[Metadata::new( &[Metadata::new(
TrackedData::COLLAR_COLOR, TrackedData::CAT_COLLAR_COLOR,
MetaDataType::INT, MetaDataType::INT,
VarInt(self.collar_color.load(Ordering::Relaxed) as i32), VarInt(self.collar_color.load(Ordering::Relaxed) as i32),
)], )],

View File

@ -4,7 +4,7 @@ pub mod statistics;
use core::f32; use core::f32;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use std::f64::consts::TAU; use std::f64::consts::TAU;
use std::num::NonZeroU8; use std::num::NonZero;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -374,6 +374,8 @@ pub struct ChunkManager {
entity_chunk_queue: VecDeque<(Vector2<i32>, Weak<ChunkEntityData>)>, entity_chunk_queue: VecDeque<(Vector2<i32>, Weak<ChunkEntityData>)>,
batches_sent_since_ack: u8, batches_sent_since_ack: u8,
last_chunk_batch_sent_at: Instant, 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. /// The current world for chunk loading. Updated on dimension change.
world: Arc<World>, world: Arc<World>,
} }
@ -398,6 +400,7 @@ impl ChunkManager {
entity_chunk_queue: VecDeque::new(), entity_chunk_queue: VecDeque::new(),
batches_sent_since_ack: 0, batches_sent_since_ack: 0,
last_chunk_batch_sent_at: Instant::now(), last_chunk_batch_sent_at: Instant::now(),
held_tickets: None,
world, world,
} }
} }
@ -413,6 +416,25 @@ impl ChunkManager {
self.chunk_sent.len() 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 { fn should_enqueue_chunk(&mut self, position: Vector2<i32>, chunk: &SyncChunk) -> bool {
self.chunk_sent self.chunk_sent
.insert(position, Arc::downgrade(chunk)) .insert(position, Arc::downgrade(chunk))
@ -459,7 +481,6 @@ impl ChunkManager {
) { ) {
view_distance += 1; // Margin for loading view_distance += 1; // Margin for loading
let old_center = self.center; let old_center = self.center;
let old_view_distance = self.view_distance;
{ {
let mut lock = level let mut lock = level
@ -475,11 +496,13 @@ impl ChunkManager {
let sim_level = ChunkLoading::get_level_from_simulation_distance(sim_dist); let sim_level = ChunkLoading::get_level_from_simulation_distance(sim_dist);
lock.add_ticket(center, sim_level); lock.add_ticket(center, sim_level);
if (old_center != center || old_view_distance != view_distance) && old_view_distance > 0 // 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
let old_level = ChunkLoading::get_level_from_view_distance(old_view_distance); // previous view distance, so recomputing them can release a ticket we never
lock.remove_ticket(old_center, old_level); // took and strand the one we did.
lock.remove_ticket(old_center, sim_level); 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(); lock.send_change();
}; };
@ -528,11 +551,13 @@ impl ChunkManager {
.chunk_loading .chunk_loading
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
lock.remove_ticket( if let Some((view_level, sim_level)) = self.held_tickets.take() {
self.center, lock.remove_ticket(self.center, view_level);
ChunkLoading::get_level_from_view_distance(self.view_distance), lock.remove_ticket(self.center, sim_level);
); }
lock.send_change(); lock.send_change();
level.should_unload.store(true, Ordering::Relaxed);
level.level_channel.notify();
let (_rx, tx) = crossbeam::channel::unbounded(); let (_rx, tx) = crossbeam::channel::unbounded();
// drop old channel // drop old channel
self.chunk_listener = tx; self.chunk_listener = tx;
@ -543,6 +568,7 @@ impl ChunkManager {
self.entity_chunk_queue.clear(); self.entity_chunk_queue.clear();
self.batches_sent_since_ack = 0; self.batches_sent_since_ack = 0;
self.last_chunk_batch_sent_at = Instant::now(); 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>) { pub fn change_world(&mut self, old_level: &Arc<Level>, new_world: Arc<World>) {
@ -550,15 +576,19 @@ impl ChunkManager {
.chunk_loading .chunk_loading
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
lock.remove_ticket( if let Some((view_level, sim_level)) = self.held_tickets.take() {
self.center, lock.remove_ticket(self.center, view_level);
ChunkLoading::get_level_from_view_distance(self.view_distance), lock.remove_ticket(self.center, sim_level);
); }
lock.send_change(); lock.send_change();
old_level.should_unload.store(true, Ordering::Relaxed);
old_level.level_channel.notify();
drop(lock); drop(lock);
self.chunk_listener = new_world.level.chunk_listener.add_global_chunk_listener(); self.chunk_listener = new_world.level.chunk_listener.add_global_chunk_listener();
self.chunk_sent.clear(); self.chunk_sent.clear();
self.chunk_queue.clear(); self.chunk_queue.clear();
self.entity_chunk_queue.clear();
self.view_distance = 0;
self.world = new_world; self.world = new_world;
// Reset batch state so chunks can be sent immediately in the new dimension // Reset batch state so chunks can be sent immediately in the new dimension
self.batches_sent_since_ack = 0; self.batches_sent_since_ack = 0;
@ -953,7 +983,7 @@ impl Player {
watched_section: AtomicCell::new(Cylindrical::new( watched_section: AtomicCell::new(Cylindrical::new(
Vector2::new(0, 0), Vector2::new(0, 0),
// Since 1 is not possible in vanilla it is used as uninit // Since 1 is not possible in vanilla it is used as uninit
NonZeroU8::new(1).unwrap_or(NonZeroU8::MIN), NonZero::new(1).unwrap_or(NonZero::<u8>::MIN),
)), )),
last_action_time: AtomicCell::new(std::time::Instant::now()), last_action_time: AtomicCell::new(std::time::Instant::now()),
ping: AtomicU32::new(0), ping: AtomicU32::new(0),
@ -3165,7 +3195,7 @@ impl Player {
self.watched_section.store(Cylindrical::new( self.watched_section.store(Cylindrical::new(
Vector2::new(0, 0), Vector2::new(0, 0),
NonZeroU8::new(1).unwrap_or(NonZeroU8::MIN), NonZero::new(1).unwrap_or(NonZero::<u8>::MIN),
)); ));
} }

View File

@ -1,5 +1,5 @@
use std::{ use std::{
num::{NonZero, NonZeroI32}, num::NonZero,
sync::{Arc, atomic::Ordering}, sync::{Arc, atomic::Ordering},
}; };

View File

@ -22,7 +22,7 @@ impl BedrockClient {
let view_distance = chunk_radius.clamp( let view_distance = chunk_radius.clamp(
2, 2,
NonZeroI32::from(server.advanced_config.networking.bedrock.view_distance).get(), NonZero::<i32>::from(server.advanced_config.networking.bedrock.view_distance).get(),
); );
self.enqueue_packet(&CChunkRadiusUpdate { self.enqueue_packet(&CChunkRadiusUpdate {

View File

@ -22,8 +22,8 @@ impl JavaClient {
self.config.store(Arc::new(PlayerConfig { self.config.store(Arc::new(PlayerConfig {
locale: client_information.locale.to_string(), locale: client_information.locale.to_string(),
// client_information.view_distance was checked above to be > 0 so compiler should optimize this out. // client_information.view_distance was checked above to be > 0 so compiler should optimize this out.
view_distance: NonZeroU8::new(client_information.view_distance as u8) view_distance: NonZero::new(client_information.view_distance as u8)
.unwrap_or(NonZeroU8::MIN), .unwrap_or(NonZero::<u8>::MIN),
chat_mode, chat_mode,
chat_colors: client_information.chat_colors, chat_colors: client_information.chat_colors,
skin_parts: client_information.skin_parts, skin_parts: client_information.skin_parts,

View File

@ -1,4 +1,4 @@
use std::{num::NonZeroU8, sync::Arc, sync::atomic::Ordering}; use std::{num::NonZero, sync::Arc, sync::atomic::Ordering};
use crate::{ use crate::{
entity::player::ChatMode, entity::player::ChatMode,

View File

@ -1,4 +1,4 @@
use std::{net::SocketAddr, num::NonZeroU8, sync::Arc}; use std::{net::SocketAddr, num::NonZero, sync::Arc};
use bytes::Bytes; use bytes::Bytes;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
@ -423,8 +423,8 @@ impl PendingConnection {
) { ) {
self.config = Some(PlayerConfig { self.config = Some(PlayerConfig {
locale: client_information.locale.to_string(), locale: client_information.locale.to_string(),
view_distance: NonZeroU8::new(client_information.view_distance as u8) view_distance: NonZero::new(client_information.view_distance as u8)
.unwrap_or(NonZeroU8::MIN), .unwrap_or(NonZero::<u8>::MIN),
chat_mode, chat_mode,
chat_colors: client_information.chat_colors, chat_colors: client_information.chat_colors,
skin_parts: client_information.skin_parts, skin_parts: client_information.skin_parts,

View File

@ -45,7 +45,7 @@ impl JavaClient {
// 3. Construct the new config // 3. Construct the new config
// If view_distance is 0, we exit early (safe guard) // If view_distance is 0, we exit early (safe guard)
let Some(new_view_distance) = NonZeroU8::new(new_view_distance_raw) else { let Some(new_view_distance) = NonZero::new(new_view_distance_raw) else {
return; return;
}; };

View File

@ -3,7 +3,7 @@ use pumpkin_util::{Hand, PermissionLvl};
use rsa::pkcs1v15::{Signature as RsaPkcs1v15Signature, VerifyingKey}; use rsa::pkcs1v15::{Signature as RsaPkcs1v15Signature, VerifyingKey};
use rsa::signature::Verifier; use rsa::signature::Verifier;
use sha1::Sha1; use sha1::Sha1;
use std::num::NonZeroU8; use std::num::NonZero;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};

View File

@ -8,7 +8,7 @@ use bytes::Bytes;
use pumpkin_world::level::SyncChunk; use pumpkin_world::level::SyncChunk;
use std::{ use std::{
net::SocketAddr, net::SocketAddr,
num::NonZeroU8, num::NonZero,
sync::{Arc, atomic::Ordering}, sync::{Arc, atomic::Ordering},
}; };
@ -79,7 +79,7 @@ pub struct PlayerConfig {
/// The player's preferred language. /// The player's preferred language.
pub locale: String, // 16 pub locale: String, // 16
/// The maximum distance at which chunks are rendered. /// The maximum distance at which chunks are rendered.
pub view_distance: NonZeroU8, pub view_distance: NonZero<u8>,
/// The player's chat mode settings /// The player's chat mode settings
pub chat_mode: ChatMode, pub chat_mode: ChatMode,
/// Whether chat colors are enabled. /// Whether chat colors are enabled.
@ -98,7 +98,7 @@ impl Default for PlayerConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
locale: "en_us".to_string(), locale: "en_us".to_string(),
view_distance: NonZeroU8::new(8).unwrap_or(NonZeroU8::MIN), view_distance: NonZero::new(8).unwrap_or(NonZero::<u8>::MIN),
chat_mode: ChatMode::Enabled, chat_mode: ChatMode::Enabled,
chat_colors: true, chat_colors: true,
skin_parts: 0x7F, skin_parts: 0x7F,

View File

@ -133,7 +133,11 @@ impl boss_bar::HostBossBar for PluginHostState {
bossbar.color = from_wit_color(color); bossbar.color = from_wit_color(color);
bossbar.division = from_wit_division(division); bossbar.division = from_wit_division(division);
let server = self.server.as_ref().expect("server not available").clone(); let server = self
.server
.as_ref()
.ok_or_else(|| wasmtime::Error::msg("server not available"))?
.clone();
let plugin_bossbar = Arc::new(Mutex::new(PluginBossBar::new( let plugin_bossbar = Arc::new(Mutex::new(PluginBossBar::new(
bossbar, bossbar,
Arc::downgrade(&server), Arc::downgrade(&server),

View File

@ -666,7 +666,10 @@ impl HostEntity for PluginHostState {
let entity = entity_from_resource(self, &entity)?; let entity = entity_from_resource(self, &entity)?;
let attribute = from_wit_attribute(attr); let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() { if let Some(living) = entity.get_living_entity() {
let map = living.attributes.read().unwrap(); let map = living
.attributes
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inst) = map.get(&attribute.id) { if let Some(inst) = map.get(&attribute.id) {
return Ok(inst return Ok(inst
.modifiers .modifiers
@ -691,7 +694,10 @@ impl HostEntity for PluginHostState {
let attribute = from_wit_attribute(attr); let attribute = from_wit_attribute(attr);
if let Some(living) = entity.get_living_entity() { if let Some(living) = entity.get_living_entity() {
{ {
let mut map = living.attributes.write().unwrap(); let mut map = living
.attributes
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.remove(&attribute.id); map.remove(&attribute.id);
}; };
crate::entity::attributes::send_attribute_updates_for_living( crate::entity::attributes::send_attribute_updates_for_living(

View File

@ -95,7 +95,7 @@ pub(super) fn to_wasm_block_name(block: &'static Block) -> String {
pub(super) fn from_wasm_block_name(block_name: &str) -> &'static Block { pub(super) fn from_wasm_block_name(block_name: &str) -> &'static Block {
Block::from_registry_key(block_name.strip_prefix("minecraft:").unwrap_or(block_name)) Block::from_registry_key(block_name.strip_prefix("minecraft:").unwrap_or(block_name))
.expect("invalid block name") .unwrap_or(&Block::AIR)
} }
pub(super) fn to_wasm_entity_type(entity_type: &'static EntityType) -> String { pub(super) fn to_wasm_entity_type(entity_type: &'static EntityType) -> String {
@ -103,7 +103,12 @@ pub(super) fn to_wasm_entity_type(entity_type: &'static EntityType) -> String {
} }
pub(super) fn from_wasm_entity_type(entity_type: &str) -> &'static EntityType { pub(super) fn from_wasm_entity_type(entity_type: &str) -> &'static EntityType {
EntityType::from_name(entity_type).expect("invalid entity type") EntityType::from_name(
entity_type
.strip_prefix("minecraft:")
.unwrap_or(entity_type),
)
.unwrap_or(&EntityType::PLAYER)
} }
pub(super) const fn to_wasm_hand(hand: Hand) -> pumpkin::plugin::common::Hand { pub(super) const fn to_wasm_hand(hand: Hand) -> pumpkin::plugin::common::Hand {
@ -228,10 +233,9 @@ impl<E: Payload + ToFromWasmEvent> EventHandler<E> for WasmPluginEventHandler {
let event = event.to_wasm_event(store.data_mut()); let event = event.to_wasm_event(store.data_mut());
match self.plugin.plugin_instance { match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => { PluginInstance::V0_1(ref plugin) => {
let server = store let Ok(server) = store.data_mut().add_server(server.clone()) else {
.data_mut() return;
.add_server(server.clone()) };
.expect("valid server");
let _ = plugin let _ = plugin
.call_handle_event(&mut *store, self.handler_id, server, &event) .call_handle_event(&mut *store, self.handler_id, server, &event)
.await; .await;
@ -250,10 +254,9 @@ impl<E: Payload + ToFromWasmEvent> EventHandler<E> for WasmPluginEventHandler {
let wasm_event = event.to_wasm_event(store.data_mut()); let wasm_event = event.to_wasm_event(store.data_mut());
match self.plugin.plugin_instance { match self.plugin.plugin_instance {
PluginInstance::V0_1(ref plugin) => { PluginInstance::V0_1(ref plugin) => {
let server = store let Ok(server) = store.data_mut().add_server(server.clone()) else {
.data_mut() return;
.add_server(server.clone()) };
.expect("valid server");
if let Ok(returned_event) = plugin if let Ok(returned_event) = plugin
.call_handle_event(&mut *store, self.handler_id, server, &wasm_event) .call_handle_event(&mut *store, self.handler_id, server, &wasm_event)
.await .await

View File

@ -1199,10 +1199,12 @@ impl ToFromWasmEvent for AsyncPlayerPreLoginEvent {
Event::AsyncPlayerPreLoginEvent(data) => Self { Event::AsyncPlayerPreLoginEvent(data) => Self {
player_name: data.player_name, player_name: data.player_name,
player_uuid: data.player_uuid.parse().unwrap_or_default(), player_uuid: data.player_uuid.parse().unwrap_or_default(),
ip_address: data ip_address: data.ip_address.parse().unwrap_or_else(|_| {
.ip_address std::net::SocketAddr::new(
.parse() std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
.unwrap_or_else(|_| "127.0.0.1:0".parse().unwrap()), 0,
)
}),
kick_message: consume_text_component(state, &data.kick_message), kick_message: consume_text_component(state, &data.kick_message),
cancelled: data.cancelled, cancelled: data.cancelled,
}, },
@ -1912,10 +1914,12 @@ impl ToFromWasmEvent for PlayerPreLoginEvent {
Event::PlayerPreLoginEvent(data) => Self { Event::PlayerPreLoginEvent(data) => Self {
player_name: data.player_name, player_name: data.player_name,
player_uuid: data.player_uuid.parse().unwrap_or_default(), player_uuid: data.player_uuid.parse().unwrap_or_default(),
ip_address: data ip_address: data.ip_address.parse().unwrap_or_else(|_| {
.ip_address std::net::SocketAddr::new(
.parse() std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST),
.unwrap_or_else(|_| "127.0.0.1:0".parse().unwrap()), 0,
)
}),
kick_message: consume_text_component(state, &data.kick_message), kick_message: consume_text_component(state, &data.kick_message),
cancelled: data.cancelled, cancelled: data.cancelled,
}, },

View File

@ -12,12 +12,15 @@ impl pumpkin::plugin::ipc::Host for PluginHostState {
recipient: PluginId, recipient: PluginId,
message: IpcMessage, message: IpcMessage,
) -> wasmtime::Result<Result<Result<IpcMessage, String>, ()>> { ) -> wasmtime::Result<Result<Result<IpcMessage, String>, ()>> {
Ok(self let Some(server) = &self.server else {
.server return Err(wasmtime::Error::msg("Server not available"));
.as_ref() };
.unwrap() let Some(name) = &self.name else {
return Err(wasmtime::Error::msg("Plugin name not available"));
};
Ok(server
.plugin_manager .plugin_manager
.send_message(self.name.as_ref().unwrap(), &recipient, &message) .send_message(name, &recipient, &message)
.await) .await)
} }
} }

View File

@ -1047,9 +1047,7 @@ impl Server {
.map_or_else(Vec::new, |player| vec![player]), .map_or_else(Vec::new, |player| vec![player]),
}; };
let Some(player_type) = EntityType::from_name("player") else { let player_type = &EntityType::PLAYER;
return Vec::new();
};
let type_included = target_selector let type_included = target_selector
.conditions .conditions
.iter() .iter()

View File

@ -1,5 +1,5 @@
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use std::{num::NonZeroU8, sync::Arc}; 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,
@ -12,8 +12,8 @@ use crate::{
net::ClientPlatform, net::ClientPlatform,
}; };
pub fn get_view_distance(player: &Player) -> NonZeroU8 { pub fn get_view_distance(player: &Player) -> NonZero<u8> {
let fallback = NonZeroU8::new(2).unwrap_or(NonZeroU8::MIN); let fallback = NonZero::new(2).unwrap_or(NonZero::<u8>::MIN);
let Some(server) = player.world().server.upgrade() else { let Some(server) = player.world().server.upgrade() else {
return fallback; return fallback;
}; };

View File

@ -16,7 +16,7 @@ serde_repr = { version = "0.1" }
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }
serde_json5 = "0.2" serde_json5 = "0.2"
wit-encoder = "0.255.0" wit-encoder = "0.256.0"
semver = "1.0" semver = "1.0"
rayon = "1.12.0" rayon = "1.12.0"

View File

@ -128,7 +128,7 @@ pub fn build() -> TokenStream {
pub struct VeryBiasedToBottomHeightProvider { pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZeroU32>, pub inner: Option<std::num::NonZero<u32>>,
} }
pub struct CaveCarverConfig { pub struct CaveCarverConfig {

View File

@ -486,7 +486,7 @@ pub fn build() -> TokenStream {
use crate::chunk::DoublePerlinNoiseParameters; use crate::chunk::DoublePerlinNoiseParameters;
use crate::BlockState; use crate::BlockState;
use std::{cell::RefCell, num::NonZeroUsize}; use std::cell::RefCell;
use pumpkin_util::random::RandomDeriver; use pumpkin_util::random::RandomDeriver;
use pumpkin_util::y_offset::YOffset; use pumpkin_util::y_offset::YOffset;
use crate::biome::Biome; use crate::biome::Biome;

View File

@ -506,7 +506,7 @@ pub fn value_to_height_provider(v: &Value) -> TokenStream {
quote! { None } quote! { None }
} else { } else {
let n = v["inner"].as_u64().unwrap_or(1) as u32; let n = v["inner"].as_u64().unwrap_or(1) as u32;
quote! { std::num::NonZeroU32::new(#n) } quote! { std::num::NonZero::new(#n) }
}; };
quote! { quote! {
HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider {

View File

@ -44,6 +44,67 @@ pub(crate) fn build() -> TokenStream {
parsed.insert("WOLF_VARIANT_ID".to_owned(), id); parsed.insert("WOLF_VARIANT_ID".to_owned(), id);
} }
let cat_variant_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(20),
_ => parsed.get("CAT_VARIANT").copied().or(Some(19)),
};
if let Some(id) = cat_variant_id {
parsed.insert("CAT_VARIANT".to_owned(), id);
parsed.insert("CAT_VARIANT_ID".to_owned(), id);
}
let cat_collar_color_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(23),
_ => Some(22),
};
if let Some(id) = cat_collar_color_id {
parsed.insert("CAT_COLLAR_COLOR".to_owned(), id);
parsed.insert("CAT_COLLAR_COLOR_ID".to_owned(), id);
}
let wolf_collar_color_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(21),
_ => Some(20),
};
if let Some(id) = wolf_collar_color_id {
parsed.insert("WOLF_COLLAR_COLOR".to_owned(), id);
parsed.insert("WOLF_COLLAR_COLOR_ID".to_owned(), id);
}
let sound_variant_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(24),
JavaMinecraftVersion::V_1_21_5
| JavaMinecraftVersion::V_1_21_6
| JavaMinecraftVersion::V_1_21_7
| JavaMinecraftVersion::V_1_21_9
| JavaMinecraftVersion::V_1_21_11 => Some(23),
_ => None,
};
if let Some(id) = sound_variant_id {
parsed.insert("SOUND_VARIANT".to_owned(), id);
parsed.insert("SOUND_VARIANT_ID".to_owned(), id);
parsed.insert("CAT_SOUND_VARIANT_ID".to_owned(), id);
parsed.insert("WOLF_SOUND_VARIANT_ID".to_owned(), id);
}
let is_lying_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(21),
_ => Some(20),
};
if let Some(id) = is_lying_id {
parsed.insert("IS_LYING".to_owned(), id);
parsed.insert("IN_SLEEPING_POSE".to_owned(), id);
}
let relax_state_one_id = match ver {
JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(22),
_ => Some(21),
};
if let Some(id) = relax_state_one_id {
parsed.insert("RELAX_STATE_ONE".to_owned(), id);
parsed.insert("HEAD_DOWN".to_owned(), id);
}
versions.insert(ver, parsed); versions.insert(ver, parsed);
} }
@ -122,10 +183,15 @@ fn generate_consts(versions: &BTreeMap<JavaMinecraftVersion, BTreeMap<String, u8
// Some versions prefix keys with DATA_ (Bedrock), others don't (Java) // Some versions prefix keys with DATA_ (Bedrock), others don't (Java)
// Try both forms so every version resolves correctly // Try both forms so every version resolves correctly
let prefixed = format!("DATA_{final_name}"); let prefixed = format!("DATA_{final_name}");
let aliases: &[&str] = if final_name == "CUSTOM_NAME_VISIBLE" { let aliases: &[&str] = match final_name.as_str() {
&["NAME_VISIBLE"] "CUSTOM_NAME_VISIBLE" => &["NAME_VISIBLE"],
} else { "IS_LYING" => &["IN_SLEEPING_POSE"],
&[] "IN_SLEEPING_POSE" => &["IS_LYING"],
"RELAX_STATE_ONE" => &["HEAD_DOWN"],
"HEAD_DOWN" => &["RELAX_STATE_ONE"],
"SOUND_VARIANT" => &["SOUND_VARIANT_ID", "DATA_SOUND_VARIANT_ID"],
"SOUND_VARIANT_ID" => &["SOUND_VARIANT", "DATA_SOUND_VARIANT_ID"],
_ => &[],
}; };
let mut fields = TokenStream::new(); let mut fields = TokenStream::new();
@ -184,6 +250,51 @@ mod tests {
assert!(wolf.contains("v26_2 : 23u8")); assert!(wolf.contains("v26_2 : 23u8"));
} }
#[test]
fn cat_trackers_keep_their_entity_specific_tracker_ids() {
let generated = build().to_string();
assert!(generated.contains("CAT_VARIANT"));
let cat_variant = generated
.split("CAT_VARIANT")
.nth(1)
.expect("cat variant constant");
assert!(cat_variant.contains("v1_21_11 : 19u8"));
assert!(cat_variant.contains("v26_2 : 20u8"));
assert!(generated.contains("IS_LYING"));
let is_lying = generated
.split("IS_LYING")
.nth(1)
.expect("is lying constant");
assert!(is_lying.contains("v1_21_11 : 20u8"));
assert!(is_lying.contains("v26_2 : 21u8"));
assert!(generated.contains("RELAX_STATE_ONE"));
let relax = generated
.split("RELAX_STATE_ONE")
.nth(1)
.expect("relax state one constant");
assert!(relax.contains("v1_21_11 : 21u8"));
assert!(relax.contains("v26_2 : 22u8"));
assert!(generated.contains("CAT_COLLAR_COLOR"));
let collar = generated
.split("CAT_COLLAR_COLOR")
.nth(1)
.expect("cat collar color constant");
assert!(collar.contains("v1_21_11 : 22u8"));
assert!(collar.contains("v26_2 : 23u8"));
assert!(generated.contains("SOUND_VARIANT"));
let sound = generated
.split("SOUND_VARIANT")
.nth(1)
.expect("sound variant constant");
assert!(sound.contains("v1_21_11 : 23u8"));
assert!(sound.contains("v26_2 : 24u8"));
}
#[test] #[test]
fn checked_in_tracker_table_matches_codegen() { fn checked_in_tracker_table_matches_codegen() {
let checked_in = let checked_in =

View File

@ -9,7 +9,7 @@ license.workspace = true
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
colored.workspace = true colored.workspace = true
rand = "0.9" rand.workspace = true
[lints.clippy] [lints.clippy]
expect_used = "allow" expect_used = "allow"

View File

@ -1,6 +1,6 @@
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use colored::Colorize; use colored::Colorize;
use rand::Rng; use rand::RngExt;
use std::{ use std::{
net::SocketAddr, net::SocketAddr,
sync::{ sync::{