feat: partially implement Stronghold

also fixed some bugs
This commit is contained in:
Alexander Medvedev 2026-01-10 17:56:25 +01:00
parent f36baa6f44
commit a44a5babe3
36 changed files with 4597 additions and 770 deletions

128
Cargo.lock generated
View File

@ -125,13 +125,12 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.36" version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40"
dependencies = [ dependencies = [
"compression-codecs", "compression-codecs",
"compression-core", "compression-core",
"futures-core",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
] ]
@ -210,9 +209,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base16ct" name = "base16ct"
version = "0.3.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
[[package]] [[package]]
name = "base64" name = "base64"
@ -228,9 +227,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
version = "1.8.1" version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -282,9 +281,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.51" version = "1.2.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -401,9 +400,9 @@ dependencies = [
[[package]] [[package]]
name = "compression-codecs" name = "compression-codecs"
version = "0.4.35" version = "0.4.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a"
dependencies = [ dependencies = [
"compression-core", "compression-core",
"flate2", "flate2",
@ -463,9 +462,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.10.1" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
@ -625,12 +624,13 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "crossfire" name = "crossfire"
version = "2.1.9" version = "2.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f19636ec7667a64122ad23414cd773dcdd32532a7df7a4f68ad16bba0db5f02" checksum = "fd901251b9b46c1752c85edfee0aee718c03a85a065f4126d32e5d6d419edf48"
dependencies = [ dependencies = [
"crossbeam-queue", "crossbeam-queue",
"crossbeam-utils", "crossbeam-utils",
"enum_dispatch",
"futures-core", "futures-core",
"parking_lot", "parking_lot",
] ]
@ -706,9 +706,9 @@ dependencies = [
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.2.0-rc.8" version = "0.2.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6165b8029cdc3e765b74d3548f85999ee799d5124877ce45c2c85ca78e4d4aa" checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809"
dependencies = [ dependencies = [
"hybrid-array", "hybrid-array",
] ]
@ -755,7 +755,7 @@ version = "0.8.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653"
dependencies = [ dependencies = [
"const-oid 0.10.1", "const-oid 0.10.2",
"pem-rfc7468 1.0.0", "pem-rfc7468 1.0.0",
"zeroize", "zeroize",
] ]
@ -810,8 +810,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9" checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9"
dependencies = [ dependencies = [
"block-buffer 0.11.0", "block-buffer 0.11.0",
"const-oid 0.10.1", "const-oid 0.10.2",
"crypto-common 0.2.0-rc.8", "crypto-common 0.2.0-rc.9",
"subtle", "subtle",
] ]
@ -945,9 +945,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -1129,9 +1129,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@ -1447,9 +1447,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.12.1" version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.16.1",
@ -1498,9 +1498,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.17" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50"
dependencies = [ dependencies = [
"jiff-static", "jiff-static",
"log", "log",
@ -1511,9 +1511,9 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.17" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1538,9 +1538,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.179" version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1593,9 +1593,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "lru" name = "lru"
version = "0.16.2" version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [ dependencies = [
"hashbrown 0.16.1", "hashbrown 0.16.1",
] ]
@ -2050,9 +2050,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.104" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -2307,9 +2307,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.42" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2322,8 +2322,8 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.10.0-rc.5" version = "0.10.0-rc.6"
source = "git+https://github.com/rust-random/rand#75fe38fff59e5abb16ed5a146d61960cad27dc0f" source = "git+https://github.com/rust-random/rand#da2211b80e7217403b2bd8305a98ae7bc61c1146"
dependencies = [ dependencies = [
"chacha20", "chacha20",
"getrandom 0.4.0-rc.0", "getrandom 0.4.0-rc.0",
@ -2433,7 +2433,7 @@ version = "0.10.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e499c52862d75a86c0024cc99dcb6d7127d15af3beae7b03573d62fab7ade08a" checksum = "e499c52862d75a86c0024cc99dcb6d7127d15af3beae7b03573d62fab7ade08a"
dependencies = [ dependencies = [
"const-oid 0.10.1", "const-oid 0.10.2",
"crypto-bigint 0.7.0-rc.10", "crypto-bigint 0.7.0-rc.10",
"crypto-primes", "crypto-primes",
"digest 0.11.0-rc.5", "digest 0.11.0-rc.5",
@ -2477,9 +2477,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.35" version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@ -2607,9 +2607,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.148" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -2640,11 +2640,11 @@ dependencies = [
[[package]] [[package]]
name = "serdect" name = "serdect"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06"
dependencies = [ dependencies = [
"base16ct 0.3.0", "base16ct 1.0.0",
"serde", "serde",
] ]
@ -2848,9 +2848,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.112" version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3029,9 +3029,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.17" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@ -3040,9 +3040,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.17" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -3054,9 +3054,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.9.10+spec-1.1.0" version = "0.9.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",
@ -3297,9 +3297,9 @@ dependencies = [
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.7" version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -3683,18 +3683,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.31" version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.31" version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3763,6 +3763,6 @@ dependencies = [
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.8" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd" checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"

View File

@ -50,7 +50,7 @@ serde_json = "1.0"
tempfile = { version = "3.24.0", default-features = false } tempfile = { version = "3.24.0", default-features = false }
proc-macro-error2 = "2" proc-macro-error2 = "2"
aes = "0.8" aes = "0.8"
async-compression = "0.4.36" async-compression = "0.4.37"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = "2.10.0" bitflags = "2.10.0"
cesu8 = "1.1" cesu8 = "1.1"
@ -60,7 +60,7 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = "1.9.0" crc-fast = "1.9.0"
criterion = { version = "0.8", default-features = false } criterion = { version = "0.8", default-features = false }
crossbeam-utils = "0.8.21" crossbeam-utils = "0.8.21"
crossfire = "2.1.9" crossfire = "2.1.10"
dashmap = "6.1" dashmap = "6.1"
ecdsa = "0.16.9" ecdsa = "0.16.9"
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"
@ -68,10 +68,10 @@ env_logger = "0.11.8"
flate2 = "1.1.5" flate2 = "1.1.5"
heck = "0.5" heck = "0.5"
hmac = "=0.13.0-rc.3" hmac = "=0.13.0-rc.3"
indexmap = "2.12" indexmap = "2.13"
itertools = "0.14.0" itertools = "0.14.0"
libloading = "0.8" libloading = "0.8"
lru = "0.16.2" lru = "0.16.3"
lz4-java-wrc = "0.2.0" lz4-java-wrc = "0.2.0"
md5 = "0.8" md5 = "0.8"
num-bigint = "0.4" num-bigint = "0.4"
@ -107,6 +107,6 @@ take_mut = "0.2.2"
temp-dir = "0.1.16" temp-dir = "0.1.16"
thread_local = "1.1.9" thread_local = "1.1.9"
time = "0.3" time = "0.3"
tokio-util = "0.7.17" tokio-util = "0.7.18"
toml = "0.9" toml = "0.9"
ureq = "3.1.4" ureq = "3.1.4"

View File

@ -2,7 +2,7 @@
# Pumpkin # Pumpkin
![CI](https://github.com/Snowiiii/Pumpkin/actions/workflows/rust.yml/badge.svg) ![CI](https://github.com/Pumpkin-MC/Pumpkin/actions/workflows/rust.yml/badge.svg)
[![Discord](https://img.shields.io/discord/1268592337445978193.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/wT8XjrjKkf) [![Discord](https://img.shields.io/discord/1268592337445978193.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/wT8XjrjKkf)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![Current version)](https://img.shields.io/badge/current_version-1.21.11-blue) ![Current version)](https://img.shields.io/badge/current_version-1.21.11-blue)

View File

@ -22,7 +22,7 @@
}, },
"scripts": { "scripts": {
"installation": { "installation": {
"script": "#!\/bin\/ash\r\nset -eux\r\n\r\napk add --no-cache musl-dev git\r\nmkdir -p \/mnt\/server\r\n\r\nREPO_URL=\"${GIT_URL:-https:\/\/github.com\/Snowiiii\/Pumpkin.git}\"\r\n\r\nif [ -n \"$GIT_BRANCH\" ]; then\r\n git clone --depth 1 --single-branch --branch \"$GIT_BRANCH\" \"$REPO_URL\"\r\nelse\r\n git clone --depth 1 --single-branch \"$REPO_URL\"\r\nfi\r\n\r\nif [ -n \"$GIT_COMMIT\" ]; then\r\n git checkout \"$GIT_COMMIT\"\r\nfi\r\n\r\ncd Pumpkin\r\n\r\n#export RUSTFLAGS=\"-C target-feature=-crt-static -C target-cpu=native\"\r\nexport CARGO_BUILD_JOBS=${CARGO_BUILD_JOBS:-default}\r\n\r\nif [[ \"$BUILD_RELEASE\" == \"1\" || \"$BUILD_RELEASE\" == \"true\" ]]; then\r\n cargo build --release\r\n strip target\/release\/pumpkin\r\n cp target\/release\/pumpkin \/mnt\/server\/pumpkin\r\nelse\r\n cargo build\r\n cp target\/debug\/pumpkin \/mnt\/server\/pumpkin\r\nfi", "script": "#!\/bin\/ash\r\nset -eux\r\n\r\napk add --no-cache musl-dev git\r\nmkdir -p \/mnt\/server\r\n\r\nREPO_URL=\"${GIT_URL:-https:\/\/github.com\/Pumpkin-MC\/Pumpkin.git}\"\r\n\r\nif [ -n \"$GIT_BRANCH\" ]; then\r\n git clone --depth 1 --single-branch --branch \"$GIT_BRANCH\" \"$REPO_URL\"\r\nelse\r\n git clone --depth 1 --single-branch \"$REPO_URL\"\r\nfi\r\n\r\nif [ -n \"$GIT_COMMIT\" ]; then\r\n git checkout \"$GIT_COMMIT\"\r\nfi\r\n\r\ncd Pumpkin\r\n\r\n#export RUSTFLAGS=\"-C target-feature=-crt-static -C target-cpu=native\"\r\nexport CARGO_BUILD_JOBS=${CARGO_BUILD_JOBS:-default}\r\n\r\nif [[ \"$BUILD_RELEASE\" == \"1\" || \"$BUILD_RELEASE\" == \"true\" ]]; then\r\n cargo build --release\r\n strip target\/release\/pumpkin\r\n cp target\/release\/pumpkin \/mnt\/server\/pumpkin\r\nelse\r\n cargo build\r\n cp target\/debug\/pumpkin \/mnt\/server\/pumpkin\r\nfi",
"container": "docker.io\/rust:1-alpine3.21", "container": "docker.io\/rust:1-alpine3.21",
"entrypoint": "ash" "entrypoint": "ash"
} }

View File

@ -5,6 +5,8 @@ pub use difficulty::Difficulty;
pub use gamemode::GameMode; pub use gamemode::GameMode;
pub use permission::PermissionLvl; pub use permission::PermissionLvl;
use crate::math::vector3::Axis;
pub mod biome; pub mod biome;
pub mod difficulty; pub mod difficulty;
pub mod gamemode; pub mod gamemode;
@ -83,6 +85,26 @@ pub enum ProfileAction {
UsingBannedSkin, UsingBannedSkin,
} }
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum BlockDirection {
Down = 0,
Up,
North,
South,
West,
East,
}
impl BlockDirection {
pub fn get_axis(&self) -> Axis {
match self {
BlockDirection::Up | BlockDirection::Down => Axis::Y,
BlockDirection::North | BlockDirection::South => Axis::Z,
BlockDirection::East | BlockDirection::West => Axis::X,
}
}
}
/// Takes a mutable reference of an index and returns a mutable "slice" where we can mutate both at /// Takes a mutable reference of an index and returns a mutable "slice" where we can mutate both at
/// the same time /// the same time
pub struct MutableSplitSlice<'a, T> { pub struct MutableSplitSlice<'a, T> {

View File

@ -1,6 +1,9 @@
use crate::math::{ use crate::{
position::BlockPos, BlockDirection,
vector3::{Axis, Vector3}, math::{
position::BlockPos,
vector3::{Axis, Vector3},
},
}; };
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -40,6 +43,56 @@ impl BlockBox {
} }
} }
#[expect(clippy::too_many_arguments)]
pub fn rotated(
x: i32,
y: i32,
z: i32,
offset_x: i32,
offset_y: i32,
offset_z: i32,
size_x: i32,
size_y: i32,
size_z: i32,
facing: &BlockDirection,
) -> Self {
match facing {
BlockDirection::North => BlockBox::new(
x + offset_x,
y + offset_y,
z - size_z + 1 + offset_z,
x + size_x - 1 + offset_x,
y + size_y - 1 + offset_y,
z + offset_z,
),
BlockDirection::West => BlockBox::new(
x - size_z + 1 + offset_z,
y + offset_y,
z + offset_x,
x + offset_z,
y + size_y - 1 + offset_y,
z + size_x - 1 + offset_x,
),
BlockDirection::East => BlockBox::new(
x + offset_z,
y + offset_y,
z + offset_x,
x + size_z - 1 + offset_z,
y + size_y - 1 + offset_y,
z + size_x - 1 + offset_x,
),
// Default / South
_ => BlockBox::new(
x + offset_x,
y + offset_y,
z + offset_z,
x + size_x - 1 + offset_x,
y + size_y - 1 + offset_y,
z + size_z - 1 + offset_z,
),
}
}
pub fn from_pos(pos: BlockPos) -> Self { pub fn from_pos(pos: BlockPos) -> Self {
Self { Self {
min: pos.0, min: pos.0,
@ -47,10 +100,70 @@ impl BlockBox {
} }
} }
pub fn move_pos(&mut self, dx: i32, dy: i32, dz: i32) {
self.min.x += dx;
self.min.y += dy;
self.min.z += dz;
self.max.x += dx;
self.max.y += dy;
self.max.z += dz;
}
pub fn contains_pos(&self, pos: &Vector3<i32>) -> bool {
self.contains(pos.x, pos.y, pos.z)
}
pub fn contains(&self, x: i32, y: i32, z: i32) -> bool {
x >= self.min.x
&& x <= self.max.x
&& y >= self.min.y
&& y <= self.max.y
&& z >= self.min.z
&& z <= self.max.z
}
pub fn intersects(&self, other: &Self) -> bool { pub fn intersects(&self, other: &Self) -> bool {
self.min.x < other.max.x self.min.x < other.max.x
&& self.max.x > other.min.x && self.max.x > other.min.x
&& self.min.y < other.max.y && self.min.y < other.max.y
&& self.max.y > other.min.y && self.max.y > other.min.y
} }
pub fn intersects_xz(&self, other: &Self) -> bool {
self.max.x >= other.min.x
&& self.min.x <= other.max.x
&& self.max.z >= other.min.z
&& self.min.z <= other.max.z
}
pub fn intersects_raw_xz(&self, min_x: i32, min_z: i32, max_x: i32, max_z: i32) -> bool {
self.max.x >= min_x && self.min.x <= max_x && self.max.z >= min_z && self.min.z <= max_z
}
pub fn get_block_count_y(&self) -> i32 {
self.max.y - self.min.y + 1
}
pub fn encompass(&mut self, other: &BlockBox) {
self.min.x = self.min.x.min(other.min.x);
self.min.y = self.min.y.min(other.min.y);
self.min.z = self.min.z.min(other.min.z);
self.max.x = self.max.x.max(other.max.x);
self.max.y = self.max.y.max(other.max.y);
self.max.z = self.max.z.max(other.max.z);
}
/// Static helper to find the box covering a collection of boxes
pub fn encompass_all<I>(boxes: I) -> Option<BlockBox>
where
I: IntoIterator<Item = BlockBox>,
{
let mut iter = boxes.into_iter();
let mut result = iter.next()?; // Return None if empty
for b in iter {
result.encompass(&b);
}
Some(result)
}
} }

View File

@ -62,10 +62,10 @@ macro_rules! population_seed_fn {
// TODO: Write unit test for this // TODO: Write unit test for this
#[inline] #[inline]
pub fn get_decorator_seed(population_seed: u64, index: usize, step: usize) -> u64 { pub fn get_decorator_seed(population_seed: u64, index: u64, step: u64) -> u64 {
population_seed population_seed
.wrapping_add(index as u64) .wrapping_add(index)
.wrapping_add(10_000u64.wrapping_mul(step as u64)) .wrapping_add(10_000u64.wrapping_mul(step))
} }
#[inline] #[inline]

View File

@ -66,12 +66,9 @@ mod test {
use crate::{ use crate::{
GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk, GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk,
chunk::palette::BIOME_NETWORK_MAX_BITS, chunk::palette::BIOME_NETWORK_MAX_BITS,
generation::{ generation::noise::router::{
noise::router::{ multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, proto_noise_router::ProtoNoiseRouters,
proto_noise_router::ProtoNoiseRouters,
},
proto_chunk::TerrainCache,
}, },
}; };
@ -109,7 +106,7 @@ mod test {
let surface_settings = GENERATION_SETTINGS let surface_settings = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld) .get(&GeneratorSetting::Overworld)
.unwrap(); .unwrap();
let _terrain_cache = TerrainCache::from_random(&random_config); //let _terrain_cache = TerrainCache::from_random(&random_config);
let default_block = surface_settings.default_block.get_state(); let default_block = surface_settings.default_block.get_state();
for data in expected_data.into_iter() { for data in expected_data.into_iter() {

View File

@ -621,6 +621,8 @@ impl StagedChunkEnum {
} }
const fn get_direct_dependencies(self) -> &'static [StagedChunkEnum] { const fn get_direct_dependencies(self) -> &'static [StagedChunkEnum] {
match self { match self {
// In vanilla StructureStart is first, but since it needs the biome in Vanilla it gets computed in StructureStart and
// the Biome Step, this should be more efficient
Self::Biomes => &[Self::Empty], Self::Biomes => &[Self::Empty],
Self::StructureStart => &[Self::Biomes], Self::StructureStart => &[Self::Biomes],
Self::StructureReferences => &[Self::StructureStart], Self::StructureReferences => &[Self::StructureStart],
@ -774,8 +776,14 @@ impl Chunk {
Proto(chunk) => chunk, Proto(chunk) => chunk,
} }
} }
fn get_proto_chunk(&self) -> &ProtoChunk {
match self {
Chunk::Level(_) => panic!("chunk isn't a ProtoChunk"),
Proto(chunk) => chunk,
}
}
fn upgrade_to_level_chunk(&mut self, dimension: &Dimension) { fn upgrade_to_level_chunk(&mut self, dimension: &Dimension) {
let proto_chunk = self.get_proto_chunk_mut(); let proto_chunk = self.get_proto_chunk();
let total_sections = dimension.height as usize / 16; let total_sections = dimension.height as usize / 16;
let mut sections = ChunkSections::new( let mut sections = ChunkSections::new(
@ -853,7 +861,7 @@ impl Chunk {
struct Cache { struct Cache {
x: i32, x: i32,
y: i32, z: i32,
size: i32, size: i32,
pub chunks: Vec<Chunk>, pub chunks: Vec<Chunk>,
} }
@ -903,6 +911,33 @@ impl BlockAccessor for Cache {
} }
impl GenerationCache for Cache { impl GenerationCache for Cache {
fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut ProtoChunk> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx >= 0 && dx < self.size && dz >= 0 && dz < self.size {
Some(self.chunks[(dx * self.size + dz) as usize].get_proto_chunk_mut())
} else {
None
}
}
fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx >= 0 && dx < self.size && dz >= 0 && dz < self.size {
Some(self.chunks[(dx * self.size + dz) as usize].get_proto_chunk())
} else {
None
}
}
fn get_center_chunk(&self) -> &ProtoChunk {
let mid = ((self.size * self.size) >> 1) as usize;
self.chunks[mid].get_proto_chunk()
}
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk { fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk {
let mid = ((self.size * self.size) >> 1) as usize; let mid = ((self.size * self.size) >> 1) as usize;
self.chunks[mid].get_proto_chunk_mut() self.chunks[mid].get_proto_chunk_mut()
@ -941,7 +976,7 @@ impl GenerationCache for Cache {
fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState { fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState {
let dx = (pos.x >> 4) - self.x; let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.y; let dz = (pos.z >> 4) - self.z;
// debug_assert!(dx < self.size && dz < self.size); // debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0); // debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
@ -949,7 +984,7 @@ impl GenerationCache for Cache {
log::error!( log::error!(
"illegal get_block_state {pos:?} cache pos ({}, {}) size {}", "illegal get_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.x,
self.y, self.z,
self.size self.size
); );
return RawBlockState::AIR; return RawBlockState::AIR;
@ -969,7 +1004,7 @@ impl GenerationCache for Cache {
} }
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) { fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) {
let dx = (pos.x >> 4) - self.x; let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.y; let dz = (pos.z >> 4) - self.z;
// debug_assert!(dx < self.size && dz < self.size); // debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0); // debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
@ -977,7 +1012,7 @@ impl GenerationCache for Cache {
log::error!( log::error!(
"illegal set_block_state {pos:?} cache pos ({}, {}) size {}", "illegal set_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.x,
self.y, self.z,
self.size self.size
); );
return; return;
@ -1013,7 +1048,7 @@ impl GenerationCache for Cache {
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 { fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x; let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y; let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size); debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
@ -1032,7 +1067,7 @@ impl GenerationCache for Cache {
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 { fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x; let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y; let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size); debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
@ -1051,7 +1086,7 @@ impl GenerationCache for Cache {
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 { fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x; let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y; let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size); debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
@ -1070,7 +1105,7 @@ impl GenerationCache for Cache {
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 { fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x; let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y; let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size); debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
@ -1083,7 +1118,7 @@ impl GenerationCache for Cache {
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome { fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome {
let dx = (x >> 4) - self.x; let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.y; let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size); debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
@ -1109,10 +1144,10 @@ impl GenerationCache for Cache {
} }
impl Cache { impl Cache {
fn new(x: i32, y: i32, size: i32) -> Cache { fn new(x: i32, z: i32, size: i32) -> Cache {
Cache { Cache {
x, x,
y, z,
size, size,
chunks: Vec::with_capacity((size * size) as usize), chunks: Vec::with_capacity((size * size) as usize),
} }
@ -1133,10 +1168,8 @@ impl Cache {
Empty => panic!("empty stage"), Empty => panic!("empty stage"),
StagedChunkEnum::StructureStart => self.chunks[mid] StagedChunkEnum::StructureStart => self.chunks[mid]
.get_proto_chunk_mut() .get_proto_chunk_mut()
.set_structure_starts(random_config), .set_structure_starts(random_config, settings),
StagedChunkEnum::StructureReferences => self.chunks[mid] StagedChunkEnum::StructureReferences => ProtoChunk::set_structure_references(self),
.get_proto_chunk_mut()
.set_structure_references(),
Biomes => self.chunks[mid] Biomes => self.chunks[mid]
.get_proto_chunk_mut() .get_proto_chunk_mut()
.step_to_biomes(dimension, noise_router), .step_to_biomes(dimension, noise_router),
@ -1155,8 +1188,9 @@ impl Cache {
ProtoChunk::generate_features_and_structure(self, block_registry, random_config) ProtoChunk::generate_features_and_structure(self, block_registry, random_config)
} }
Full => { Full => {
debug_assert_eq!(self.chunks[mid].get_proto_chunk_mut().stage, Features); let chunk = self.chunks[mid].get_proto_chunk_mut();
self.chunks[mid].get_proto_chunk_mut().stage = Full; debug_assert_eq!(chunk.stage, Features);
chunk.stage = Full;
self.chunks[mid].upgrade_to_level_chunk(&dimension); self.chunks[mid].upgrade_to_level_chunk(&dimension);
} }
StagedChunkEnum::None => {} StagedChunkEnum::None => {}
@ -1532,9 +1566,9 @@ impl GenerationSchedule {
let dependency = stage.get_direct_dependencies(); let dependency = stage.get_direct_dependencies();
let radius = stage.get_direct_radius(); let radius = stage.get_direct_radius();
for dx in -radius..=radius { for dx in -radius..=radius {
for dy in -radius..=radius { for dz in -radius..=radius {
let new_pos = pos.add_raw(dx, dy); let new_pos = pos.add_raw(dx, dz);
let req_stage = dependency[dx.abs().max(dy.abs()) as usize]; let req_stage = dependency[dx.abs().max(dz.abs()) as usize];
if new_pos == pos { if new_pos == pos {
// TODO // TODO
holder.occupied_by = self holder.occupied_by = self
@ -1883,7 +1917,7 @@ impl GenerationSchedule {
let mut dx = 0; let mut dx = 0;
let mut dy = 0; let mut dy = 0;
for chunk in data.chunks { for chunk in data.chunks {
let new_pos = ChunkPos::new(data.x + dx, data.y + dy); let new_pos = ChunkPos::new(data.x + dx, data.z + dy);
match chunk { match chunk {
Chunk::Level(chunk) => { Chunk::Level(chunk) => {
let mut holder = self.chunk_map.remove(&new_pos).unwrap(); let mut holder = self.chunk_map.remove(&new_pos).unwrap();

View File

@ -1,5 +1,4 @@
use core::f32; use core::f32;
use std::collections::HashSet;
use pumpkin_data::{BlockDirection, BlockState}; use pumpkin_data::{BlockDirection, BlockState};
use pumpkin_util::{ use pumpkin_util::{
@ -84,7 +83,8 @@ impl OreFeature {
vertical_size: i32, vertical_size: i32,
) -> bool { ) -> bool {
let mut placed_blocks_count = 0; let mut placed_blocks_count = 0;
let mut bit_set = HashSet::new(); let bitset_size = (horizontal_size * vertical_size * horizontal_size) as usize;
let mut bit_set = vec![false; bitset_size];
let mut mutable_pos = BlockPos::ZERO; let mut mutable_pos = BlockPos::ZERO;
let j = self.size; let j = self.size;
let mut ds = vec![0.0; (j * 4) as usize]; let mut ds = vec![0.0; (j * 4) as usize];
@ -151,6 +151,8 @@ impl OreFeature {
if u_val * u_val + w_val * w_val >= 1.0 { if u_val * u_val + w_val * w_val >= 1.0 {
continue; continue;
} }
let base_idx = (t_val - x_bound) + (v_val - y_bound) * horizontal_size;
for aa_val in p_bound..=s_bound { for aa_val in p_bound..=s_bound {
let ab_val = (aa_val as f64 + 0.5 - h_val) / d_val; let ab_val = (aa_val as f64 + 0.5 - h_val) / d_val;
if u_val * u_val + w_val * w_val + ab_val * ab_val >= 1.0 { if u_val * u_val + w_val * w_val + ab_val * ab_val >= 1.0 {
@ -160,14 +162,13 @@ impl OreFeature {
continue; continue;
} }
let ac = (t_val - x_bound) let ac = (base_idx + (aa_val - z_bound) * horizontal_size * vertical_size)
+ (v_val - y_bound) * horizontal_size as usize;
+ (aa_val - z_bound) * horizontal_size * vertical_size;
if bit_set.contains(&ac) { if bit_set[ac] {
continue; continue;
} }
bit_set.insert(ac); bit_set[ac] = true;
mutable_pos.0.x = t_val; mutable_pos.0.x = t_val;
mutable_pos.0.y = v_val; mutable_pos.0.y = v_val;
@ -177,12 +178,13 @@ impl OreFeature {
// continue; // continue;
// } // }
let ad = t_val; // let ad = t_val;
let ae = v_val; // let ae = v_val;
let af = aa_val; // let af = aa_val;
// TODO: using a section would be faster
let block_state = let pos_vec = Vector3::new(t_val, v_val, aa_val);
GenerationCache::get_block_state(chunk, &Vector3::new(ad, ae, af)); let block_state = GenerationCache::get_block_state(chunk, &pos_vec);
for target in &self.targets { for target in &self.targets {
if self.should_place( if self.should_place(
@ -192,10 +194,7 @@ impl OreFeature {
target, target,
&mut mutable_pos, &mut mutable_pos,
) { ) {
chunk.set_block_state( chunk.set_block_state(&pos_vec, target.state.get_state());
&Vector3::new(ad, ae, af),
target.state.get_state(),
);
placed_blocks_count += 1; placed_blocks_count += 1;
break; // Equivalent to 'continue block11;' break; // Equivalent to 'continue block11;'
} }

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::pin::Pin; use std::pin::Pin;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
@ -7,7 +7,7 @@ use pumpkin_data::tag;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockState, block_properties::blocks_movement, chunk::Biome, tag::Taggable, Block, BlockState, block_properties::blocks_movement, chunk::Biome, tag::Taggable,
}; };
use pumpkin_util::math::block_box::BlockBox; use pumpkin_util::random::{RandomImpl, get_carver_seed};
use pumpkin_util::{ use pumpkin_util::{
HeightMap, HeightMap,
math::{position::BlockPos, vector3::Vector3}, math::{position::BlockPos, vector3::Vector3},
@ -37,7 +37,7 @@ use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions; use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
use crate::generation::structure::placement::StructurePlacementCalculator; use crate::generation::structure::placement::StructurePlacementCalculator;
use crate::generation::structure::structures::StructureInstance; use crate::generation::structure::structures::StructureInstance;
use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, Structure}; use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry};
use crate::{ use crate::{
BlockStateId, ProtoNoiseRouters, BlockStateId, ProtoNoiseRouters,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier}, biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier},
@ -49,6 +49,11 @@ use crate::{
pub trait GenerationCache: HeightLimitView + BlockAccessor { pub trait GenerationCache: HeightLimitView + BlockAccessor {
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk; fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
fn get_center_chunk(&self) -> &ProtoChunk;
fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut ProtoChunk>;
fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk>;
fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState; fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState;
fn get_fluid_and_fluid_state(&self, position: &Vector3<i32>) -> (Fluid, FluidState); fn get_fluid_and_fluid_state(&self, position: &Vector3<i32>) -> (Fluid, FluidState);
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState); fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState);
@ -134,8 +139,7 @@ pub struct ProtoChunk {
flat_ocean_floor_height_map: Box<[i16]>, flat_ocean_floor_height_map: Box<[i16]>,
pub flat_motion_blocking_height_map: Box<[i16]>, pub flat_motion_blocking_height_map: Box<[i16]>,
pub flat_motion_blocking_no_leaves_height_map: Box<[i16]>, pub flat_motion_blocking_no_leaves_height_map: Box<[i16]>,
// may want to use chunk status structure_starts: HashMap<StructureKeys, StructureInstance>,
structure_starts: HashMap<Structure, StructureInstance>,
// Height of the chunk for indexing // Height of the chunk for indexing
height: u16, height: u16,
@ -463,7 +467,8 @@ impl ProtoChunk {
let blocks_movement = blocks_movement(block_state, block); let blocks_movement = blocks_movement(block_state, block);
if blocks_movement { if blocks_movement {
self.maybe_update_ocean_floor_height_map(local_x, pos.y, local_z); self.maybe_update_ocean_floor_height_map(local_x, pos.y, local_z);
} else if blocks_movement || block_state.is_liquid() { }
if blocks_movement || block_state.is_liquid() {
self.maybe_update_motion_blocking_height_map(local_x, pos.y, local_z); self.maybe_update_motion_blocking_height_map(local_x, pos.y, local_z);
if !block.has_tag(&tag::Block::MINECRAFT_LEAVES) { if !block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
{ {
@ -634,8 +639,6 @@ impl ProtoChunk {
dimension, dimension,
) )
}; };
//dbg!("Populating biome: {:?} -> {:?}", biome_pos, biome);
let index = self.local_biome_pos_to_biome_index( let index = self.local_biome_pos_to_biome_index(
x, x,
start_biome_y + y - biome_coords::from_block(min_y as i32), start_biome_y + y - biome_coords::from_block(min_y as i32),
@ -906,96 +909,221 @@ impl ProtoChunk {
block_registry: &dyn BlockRegistryExt, block_registry: &dyn BlockRegistryExt,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,
) { ) {
let chunk = cache.get_center_chunk_mut(); let (center_x, center_z, min_y, height, biomes_in_chunk) = {
debug_assert_eq!(chunk.stage, StagedChunkEnum::Surface); let chunk = cache.get_center_chunk();
let min_y = chunk.bottom_y(); let mut biomes = HashSet::new();
let height = chunk.height(); // bad
for biome in chunk.flat_biome_map.clone() {
biomes.insert(biome);
}
let bottom_section = section_coords::block_to_section(min_y) as i32; (chunk.x, chunk.z, chunk.bottom_y(), chunk.height(), biomes)
let block_pos = BlockPos(Vector3::new( };
section_coords::section_to_block(chunk.x),
bottom_section,
section_coords::section_to_block(chunk.z),
));
let block_pos = BlockPos::new(
section_coords::section_to_block(center_x),
section_coords::block_to_section(min_y) as i32,
section_coords::section_to_block(center_z),
);
let population_seed = let population_seed =
Xoroshiro::get_population_seed(random_config.seed, block_pos.0.x, block_pos.0.z); Xoroshiro::get_population_seed(random_config.seed, block_pos.0.x, block_pos.0.z);
let _chunk_box = chunk.get_block_box_for_chunk(); const MAX_STEPS: usize = 11;
for (_structure_config, instance) in chunk.structure_starts.clone() {
dbg!("generating"); for step in 0..MAX_STEPS {
match instance { let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
StructureInstance::Start(pos, _stype) => {
// Use the collector directly to place blocks
pos.collector
.generate_in_chunk(chunk, random_config.seed as i64);
}
StructureInstance::Reference(_start_block_pos) => {}
}
}
// TODO: This needs to be different depending on what biomes are in the chunk -> affects the
// random
for (name, feature) in PLACED_FEATURES.iter() {
// TODO: Properly set index and step
let decorator_seed = get_decorator_seed(population_seed, 0, 0);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed)); let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
feature.generate(
cache,
block_registry,
min_y,
height,
name,
&mut random,
block_pos,
);
}
let chunk = cache.get_center_chunk_mut();
chunk.stage = StagedChunkEnum::Features;
}
pub fn set_structure_starts(&mut self, random_config: &GlobalRandomConfig) { let mut tasks = Vec::new();
for (_set_name, set) in STRUCTURE_SETS.iter() {
let calculator = StructurePlacementCalculator {
seed: random_config.seed as i64,
};
if !set.placement.should_generate(calculator, self.x, self.z) {
continue;
}
if set.structures.len() == 1
&& let Some(structure_entry) = set.structures.first()
{ {
let position = structure_entry.structure.try_generate( let center_chunk = cache.get_center_chunk();
random_config.seed as i64, for (id, instance) in &center_chunk.structure_starts {
self.x, // Check if this structure belongs to the current step
self.z, if let Some(s) = STRUCTURES.get(id)
self, && s.step.ordinal() != step
); {
continue;
}
if let Some(pos) = position match instance {
&& let Some(structure_config) = STRUCTURES.get(&structure_entry.structure) StructureInstance::Start(pos) => {
{ tasks.push(pos.collector.clone());
self.structure_starts.insert( }
structure_config.clone(), StructureInstance::Reference(origin_pos) => {
StructureInstance::Start(pos, structure_entry.structure.clone()), let origin_chunk_x = origin_pos.0.x >> 4;
let origin_chunk_z = origin_pos.0.z >> 4;
if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z)
&& let Some(StructureInstance::Start(pos)) =
neighbor.structure_starts.get(id)
{
tasks.push(pos.collector.clone());
}
}
}
}
}
let chunk = cache.get_center_chunk_mut();
for collector_arc in tasks {
let mut collector = collector_arc.lock().unwrap();
collector.generate_in_chunk(chunk, &mut random, random_config.seed as i64);
}
let mut features_to_run = HashSet::new();
for biome in &biomes_in_chunk {
if let Some(features_at_step) = biome.features.get(step) {
for feature_id in *features_at_step {
features_to_run
.insert(feature_id.strip_prefix("minecraft:").unwrap_or(feature_id));
}
}
}
// let mut sorted_features: Vec<_> = features_to_run.into_iter().collect();
// sorted_features.sort_by_key(|f| f.registry_index());
for (p, feature_id) in features_to_run.into_iter().enumerate() {
if let Some(feature) = PLACED_FEATURES.get(feature_id) {
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
let mut random =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
feature.generate(
cache,
block_registry,
min_y,
height,
feature_id,
&mut random,
block_pos,
); );
} }
} }
// TODO add weight }
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
}
pub fn set_structure_starts(
&mut self,
random_config: &GlobalRandomConfig,
settings: &GenerationSettings,
) {
let seed = random_config.seed;
let calculator = StructurePlacementCalculator::new(seed as i64);
for (_set_name, set) in STRUCTURE_SETS.iter() {
if !set.placement.should_generate(&calculator, self.x, self.z) {
continue;
}
if set.structures.len() == 1 {
if let Some(entry) = set.structures.first() {
self.try_set_structure_start(settings.sea_level, entry, random_config);
}
continue;
}
let mut candidates = set.structures.clone();
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed));
let carver_seed = get_carver_seed(&mut random, seed, self.x, self.z);
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum();
while !candidates.is_empty() {
let mut roll = random.next_bounded_i32(total_weight as i32);
let mut selected_idx = 0;
for (i, entry) in candidates.iter().enumerate() {
roll -= entry.weight as i32;
if roll < 0 {
selected_idx = i;
break;
}
}
let selected_entry = &candidates[selected_idx];
if self.try_set_structure_start(settings.sea_level, selected_entry, random_config) {
break;
}
let failed_entry = candidates.remove(selected_idx);
total_weight -= failed_entry.weight;
}
} }
self.stage = StagedChunkEnum::StructureStart; self.stage = StagedChunkEnum::StructureStart;
} }
pub fn set_structure_references(&mut self) { fn try_set_structure_start(
self.stage = StagedChunkEnum::StructureReferences; &mut self,
sea_level: i32,
entry: &WeightedEntry,
random_config: &GlobalRandomConfig,
) -> bool {
if let Some(structure) = STRUCTURES.get(&entry.structure) {
let position =
entry
.structure
.try_generate(structure, random_config.seed as i64, self, sea_level);
if let Some(pos) = position {
self.structure_starts
.insert(entry.structure.clone(), StructureInstance::Start(pos));
return true;
}
}
false
} }
fn get_block_box_for_chunk(&self) -> BlockBox { pub fn set_structure_references<T: GenerationCache>(cache: &mut T) {
let x = start_block_x(self.x); let (center_x, center_z) = {
let z = start_block_z(self.z); let chunk = cache.get_center_chunk();
let bottom = self.bottom_y() as i32 + 1; (chunk.x, chunk.z)
let top = self.top_y() as i32; };
BlockBox::new(x, bottom, z, x + 15, top, z + 15)
let start_block_x = chunk_pos::start_block_x(center_x);
let start_block_z = chunk_pos::start_block_z(center_z);
let end_block_x = start_block_x + 15;
let end_block_z = start_block_z + 15;
let radius = 8;
let mut references_to_add = Vec::new();
for x in (center_x - radius)..=(center_x + radius) {
for z in (center_z - radius)..=(center_z + radius) {
if let Some(neighbor_chunk) = cache.get_chunk(x, z) {
for (structure_key, instance) in &neighbor_chunk.structure_starts {
if let StructureInstance::Start(start_data) = instance {
let bbox = start_data.get_bounding_box();
if bbox.intersects_raw_xz(
start_block_x,
start_block_z,
end_block_x,
end_block_z,
) {
references_to_add
.push((structure_key.clone(), start_data.start_pos));
}
}
}
}
}
}
let center_chunk = cache.get_center_chunk_mut();
for (key, pos) in references_to_add {
center_chunk
.structure_starts
.entry(key)
.or_insert(StructureInstance::Reference(pos));
}
center_chunk.stage = StagedChunkEnum::StructureReferences;
} }
fn start_cell_x(&self, horizontal_cell_block_count: u8) -> i32 { fn start_cell_x(&self, horizontal_cell_block_count: u8) -> i32 {

View File

@ -11,14 +11,17 @@ use crate::{
structure::{ structure::{
placement::StructurePlacement, placement::StructurePlacement,
structures::{ structures::{
StructureGenerator, StructurePosition, buried_treasure::BuriedTreasureGenerator, StructureGenerator, StructureGeneratorContext, StructurePosition,
nether_fortress::NetherFortressGenerator, swamp_hut::SwampHutGenerator, buried_treasure::BuriedTreasureGenerator, create_chunk_random,
stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator,
}, },
}, },
}, },
}; };
pub mod piece;
pub mod placement; pub mod placement;
pub mod shiftable_piece;
pub mod structures; pub mod structures;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -27,49 +30,49 @@ pub struct StructureSet {
pub structures: Vec<WeightedEntry>, pub structures: Vec<WeightedEntry>,
} }
#[derive(Deserialize)] #[derive(Deserialize, Clone)]
pub struct WeightedEntry { pub struct WeightedEntry {
pub structure: StructureKeys, pub structure: StructureKeys,
pub weight: u32, pub weight: u32,
} }
#[derive(Deserialize, Clone)] // #[derive(Deserialize)]
pub enum StructureType { // pub enum StructureType {
#[serde(rename = "minecraft:buried_treasure")] // #[serde(rename = "minecraft:buried_treasure")]
BuriedTreasure(BuriedTreasureGenerator), // BuriedTreasure(BuriedTreasureGenerator),
#[serde(rename = "minecraft:desert_pyramid")] // #[serde(rename = "minecraft:desert_pyramid")]
DesertPyramid, // DesertPyramid,
#[serde(rename = "minecraft:end_city")] // #[serde(rename = "minecraft:end_city")]
EndCity, // EndCity,
#[serde(rename = "minecraft:fortress")] // #[serde(rename = "minecraft:fortress")]
NetherFortress(NetherFortressGenerator), // NetherFortress(NetherFortressGenerator),
#[serde(rename = "minecraft:igloo")] // #[serde(rename = "minecraft:igloo")]
Igloo, // Igloo,
#[serde(rename = "minecraft:jigsaw")] // #[serde(rename = "minecraft:jigsaw")]
Jigsaw, // Jigsaw,
#[serde(rename = "minecraft:jungle_temple")] // #[serde(rename = "minecraft:jungle_temple")]
JungleTemple, // JungleTemple,
#[serde(rename = "minecraft:mineshaft")] // #[serde(rename = "minecraft:mineshaft")]
Mineshaft, // Mineshaft,
#[serde(rename = "minecraft:nether_fossil")] // #[serde(rename = "minecraft:nether_fossil")]
NetherFossil, // NetherFossil,
#[serde(rename = "minecraft:ocean_monument")] // #[serde(rename = "minecraft:ocean_monument")]
OceanMonument, // OceanMonument,
#[serde(rename = "minecraft:ocean_ruin")] // #[serde(rename = "minecraft:ocean_ruin")]
OceanRuin, // OceanRuin,
#[serde(rename = "minecraft:ruined_portal")] // #[serde(rename = "minecraft:ruined_portal")]
RuinedPortal, // RuinedPortal,
#[serde(rename = "minecraft:shipwreck")] // #[serde(rename = "minecraft:shipwreck")]
Shipwreck, // Shipwreck,
#[serde(rename = "minecraft:stronghold")] // #[serde(rename = "minecraft:stronghold")]
Stronghold, // Stronghold,
#[serde(rename = "minecraft:swamp_hut")] // #[serde(rename = "minecraft:swamp_hut")]
SwampHut(SwampHutGenerator), // SwampHut(SwampHutGenerator),
#[serde(rename = "minecraft:woodland_mansion")] // #[serde(rename = "minecraft:woodland_mansion")]
WoodlandMansion, // WoodlandMansion,
} // }
#[derive(Deserialize, Clone, PartialEq, Eq, Hash)] #[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub enum StructureKeys { pub enum StructureKeys {
#[serde(rename = "minecraft:pillager_outpost")] #[serde(rename = "minecraft:pillager_outpost")]
PillagerOutpost, PillagerOutpost,
@ -147,44 +150,49 @@ pub struct EmptyStruct {}
impl StructureKeys { impl StructureKeys {
pub fn try_generate( pub fn try_generate(
&self, &self,
structure: &Structure,
seed: i64, seed: i64,
chunk_x: i32, chunk: &ProtoChunk,
chunk_z: i32, sea_level: i32,
chunk_ref_for_biomes: &ProtoChunk,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let random = create_chunk_random(seed, chunk.x, chunk.z);
let context = StructureGeneratorContext {
seed,
chunk_x: chunk.x,
chunk_z: chunk.z,
random,
sea_level,
min_y: chunk.bottom_y() as i32,
};
let structure_pos = match self { let structure_pos = match self {
StructureKeys::BuriedTreasure => BuriedTreasureGenerator::get_structure_position( StructureKeys::BuriedTreasure => {
&BuriedTreasureGenerator, BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context)
seed, }
chunk_x, // StructureKeys::Fortress => {
chunk_z, // NetherFortressGenerator::get_structure_position(&NetherFortressGenerator, context)
), // }
StructureKeys::Fortress => NetherFortressGenerator::get_structure_position( StructureKeys::SwampHut => {
&NetherFortressGenerator, SwampHutGenerator::get_structure_position(&SwampHutGenerator, context)
seed, }
chunk_x, StructureKeys::Stronghold => {
chunk_z, StrongholdGenerator::get_structure_position(&StrongholdGenerator, context)
), }
StructureKeys::SwampHut => SwampHutGenerator::get_structure_position( // StructureKeys::DesertPyramid => DesertTempleGenerator::get_structure_position(
&SwampHutGenerator, // &DesertTempleGenerator,
seed, // context
chunk_x, // ),
chunk_z,
),
// TODO // TODO
_ => None, _ => None,
}; };
if let Some(pos) = structure_pos if let Some(pos) = structure_pos {
&& let Some(structure_data) = STRUCTURES.get(self)
{
// Get the biome at the structure's starting position // Get the biome at the structure's starting position
let current_biome = chunk_ref_for_biomes.get_biome( let current_biome = chunk.get_biome(
biome_coords::from_block(pos.start_pos.0.x), biome_coords::from_block(pos.start_pos.0.x),
biome_coords::from_block(pos.start_pos.0.y), biome_coords::from_block(pos.start_pos.0.y),
biome_coords::from_block(pos.start_pos.0.z), biome_coords::from_block(pos.start_pos.0.z),
); );
let biomes = Biome::get_tag_values(&structure_data.biomes).unwrap(); let biomes = Biome::get_tag_values(&structure.biomes).unwrap();
// Check if the biome is allowed for this structure // Check if the biome is allowed for this structure
if biomes.contains(&current_biome.registry_id) { if biomes.contains(&current_biome.registry_id) {
return Some(pos); return Some(pos);
@ -195,17 +203,32 @@ impl StructureKeys {
} }
} }
pub fn generate_structure_pieces(
structure_pos: &StructurePosition,
chunk: &mut ProtoChunk,
seed: i64,
) {
structure_pos.collector.generate_in_chunk(chunk, seed);
}
#[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)] #[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Structure { pub struct Structure {
biomes: String, pub biomes: String,
pub step: GenerationStep,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GenerationStep {
RawGeneration,
Lakes,
LocalModifications,
UndergroundStructures,
SurfaceStructures,
Strongholds,
UndergroundOres,
UndergroundDecoration,
FluidSprings,
VegetalDecoration,
TopLayerModification,
}
impl GenerationStep {
pub fn ordinal(&self) -> usize {
*self as usize
}
} }
pub static STRUCTURES: LazyLock<HashMap<StructureKeys, Structure>> = LazyLock::new( pub static STRUCTURES: LazyLock<HashMap<StructureKeys, Structure>> = LazyLock::new(

View File

@ -0,0 +1,70 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StructurePieceType {
// Mineshaft
MineshaftCorridor,
MineshaftCrossing,
MineshaftRoom,
MineshaftStairs,
// Nether Fortress
NetherFortressBridgeCrossing,
NetherFortressBridgeEnd,
NetherFortressBridge,
NetherFortressCorridorStairs,
NetherFortressCorridorBalcony,
NetherFortressCorridorExit,
NetherFortressCorridorCrossing,
NetherFortressCorridorLeftTurn,
NetherFortressSmallCorridor,
NetherFortressCorridorRightTurn,
NetherFortressCorridorNetherWartsRoom,
NetherFortressBridgePlatform,
NetherFortressBridgeSmallCrossing,
NetherFortressBridgeStairs,
NetherFortressStart,
// Stronghold
StrongholdChestCorridor,
StrongholdSmallCorridor,
StrongholdFiveWayCrossing,
StrongholdLeftTurn,
StrongholdLibrary,
StrongholdPortalRoom,
StrongholdPrisonHall,
StrongholdRightTurn,
StrongholdSquareRoom,
StrongholdSpiralStaircase,
StrongholdStart,
StrongholdCorridor,
StrongholdStairs,
// Overworld/General
JungleTemple,
OceanTemple,
Igloo,
RuinedPortal,
SwampHut,
DesertTemple,
// Ocean Monument
OceanMonumentBase,
OceanMonumentCoreRoom,
OceanMonumentDoubleXRoom,
OceanMonumentDoubleXYRoom,
OceanMonumentDoubleYRoom,
OceanMonumentDoubleYZRoom,
OceanMonumentDoubleZRoom,
OceanMonumentEntryRoom,
OceanMonumentPenthouse,
OceanMonumentSimpleRoom,
OceanMonumentSimpleTopRoom,
OceanMonumentWingRoom,
// End / Other
EndCity,
WoodlandMansion,
BuriedTreasure,
Shipwreck,
NetherFossil,
Jigsaw,
}

View File

@ -19,12 +19,12 @@ pub struct StructurePlacement {
impl StructurePlacement { impl StructurePlacement {
pub fn should_generate( pub fn should_generate(
&self, &self,
calculator: StructurePlacementCalculator, calculator: &StructurePlacementCalculator,
chunk_x: i32, chunk_x: i32,
chunk_z: i32, chunk_z: i32,
) -> bool { ) -> bool {
self.r#type self.r#type
.is_start_chunk(&calculator, chunk_x, chunk_z, self.salt) .is_start_chunk(calculator, chunk_x, chunk_z, self.salt)
&& self.apply_frequency_reduction(calculator.seed, chunk_x, chunk_z) && self.apply_frequency_reduction(calculator.seed, chunk_x, chunk_z)
// TODO: add exclusion_zone, only used for pillager_outposts // TODO: add exclusion_zone, only used for pillager_outposts
} }
@ -100,7 +100,7 @@ pub enum StructurePlacementType {
#[serde(rename = "minecraft:random_spread")] #[serde(rename = "minecraft:random_spread")]
RandomSpread(RandomSpreadStructurePlacement), RandomSpread(RandomSpreadStructurePlacement),
#[serde(rename = "minecraft:concentric_rings")] #[serde(rename = "minecraft:concentric_rings")]
ConcentricRings, ConcentricRings(ConcentricRingsStructurePlacement),
} }
impl StructurePlacementType { impl StructurePlacementType {
@ -115,7 +115,10 @@ impl StructurePlacementType {
StructurePlacementType::RandomSpread(placement) => { StructurePlacementType::RandomSpread(placement) => {
placement.is_start_chunk(calculator, chunk_x, chunk_z, salt) placement.is_start_chunk(calculator, chunk_x, chunk_z, salt)
} }
StructurePlacementType::ConcentricRings => false, // TODO, This is needed for Stronghold, since it is placed in rings // This is needed for Stronghold, since it is placed in rings
StructurePlacementType::ConcentricRings(placement) => {
placement.is_start_chunk(calculator, chunk_x, chunk_z, salt)
}
} }
} }
} }
@ -127,6 +130,45 @@ pub struct RandomSpreadStructurePlacement {
spread_type: Option<SpreadType>, spread_type: Option<SpreadType>,
} }
#[derive(Deserialize)]
pub struct ConcentricRingsStructurePlacement {
spread: i32,
distance: i32,
count: i32,
preferred_biomes: String,
}
impl ConcentricRingsStructurePlacement {
pub fn is_start_chunk(
&self,
_calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> bool {
// TODO: this is entirely wrong, but the original logic is too complex for now
let x = (chunk_x << 4) as f64;
let z = (chunk_z << 4) as f64;
let distance_sq = x * x + z * z;
let distance = distance_sq.sqrt();
let ring_width = 1000.0;
let ring_gap = 2000.0;
let in_ring = (distance % (ring_width + ring_gap)) < ring_width;
if !in_ring {
return false;
}
let seed = (chunk_x as i64).wrapping_mul(341873128712)
^ (chunk_z as i64).wrapping_mul(132897987541)
^ (salt as i64);
(seed.abs() % 400) == 0
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum SpreadType { pub enum SpreadType {
@ -174,6 +216,12 @@ pub struct StructurePlacementCalculator {
pub seed: i64, pub seed: i64,
} }
impl StructurePlacementCalculator {
pub fn new(seed: i64) -> Self {
Self { seed }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pumpkin_util::random::{ use pumpkin_util::random::{

View File

@ -0,0 +1,116 @@
use pumpkin_util::{
HeightMap,
math::{block_box::BlockBox, vector3::Axis},
};
use crate::{
ProtoChunk,
generation::structure::{piece::StructurePieceType, structures::StructurePiece},
};
pub const DEFAULT_H_POS: i32 = -1;
#[derive(Clone)]
pub struct ShiftableStructurePiece {
pub piece: StructurePiece,
h_pos: i32,
width: i32,
height: i32,
}
impl ShiftableStructurePiece {
#[expect(clippy::too_many_arguments)]
pub fn new(
r#type: StructurePieceType,
x: i32,
y: i32,
z: i32,
width: i32,
height: i32,
depth: i32,
axis: Axis,
) -> Self {
Self {
piece: StructurePiece::new(
r#type,
BlockBox::create_box(x, y, z, axis, width, height, depth),
0,
),
h_pos: DEFAULT_H_POS,
width,
height,
}
}
pub fn adjust_to_average_height(&mut self, chunk: &ProtoChunk) -> bool {
let bounding_box = self.piece.bounding_box;
if self.h_pos >= 0 {
return true;
}
let mut sum_y = 0;
let mut count = 0;
for z in bounding_box.min.z..=bounding_box.max.z {
for x in bounding_box.min.x..=bounding_box.max.x {
let y = chunk.get_top_y(&HeightMap::OceanFloorWg, x, z);
sum_y += y;
count += 1;
}
}
if count == 0 {
return false;
}
self.h_pos = sum_y / count;
let current_min_y = bounding_box.min.y;
let offset = self.h_pos - current_min_y;
self.piece.bounding_box.min.y += offset;
self.piece.bounding_box.max.y += offset;
true
}
pub fn adjust_to_min_height(&mut self, chunk: &ProtoChunk, y_offset: i32) -> bool {
let bounding_box = self.piece.bounding_box;
if self.h_pos >= 0 {
return true;
}
let mut min_y = i32::MAX;
let mut found_any = false;
// Iterate over the X/Z footprint of the bounding box
for z in bounding_box.min.z..=bounding_box.max.z {
for x in bounding_box.min.x..=bounding_box.max.x {
// Vanilla uses MOTION_BLOCKING_NO_LEAVES.
// In Pumpkin/Standard generation, OceanFloorWg is usually the closest equivalent for terrain height.
let y = chunk.get_top_y(&HeightMap::OceanFloorWg, x, z);
if y < min_y {
min_y = y;
}
found_any = true;
}
}
// Should only return false if the bounding box has no area (width or depth is 0)
if !found_any {
return false;
}
self.h_pos = min_y;
// Calculate shift: (Target Height - Current Height) + Offset
let current_y = bounding_box.min.y;
let shift_y = (self.h_pos - current_y) + y_offset;
self.piece.bounding_box.min.y += shift_y;
self.piece.bounding_box.max.y += shift_y;
true
}
}

View File

@ -1,7 +1,10 @@
use std::sync::Arc;
use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{ use pumpkin_util::{
HeightMap, HeightMap,
math::{block_box::BlockBox, position::BlockPos}, math::{block_box::BlockBox, position::BlockPos},
random::RandomGenerator,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -9,59 +12,61 @@ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::{
positions::chunk_pos::{get_center_x, get_center_z}, positions::chunk_pos::{get_center_x, get_center_z},
structure::structures::{ structure::{
StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, piece::StructurePieceType,
structures::{
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
StructurePiecesCollector, StructurePosition,
},
}, },
}, },
}; };
#[derive(Deserialize, Clone)] #[derive(Deserialize)]
pub struct BuriedTreasureGenerator; pub struct BuriedTreasureGenerator;
impl StructureGenerator for BuriedTreasureGenerator { impl StructureGenerator for BuriedTreasureGenerator {
fn get_structure_position( fn get_structure_position(
&self, &self,
_seed: i64, context: StructureGeneratorContext,
chunk_x: i32,
chunk_z: i32,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let x = get_center_x(chunk_x); let x = get_center_x(context.chunk_x);
let z = get_center_z(chunk_z); let z = get_center_z(context.chunk_z);
let bounding_box = BlockBox::new(x, -64, z, x, 320, z); let bounding_box = BlockBox::new(x, -64, z, x, 320, z);
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(BuriedTreasurePiece { collector.add_piece(Box::new(BuriedTreasurePiece {
target_x: x, piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0),
target_z: z,
bounding_box,
})); }));
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new(x, 90, z), start_pos: BlockPos::new(x, 90, z),
collector: collector.into(), collector: Arc::new(collector.into()),
}) })
} }
} }
/// The Logic for actually placing the chest #[derive(Clone)]
#[derive(Debug, Clone)]
pub struct BuriedTreasurePiece { pub struct BuriedTreasurePiece {
target_x: i32, piece: StructurePiece,
target_z: i32,
bounding_box: BlockBox,
} }
impl StructurePiece for BuriedTreasurePiece { impl StructurePieceBase for BuriedTreasurePiece {
fn bounding_box(&self) -> &BlockBox { fn clone_box(&self) -> Box<dyn StructurePieceBase> {
&self.bounding_box Box::new((*self).clone())
} }
fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) {
let y = chunk.get_top_y(&HeightMap::OceanFloorWg, self.target_x, self.target_z); let boundingbox = self.bounding_box();
let y = chunk.get_top_y(
&HeightMap::OceanFloorWg,
boundingbox.min.x,
boundingbox.min.z,
);
let mut pos = BlockPos::new(self.target_x, y, self.target_z); let mut pos = BlockPos::new(boundingbox.min.x, y, boundingbox.min.z);
let bottom_y = chunk.bottom_y() as i32; let bottom_y = chunk.bottom_y() as i32;
for _ in (bottom_y..=y).rev() { for _ in (bottom_y..=y).rev() {
@ -111,6 +116,14 @@ impl StructurePiece for BuriedTreasurePiece {
pos = pos.down(); pos = pos.down();
} }
} }
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece
}
} }
impl BuriedTreasurePiece { impl BuriedTreasurePiece {

View File

@ -1,175 +1,542 @@
use std::sync::Arc; use std::sync::{Arc, Mutex};
use pumpkin_data::Block;
use pumpkin_data::BlockState; use pumpkin_data::BlockState;
use pumpkin_util::math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}; use pumpkin_util::HeightMap;
use pumpkin_util::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, get_carver_seed, xoroshiro128::Xoroshiro},
};
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{height_limit::HeightLimitView, structure::StructureKeys}, generation::{
height_limit::HeightLimitView,
positions::chunk_pos::{start_block_x, start_block_z},
structure::piece::StructurePieceType,
},
}; };
pub mod buried_treasure; pub mod buried_treasure;
pub mod nether_fortress; pub mod nether_fortress;
pub mod stronghold;
pub mod swamp_hut; pub mod swamp_hut;
#[expect(clippy::too_many_arguments)] pub trait BlockRandomizer {
pub fn fill_with_outline( fn get_block(&self, rng: &mut RandomGenerator, is_border: bool) -> &BlockState;
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
outline: &BlockState,
inside: &BlockState,
chunk: &mut ProtoChunk,
) {
let chunk_x = chunk.x;
let chunk_z = chunk.z;
let chunk_min_x = chunk_x * 16;
let chunk_max_x = chunk_min_x + 15;
let chunk_min_z = chunk_z * 16;
let chunk_max_z = chunk_min_z + 15;
let start_x = min_x.max(chunk_min_x);
let end_x = max_x.min(chunk_max_x);
let start_z = min_z.max(chunk_min_z);
let end_z = max_z.min(chunk_max_z);
if start_x > end_x || start_z > end_z {
return;
}
for y in min_y..=max_y {
for x in start_x..=end_x {
for z in start_z..=end_z {
let is_border = x == min_x
|| x == max_x
|| y == min_y
|| y == max_y
|| z == min_z
|| z == max_z;
let block = if is_border { outline } else { inside };
chunk.set_block_state(&Vector3::new(x, y, z), block);
}
}
}
}
/// Fills a solid cuboid.
#[expect(clippy::too_many_arguments)]
pub fn fill(
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
state: &BlockState,
chunk: &mut ProtoChunk,
) {
// Basic bounds check to avoid iterating 1000s of blocks outside the chunk
let chunk_x = chunk.x;
let chunk_z = chunk.z;
let chunk_min_x = chunk_x * 16;
let chunk_max_x = chunk_min_x + 15;
let chunk_min_z = chunk_z * 16;
let chunk_max_z = chunk_min_z + 15;
let start_x = min_x.max(chunk_min_x);
let end_x = max_x.min(chunk_max_x);
let start_z = min_z.max(chunk_min_z);
let end_z = max_z.min(chunk_max_z);
if start_x > end_x || start_z > end_z {
return;
}
for y in min_y..=max_y {
for x in start_x..=end_x {
for z in start_z..=end_z {
chunk.set_block_state(&Vector3::new(x, y, z), state);
}
}
}
}
/// Fills downwards from a point until the bottom of the world.
pub fn fill_downwards(x: i32, y: i32, z: i32, state: &BlockState, chunk: &mut ProtoChunk) {
let start_y = y;
let end_y = chunk.bottom_y() as i32; // e.g. -64
for current_y in (end_y..=start_y).rev() {
chunk.set_block_state(&Vector3::new(x, current_y, z), state);
}
} }
/// Represents a single component of a structure (e.g., a room, a bridge). /// Represents a single component of a structure (e.g., a room, a bridge).
pub trait StructurePiece: Send + Sync { pub trait StructurePieceBase: Send + Sync {
/// The bounding box of this specific piece. fn get_structure_piece(&self) -> &StructurePiece;
fn bounding_box(&self) -> &BlockBox;
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece;
fn bounding_box(&self) -> BlockBox {
self.get_structure_piece().bounding_box
}
fn translate(&mut self, x: i32, y: i32, z: i32) {
self.get_structure_piece_mut().translate(x, y, z);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase>;
/// Places the blocks for this piece into the chunk. /// Places the blocks for this piece into the chunk.
fn place(&self, chunk: &mut ProtoChunk, seed: i64); fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, seed: i64);
fn fill_openings(
&self,
_start: &StructurePiece,
_random: &mut RandomGenerator,
_collector: &mut StructurePiecesCollector,
_pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
}
}
#[derive(Clone)]
pub struct StructurePiece {
pub r#type: StructurePieceType,
pub bounding_box: BlockBox,
pub facing: Option<BlockDirection>,
pub chain_length: u32,
}
impl StructurePiece {
pub fn new(r#type: StructurePieceType, bounding_box: BlockBox, chain_length: u32) -> Self {
Self {
r#type,
bounding_box,
facing: None,
chain_length,
}
}
pub fn set_facing(&mut self, facing: Option<BlockDirection>) {
self.facing = facing;
}
fn offset_pos(&self, x: i32, y: i32, z: i32) -> Vector3<i32> {
Vector3::new(
self.apply_x_transform(x, z),
self.apply_y_transform(y),
self.apply_z_transform(x, z),
)
}
fn apply_x_transform(&self, x: i32, z: i32) -> i32 {
match self.facing {
None => x,
Some(BlockDirection::North) | Some(BlockDirection::South) => {
self.bounding_box.min.x + x
}
Some(BlockDirection::West) => self.bounding_box.max.x - z,
Some(BlockDirection::East) => self.bounding_box.min.x + z,
_ => x,
}
}
fn apply_y_transform(&self, y: i32) -> i32 {
match self.facing {
None => y,
Some(_) => y + self.bounding_box.min.y,
}
}
fn apply_z_transform(&self, x: i32, z: i32) -> i32 {
match self.facing {
None => z,
Some(BlockDirection::North) => self.bounding_box.max.z - z,
Some(BlockDirection::South) => self.bounding_box.min.z + z,
Some(BlockDirection::West) | Some(BlockDirection::East) => self.bounding_box.min.z + x,
_ => z,
}
}
#[expect(clippy::too_many_arguments)]
pub fn fill_outline_random(
&self,
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
randomizer: &impl BlockRandomizer,
chunk: &mut ProtoChunk,
cant_replace_air: bool,
rng: &mut RandomGenerator,
box_limit: &BlockBox,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
if cant_replace_air && self.get_block_at(chunk, x, y, z, box_limit).is_air() {
continue;
}
let is_border = x == min_x
|| x == max_x
|| y == min_y
|| y == max_y
|| z == min_z
|| z == max_z;
let state = randomizer.get_block(rng, is_border);
self.add_block(chunk, state, x, y, z, box_limit);
}
}
}
}
#[expect(clippy::too_many_arguments)]
pub fn fill_with_outline(
&self,
chunk: &mut ProtoChunk,
box_limit: &BlockBox,
cant_replace_air: bool,
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
outline: &BlockState,
inside: &BlockState,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
if cant_replace_air && self.get_block_at(chunk, x, y, z, box_limit).is_air() {
continue;
}
let is_border = x == min_x
|| x == max_x
|| y == min_y
|| y == max_y
|| z == min_z
|| z == max_z;
let block = if is_border { outline } else { inside };
self.add_block(chunk, block, x, y, z, box_limit);
}
}
}
}
#[expect(clippy::too_many_arguments)]
pub fn fill_with_outline_under_sea_level(
&self,
chunk: &mut ProtoChunk,
box_limit: &BlockBox,
rng: &mut RandomGenerator,
block_chance: f32,
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
outline: &BlockState,
inside: &BlockState,
cant_replace_air: bool,
stay_below_sea_level: bool,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
// 1. Random Threshold Check
if rng.next_f32() > block_chance {
continue;
}
// 2. Air Replacement Check
if cant_replace_air && self.get_block_at(chunk, x, y, z, box_limit).is_air() {
continue;
}
if stay_below_sea_level && !self.is_under_sea_level(chunk, x, y, z, box_limit) {
continue;
}
let is_border = x == min_x
|| x == max_x
|| y == min_y
|| y == max_y
|| z == min_z
|| z == max_z;
let state = if is_border { outline } else { inside };
self.add_block(chunk, state, x, y, z, box_limit);
}
}
}
}
/// Fills a solid cuboid.
#[expect(clippy::too_many_arguments)]
pub fn fill(
&self,
chunk: &mut ProtoChunk,
box_limit: &BlockBox,
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
state: &BlockState,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
self.add_block(chunk, state, x, y, z, box_limit);
}
}
}
}
/// Fills downwards from a relative point until the bottom of the world.
pub fn fill_downwards(
&self,
chunk: &mut ProtoChunk,
state: &BlockState,
x: i32,
y: i32,
z: i32,
box_limit: &BlockBox,
) {
// We transform the starting point to world space to get the real Y
let world_pos = self.offset_pos(x, y, z);
let start_y = world_pos.y;
let end_y = chunk.bottom_y() as i32;
for current_y in (end_y..=start_y).rev() {
// We bypass add_block here because add_block transforms X/Z again.
// We use the already-transformed world X/Z but iterate Y manually.
let current_pos = Vector3::new(world_pos.x, current_y, world_pos.z);
if box_limit.contains_pos(&current_pos) {
chunk.set_block_state(&current_pos, state);
}
}
}
pub fn is_under_sea_level(
&self,
chunk: &mut ProtoChunk,
x: i32,
y: i32,
z: i32,
box_limit: &BlockBox,
) -> bool {
let block_pos = self.offset_pos(x, y, z);
if !box_limit.contains_pos(&block_pos) {
return false;
}
let sea_level_at_pos = chunk.get_top_y(&HeightMap::OceanFloorWg, block_pos.x, block_pos.z);
block_pos.y < sea_level_at_pos
}
pub fn get_block_at(
&self,
chunk: &ProtoChunk,
x: i32,
y: i32,
z: i32,
box_limit: &BlockBox,
) -> &BlockState {
let block_pos = self.offset_pos(x, y, z);
if !box_limit.contains_pos(&block_pos) {
log::warn!("Structure out of bounds");
return Block::AIR.default_state;
}
chunk.get_block_state(&block_pos).to_state()
}
pub fn add_block(
&self,
world: &mut ProtoChunk,
block: &BlockState,
x: i32,
y: i32,
z: i32,
box_limit: &BlockBox,
) {
let block_pos = self.offset_pos(x, y, z);
// Bounds and logic checks
if !box_limit.contains_pos(&block_pos) {
log::warn!("Structure out of bounds");
return;
}
// // Apply Mirror and Rotation
// if self.mirror != BlockMirror::None {
// block = block.mirror(self.mirror);
// }
// if self.rotation != BlockRotation::None {
// block = block.rotate(self.rotation);
// }
// World interaction
world.set_block_state(&block_pos, block);
// let fluid_state = world.get_fluid_state(&block_pos);
// if !fluid_state.is_empty() {
// world.schedule_fluid_tick(&block_pos, fluid_state.fluid(), 0);
// }
// if block.needs_post_processing() {
// world.mark_block_for_post_processing(&block_pos);
// }
}
pub fn get_random_horizontal_direction(random: &mut impl RandomImpl) -> BlockDirection {
match random.next_bounded_i32(4) {
0 => BlockDirection::North,
1 => BlockDirection::South,
2 => BlockDirection::West,
_ => BlockDirection::East,
}
}
}
impl StructurePieceBase for StructurePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(&mut self, _chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) {}
fn translate(&mut self, x: i32, y: i32, z: i32) {
self.bounding_box.move_pos(x, y, z);
}
fn get_structure_piece(&self) -> &StructurePiece {
self
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
self
}
} }
/// Holds all the pieces that make up a generated structure instance. /// Holds all the pieces that make up a generated structure instance.
#[derive(Default)] #[derive(Default)]
pub struct StructurePiecesCollector { pub struct StructurePiecesCollector {
pub pieces: Vec<Box<dyn StructurePiece>>, pub pieces: Vec<Box<dyn StructurePieceBase>>,
cached_box: Option<BlockBox>,
} }
impl StructurePiecesCollector { impl StructurePiecesCollector {
pub fn new() -> Self { pub fn new() -> Self {
Self { pieces: Vec::new() } Self {
pieces: Vec::new(),
cached_box: None,
}
} }
pub fn add_piece(&mut self, piece: Box<dyn StructurePiece>) { pub fn add_piece(&mut self, piece: Box<dyn StructurePieceBase>) {
self.pieces.push(piece); self.pieces.push(piece);
} }
#[expect(clippy::borrowed_box)]
pub fn get_intersecting(
&self,
box_to_check: &BlockBox,
) -> Option<&Box<dyn StructurePieceBase>> {
self.pieces
.iter()
.find(|&piece| {
piece
.get_structure_piece()
.bounding_box
.intersects(box_to_check)
})
.map(|v| v as _)
}
/// Iterates over all pieces and generates them if they intersect the current chunk. /// Iterates over all pieces and generates them if they intersect the current chunk.
pub fn generate_in_chunk(&self, chunk: &mut ProtoChunk, seed: i64) { pub fn generate_in_chunk(
let chunk_x = chunk.x; &mut self,
let chunk_z = chunk.z; chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
seed: i64,
) {
let chunk_x = start_block_x(chunk.x);
let chunk_z = start_block_z(chunk.z);
let chunk_box = BlockBox::new( let chunk_box = BlockBox::new(
chunk_x * 16, chunk_x,
chunk.bottom_y() as i32, chunk.bottom_y() as i32 + 1,
chunk_z * 16, chunk_z,
(chunk_x * 16) + 15, chunk_x + 15,
chunk.top_y() as i32, chunk.bottom_y() as i32 + chunk.top_y() as i32 - 1,
(chunk_z * 16) + 15, chunk_z + 15,
); );
for piece in &self.pieces { for piece in self.pieces.iter_mut() {
if piece.bounding_box().intersects(&chunk_box) { if piece.bounding_box().intersects(&chunk_box) {
piece.place(chunk, seed); piece.place(chunk, random, seed);
} }
} }
} }
pub fn shift(&mut self, y_offset: i32) {
for piece in self.pieces.iter_mut() {
piece.translate(0, y_offset, 0);
}
self.cached_box = None;
}
/// Calculates a random vertical position and shifts the structure to fit.
/// Matches 'shiftInto(int topY, int bottomY, Random random, int topPenalty)'
pub fn shift_into(
&mut self,
top_y: i32,
bottom_y: i32,
random: &mut RandomGenerator,
top_penalty: i32,
) -> i32 {
let i = top_y - top_penalty;
let bounding_box = self.get_bounding_box();
let mut j = (bounding_box.max.y - bounding_box.min.y + 1) + bottom_y + 1;
if j < i {
j += random.next_bounded_i32(i - j);
}
let k = j - bounding_box.max.y;
self.shift(k);
k
}
pub fn get_bounding_box(&mut self) -> BlockBox {
if let Some(bbox) = self.cached_box {
return bbox;
}
let bbox = BlockBox::encompass_all(self.pieces.iter().map(|p| p.bounding_box()))
.expect("Structure must have at least one piece to calculate a bounding box");
self.cached_box = Some(bbox);
bbox
}
pub fn is_empty(&self) -> bool {
self.pieces.is_empty()
}
pub fn clear(&mut self) {
self.pieces.clear()
}
} }
#[derive(Clone)] #[derive(Clone)]
pub struct StructurePosition { pub struct StructurePosition {
pub start_pos: BlockPos, pub start_pos: BlockPos,
pub collector: Arc<StructurePiecesCollector>, pub collector: Arc<Mutex<StructurePiecesCollector>>,
}
impl StructurePosition {
pub fn get_bounding_box(&self) -> BlockBox {
self.collector.lock().unwrap().get_bounding_box()
}
} }
pub trait StructureGenerator { pub trait StructureGenerator {
fn get_structure_position( fn get_structure_position(
&self, &self,
seed: i64, context: StructureGeneratorContext,
chunk_x: i32,
chunk_z: i32,
) -> Option<StructurePosition>; ) -> Option<StructurePosition>;
} }
pub struct StructureGeneratorContext {
pub seed: i64,
pub chunk_x: i32,
pub chunk_z: i32,
pub random: RandomGenerator,
pub sea_level: i32,
pub min_y: i32,
}
pub fn create_chunk_random(seed: i64, chunk_x: i32, chunk_z: i32) -> RandomGenerator {
let mut random: RandomGenerator = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
let carver_seed = get_carver_seed(&mut random, seed as u64, chunk_x, chunk_z);
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed))
}
#[derive(Clone)] #[derive(Clone)]
pub enum StructureInstance { pub enum StructureInstance {
/// This chunk is the "owner" of the structure. /// This chunk is the "owner" of the structure.
Start(StructurePosition, StructureKeys), Start(StructurePosition),
/// This chunk just contains a piece of a structure starting elsewhere. /// This chunk just contains a piece of a structure starting elsewhere.
/// Stores the BlockPos of the 'Start' so you can look it up. /// Stores the BlockPos of the 'Start' so you can look it up.
Reference(BlockPos), Reference(BlockPos),

View File

@ -1,191 +1,188 @@
use pumpkin_data::Block; // use std::sync::Arc;
use pumpkin_util::math::{block_box::BlockBox, position::BlockPos, vector3::Vector3};
use serde::Deserialize;
use crate::{ // use pumpkin_data::Block;
ProtoChunk, // use pumpkin_util::{
generation::{ // math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
positions::chunk_pos, // random::RandomGenerator,
structure::structures::{ // };
StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, fill, // use serde::Deserialize;
fill_downwards, fill_with_outline,
},
},
};
#[derive(Deserialize, Clone, Debug)] // use crate::{
pub struct NetherFortressGenerator; // ProtoChunk,
// generation::{
// positions::chunk_pos,
// structure::structures::{
// StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
// StructurePiecesCollector, StructurePosition,
// },
// },
// };
impl StructureGenerator for NetherFortressGenerator { // #[derive(Deserialize, Clone, Debug)]
fn get_structure_position( // pub struct NetherFortressGenerator;
&self,
_seed: i64,
chunk_x: i32,
chunk_z: i32,
) -> Option<StructurePosition> {
let start_x = chunk_pos::start_block_x(chunk_x);
let start_z = chunk_pos::start_block_z(chunk_z);
let start_y = 64;
let mut collector = StructurePiecesCollector::default(); // impl StructureGenerator for NetherFortressGenerator {
// fn get_structure_position(
// &self,
// context: StructureGeneratorContext,
// ) -> Option<StructurePosition> {
// let start_x = chunk_pos::start_block_x(context.chunk_x);
// let start_z = chunk_pos::start_block_z(context.chunk_z);
// let start_y = 64;
// Add a Bridge Platform piece // let mut collector = StructurePiecesCollector::default();
let box_min = BlockPos::new(start_x, start_y, start_z);
let box_max = BlockPos::new(start_x + 8, start_y + 10, start_z + 8);
collector.add_piece(Box::new(NetherFortressPiece { // // Add a Bridge Platform piece
bounding_box: BlockBox::new( // let box_min = BlockPos::new(start_x, start_y, start_z);
box_min.0.x, // let box_max = BlockPos::new(start_x + 8, start_y + 10, start_z + 8);
box_min.0.y,
box_min.0.z,
box_max.0.x,
box_max.0.y,
box_max.0.z,
),
piece_type: NetherFortressPieceType::BridgePlatform,
}));
Some(StructurePosition { // todo!();
start_pos: box_min,
collector: collector.into(),
})
}
}
#[derive(Debug, Clone)] // Some(StructurePosition {
pub enum NetherFortressPieceType { // start_pos: box_min,
BridgePlatform, // collector: Arc::new(collector.into()),
SmallCorridor, // })
} // }
// }
#[derive(Debug, Clone)] // #[derive(Debug, Clone)]
pub struct NetherFortressPiece { // pub enum NetherFortressPieceType {
pub bounding_box: BlockBox, // BridgePlatform,
pub piece_type: NetherFortressPieceType, // SmallCorridor,
} // }
impl StructurePiece for NetherFortressPiece { // struct NetherFortressPiece {
fn bounding_box(&self) -> &BlockBox { // piece: StructurePiece,
&self.bounding_box // piece_type: NetherFortressPieceType,
} // }
fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { // impl StructurePieceBase for NetherFortressPiece {
let origin_x = self.bounding_box.min.x; // fn place(&mut self, chunk: &mut ProtoChunk, _seed: i64) {
let origin_y = self.bounding_box.min.y; // let bounding_box = self.piece.bounding_box;
let origin_z = self.bounding_box.min.z; // let origin_x = bounding_box.min.x;
// let origin_y = bounding_box.min.y;
// let origin_z = bounding_box.min.z;
let offset = |x, y, z| Vector3::new(origin_x + x, origin_y + y, origin_z + z); // let offset = |x, y, z| Vector3::new(origin_x + x, origin_y + y, origin_z + z);
let fill_rel = |min_x, min_y, min_z, max_x, max_y, max_z, block, c: &mut ProtoChunk| { // let fill_rel = |min_x, min_y, min_z, max_x, max_y, max_z, block, c: &mut ProtoChunk| {
fill( // fill(
origin_x + min_x, // origin_x + min_x,
origin_y + min_y, // origin_y + min_y,
origin_z + min_z, // origin_z + min_z,
origin_x + max_x, // origin_x + max_x,
origin_y + max_y, // origin_y + max_y,
origin_z + max_z, // origin_z + max_z,
block, // block,
c, // c,
) // )
}; // };
let fill_outline_rel = // let fill_outline_rel =
|min_x, min_y, min_z, max_x, max_y, max_z, outline, inside, c: &mut ProtoChunk| { // |min_x, min_y, min_z, max_x, max_y, max_z, outline, inside, c: &mut ProtoChunk| {
fill_with_outline( // fill_with_outline(
origin_x + min_x, // origin_x + min_x,
origin_y + min_y, // origin_y + min_y,
origin_z + min_z, // origin_z + min_z,
origin_x + max_x, // origin_x + max_x,
origin_y + max_y, // origin_y + max_y,
origin_z + max_z, // origin_z + max_z,
outline, // outline,
inside, // inside,
c, // c,
) // )
}; // };
match self.piece_type { // match self.piece_type {
NetherFortressPieceType::BridgePlatform => { // NetherFortressPieceType::BridgePlatform => {
fill_rel(0, 2, 0, 6, 7, 7, Block::AIR.default_state, chunk); // fill_rel(0, 2, 0, 6, 7, 7, Block::AIR.default_state, chunk);
fill_rel(1, 0, 0, 5, 1, 7, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 0, 0, 5, 1, 7, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 2, 1, 5, 2, 7, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 2, 1, 5, 2, 7, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 3, 2, 5, 3, 7, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 3, 2, 5, 3, 7, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 4, 3, 5, 4, 7, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 4, 3, 5, 4, 7, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 2, 0, 1, 4, 2, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 2, 0, 1, 4, 2, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(5, 2, 0, 5, 4, 2, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(5, 2, 0, 5, 4, 2, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 5, 2, 1, 5, 3, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 5, 2, 1, 5, 3, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(5, 5, 2, 5, 5, 3, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(5, 5, 2, 5, 5, 3, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(0, 5, 3, 0, 5, 8, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(0, 5, 3, 0, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(6, 5, 3, 6, 5, 8, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(6, 5, 3, 6, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(1, 5, 8, 5, 5, 8, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(1, 5, 8, 5, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
let fence = &Block::NETHER_BRICK_FENCE.default_state; // let fence = &Block::NETHER_BRICK_FENCE.default_state;
chunk.set_block_state(&offset(1, 6, 3), fence); // chunk.set_block_state(&offset(1, 6, 3), fence);
chunk.set_block_state(&offset(5, 6, 3), fence); // chunk.set_block_state(&offset(5, 6, 3), fence);
chunk.set_block_state(&offset(0, 6, 3), fence); // chunk.set_block_state(&offset(0, 6, 3), fence);
chunk.set_block_state(&offset(6, 6, 3), fence); // chunk.set_block_state(&offset(6, 6, 3), fence);
fill_outline_rel(0, 6, 4, 0, 6, 7, fence, fence, chunk); // fill_outline_rel(0, 6, 4, 0, 6, 7, fence, fence, chunk);
fill_outline_rel(6, 6, 4, 6, 6, 7, fence, fence, chunk); // fill_outline_rel(6, 6, 4, 6, 6, 7, fence, fence, chunk);
chunk.set_block_state(&offset(0, 6, 8), fence); // chunk.set_block_state(&offset(0, 6, 8), fence);
chunk.set_block_state(&offset(6, 6, 8), fence); // chunk.set_block_state(&offset(6, 6, 8), fence);
fill_outline_rel(1, 6, 8, 5, 6, 8, fence, fence, chunk); // fill_outline_rel(1, 6, 8, 5, 6, 8, fence, fence, chunk);
chunk.set_block_state(&offset(1, 7, 8), fence); // chunk.set_block_state(&offset(1, 7, 8), fence);
fill_outline_rel(2, 7, 8, 4, 7, 8, fence, fence, chunk); // fill_outline_rel(2, 7, 8, 4, 7, 8, fence, fence, chunk);
chunk.set_block_state(&offset(5, 7, 8), fence); // chunk.set_block_state(&offset(5, 7, 8), fence);
chunk.set_block_state(&offset(2, 8, 8), fence); // chunk.set_block_state(&offset(2, 8, 8), fence);
chunk.set_block_state(&offset(3, 8, 8), fence); // chunk.set_block_state(&offset(3, 8, 8), fence);
chunk.set_block_state(&offset(4, 8, 8), fence); // chunk.set_block_state(&offset(4, 8, 8), fence);
// Blaze Spawner // // Blaze Spawner
chunk.set_block_state(&offset(3, 5, 5), Block::SPAWNER.default_state); // chunk.set_block_state(&offset(3, 5, 5), Block::SPAWNER.default_state);
// Pillars down // // Pillars down
for x in 0..6 { // for x in 0..6 {
for z in 0..6 { // for z in 0..6 {
fill_downwards( // fill_downwards(
origin_x + x, // origin_x + x,
origin_y - 1, // origin_y - 1,
origin_z + z, // origin_z + z,
Block::NETHER_BRICKS.default_state, // Block::NETHER_BRICKS.default_state,
chunk, // chunk,
); // );
} // }
} // }
} // }
NetherFortressPieceType::SmallCorridor => { // NetherFortressPieceType::SmallCorridor => {
fill_rel(0, 0, 0, 4, 1, 4, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(0, 0, 0, 4, 1, 4, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(0, 2, 0, 4, 5, 4, Block::AIR.default_state, chunk); // fill_rel(0, 2, 0, 4, 5, 4, Block::AIR.default_state, chunk);
let fence = &Block::NETHER_BRICK_FENCE.default_state; // let fence = &Block::NETHER_BRICK_FENCE.default_state;
fill_rel(0, 2, 0, 0, 5, 4, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(0, 2, 0, 0, 5, 4, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(4, 2, 0, 4, 5, 4, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(4, 2, 0, 4, 5, 4, Block::NETHER_BRICKS.default_state, chunk);
fill_rel(0, 3, 1, 0, 4, 1, fence, chunk); // fill_rel(0, 3, 1, 0, 4, 1, fence, chunk);
fill_rel(0, 3, 3, 0, 4, 3, fence, chunk); // fill_rel(0, 3, 3, 0, 4, 3, fence, chunk);
fill_rel(4, 3, 1, 4, 4, 1, fence, chunk); // fill_rel(4, 3, 1, 4, 4, 1, fence, chunk);
fill_rel(4, 3, 3, 4, 4, 3, fence, chunk); // fill_rel(4, 3, 3, 4, 4, 3, fence, chunk);
fill_rel(0, 6, 0, 4, 6, 4, Block::NETHER_BRICKS.default_state, chunk); // fill_rel(0, 6, 0, 4, 6, 4, Block::NETHER_BRICKS.default_state, chunk);
for i in 0..=4 { // for i in 0..=4 {
for j in 0..=4 { // for j in 0..=4 {
fill_downwards( // fill_downwards(
origin_x + i, // origin_x + i,
origin_y - 1, // origin_y - 1,
origin_z + j, // origin_z + j,
Block::NETHER_BRICKS.default_state, // Block::NETHER_BRICKS.default_state,
chunk, // chunk,
); // );
} // }
} // }
} // }
} // }
} // }
}
// fn get_structure_piece(&self) -> &super::StructurePiece {
// &self.piece
// }
// fn get_structure_piece_mut(&mut self) -> &mut super::StructurePiece {
// &mut self.piece
// }
// }

View File

@ -0,0 +1,145 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct ChestCorridorPiece {
pub piece: StrongholdPiece,
pub chest_generated: bool,
}
impl ChestCorridorPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, 7, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdChestCorridor,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self {
piece,
chest_generated: false,
}))
}
}
impl StructurePieceBase for ChestCorridorPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
self.piece
.fill_forward_opening(start, collector, random, 1, 1, pieces_to_process, None);
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
inner.fill_outline_random(
0,
0,
0,
4,
4,
6,
&randomizer,
chunk,
true,
random,
&box_limit,
);
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 1, 0);
p.generate_entrance(chunk, &box_limit, EntranceType::Opening, 1, 1, 6);
let stone_bricks = Block::STONE_BRICKS.default_state;
inner.fill_with_outline(
chunk,
&box_limit,
false,
3,
1,
2,
3,
1,
4,
stone_bricks,
stone_bricks,
);
let slab = Block::STONE_BRICK_SLAB.default_state;
inner.add_block(chunk, slab, 3, 1, 1, &box_limit);
inner.add_block(chunk, slab, 3, 1, 5, &box_limit);
inner.add_block(chunk, slab, 3, 2, 2, &box_limit);
inner.add_block(chunk, slab, 3, 2, 4, &box_limit);
for i in 2..=4 {
inner.add_block(chunk, slab, 2, 1, i, &box_limit);
}
// if !self.chest_generated {
// // Check if the target chest position is within the current chunk being processed
// let chest_pos = inner.to_world(3, 2, 3);
// if box_limit.contains(&chest_pos) {
// self.chest_generated = true;
// let chest_state = Block::CHEST.default_state;
// // Note: In a full implementation, you would use a helper to set the LootTable NBT here
// inner.add_block(chunk, &chest_state, 3, 2, 3, &box_limit);
// // If Pumpkin has a loot provider, you'd hook into it here:
// // chunk.set_loot_table(chest_pos, "minecraft:chests/stronghold_corridor");
// }
// }
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,247 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct CorridorPiece {
piece: StrongholdPiece,
left_exit: bool,
right_exit: bool,
}
impl CorridorPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, 7, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdCorridor,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self {
piece,
left_exit: random.next_bounded_i32(2) == 0,
right_exit: random.next_bounded_i32(2) == 0,
}))
}
}
impl StructurePieceBase for CorridorPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
self.piece
.fill_forward_opening(start, collector, random, 1, 1, pieces_to_process, None);
if self.left_exit {
self.piece
.fill_nw_opening(start, collector, random, 1, 2, pieces_to_process);
}
if self.right_exit {
self.piece
.fill_se_opening(start, collector, random, 1, 2, pieces_to_process);
}
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let air = Block::AIR.default_state;
inner.fill_outline_random(
0,
0,
0,
4,
4,
6,
&randomizer,
chunk,
true,
random,
&box_limit,
);
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 1, 0);
p.generate_entrance(chunk, &box_limit, EntranceType::Opening, 1, 1, 6);
// let torch_east = Block::WALL_TORCH.default_state.with("facing", "east");
// let torch_west = Block::WALL_TORCH.default_state.with("facing", "west");
// inner.add_block_with_random_threshold(chunk, &box_limit, random, 0.1, 1, 2, 1, &torch_east);
// inner.add_block_with_random_threshold(chunk, &box_limit, random, 0.1, 3, 2, 1, &torch_west);
// inner.add_block_with_random_threshold(chunk, &box_limit, random, 0.1, 1, 2, 5, &torch_east);
// inner.add_block_with_random_threshold(chunk, &box_limit, random, 0.1, 3, 2, 5, &torch_west);
if self.left_exit {
inner.fill_with_outline(chunk, &box_limit, false, 0, 1, 2, 0, 3, 4, air, air);
}
if self.right_exit {
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 2, 4, 3, 4, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}
#[derive(Clone)]
pub struct SmallCorridorPiece {
pub piece: StrongholdPiece,
pub length: i32,
}
impl SmallCorridorPiece {
pub fn new(chain_length: u32, bbox: BlockBox, orientation: BlockDirection) -> Self {
let length = match orientation {
BlockDirection::North | BlockDirection::South => bbox.max.z - bbox.min.z + 1,
_ => bbox.max.x - bbox.min.x + 1,
};
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdSmallCorridor,
chain_length,
bbox,
);
piece.piece.set_facing(Some(orientation));
Self { piece, length }
}
/// Port of the specialized static 'create' logic
pub fn create_box(
holder: &mut StructurePiecesCollector,
x: i32,
y: i32,
z: i32,
orientation: &BlockDirection,
) -> Option<BlockBox> {
// Initial attempt: 5x5x4 box
let mut block_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, 4, orientation);
// Find if we hit something
let intersecting = holder.get_intersecting(&block_box)?;
// Vanilla Logic: If we hit something at the same height, try to shorten the corridor
// so it perfectly abuts the intersecting piece.
if intersecting.bounding_box().min.y == block_box.min.y {
for j in (1..=2).rev() {
// Corresponds to for (int j = 2; j >= 1; --j)
block_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, j, orientation);
if !intersecting.bounding_box().intersects(&block_box) {
// Found a shorter length that doesn't intersect!
// Return the next size up (j + 1) to seal the gap.
return Some(BlockBox::rotated(
x,
y,
z,
-1,
-1,
0,
5,
5,
j + 1,
orientation,
));
}
}
}
None
}
}
impl StructurePieceBase for SmallCorridorPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn get_structure_piece(&self) -> &crate::generation::structure::structures::StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(
&mut self,
) -> &mut crate::generation::structure::structures::StructurePiece {
&mut self.piece.piece
}
fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) {
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece.piece;
let stone = Block::STONE_BRICKS.default_state;
let cave_air = Block::CAVE_AIR.default_state;
for i in 0..self.length {
// 1. Floor (Y=0)
p.add_block(chunk, stone, 0, 0, i, &box_limit);
p.add_block(chunk, stone, 1, 0, i, &box_limit);
p.add_block(chunk, stone, 2, 0, i, &box_limit);
p.add_block(chunk, stone, 3, 0, i, &box_limit);
p.add_block(chunk, stone, 4, 0, i, &box_limit);
for j in 1..=3 {
p.add_block(chunk, stone, 0, j, i, &box_limit); // Left Wall
p.add_block(chunk, cave_air, 1, j, i, &box_limit); // Air Gap
p.add_block(chunk, cave_air, 2, j, i, &box_limit); // Air Gap
p.add_block(chunk, cave_air, 3, j, i, &box_limit); // Air Gap
p.add_block(chunk, stone, 4, j, i, &box_limit); // Right Wall
}
p.add_block(chunk, stone, 0, 4, i, &box_limit);
p.add_block(chunk, stone, 1, 4, i, &box_limit);
p.add_block(chunk, stone, 2, 4, i, &box_limit);
p.add_block(chunk, stone, 3, 4, i, &box_limit);
p.add_block(chunk, stone, 4, 4, i, &box_limit);
}
}
}

View File

@ -0,0 +1,249 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct FiveWayCrossingPiece {
pub piece: StrongholdPiece,
pub lower_left_exists: bool,
pub upper_left_exists: bool,
pub lower_right_exists: bool,
pub upper_right_exists: bool,
}
impl FiveWayCrossingPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -4, -3, 0, 10, 9, 11, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdFiveWayCrossing,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self {
piece,
lower_left_exists: random.next_bool(),
upper_left_exists: random.next_bool(),
lower_right_exists: random.next_bool(),
upper_right_exists: random.next_bounded_i32(3) > 0,
}))
}
}
impl StructurePieceBase for FiveWayCrossingPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
let mut i = 3;
let mut j = 5;
let facing = self.piece.piece.facing.unwrap_or(BlockDirection::North);
if facing == BlockDirection::West || facing == BlockDirection::North {
i = 8 - i;
j = 8 - j;
}
self.piece
.fill_forward_opening(start, collector, random, 5, 1, pieces_to_process, None);
if self.lower_left_exists {
self.piece
.fill_nw_opening(start, collector, random, i, 1, pieces_to_process);
}
if self.upper_left_exists {
self.piece
.fill_nw_opening(start, collector, random, j, 7, pieces_to_process);
}
if self.lower_right_exists {
self.piece
.fill_se_opening(start, collector, random, i, 1, pieces_to_process);
}
if self.upper_right_exists {
self.piece
.fill_se_opening(start, collector, random, j, 7, pieces_to_process);
}
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let air = Block::AIR.default_state;
inner.fill_outline_random(
0,
0,
0,
9,
8,
10,
&randomizer,
chunk,
true,
random,
&box_limit,
);
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 4, 3, 0);
if self.lower_left_exists {
inner.fill_with_outline(chunk, &box_limit, false, 0, 3, 1, 0, 5, 3, air, air);
}
if self.lower_right_exists {
inner.fill_with_outline(chunk, &box_limit, false, 9, 3, 1, 9, 5, 3, air, air);
}
if self.upper_left_exists {
inner.fill_with_outline(chunk, &box_limit, false, 0, 5, 7, 0, 7, 9, air, air);
}
if self.upper_right_exists {
inner.fill_with_outline(chunk, &box_limit, false, 9, 5, 7, 9, 7, 9, air, air);
}
inner.fill_with_outline(chunk, &box_limit, false, 5, 1, 10, 7, 3, 10, air, air);
inner.fill_outline_random(
1,
2,
1,
8,
2,
6,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
4,
1,
5,
4,
4,
9,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
8,
1,
5,
8,
4,
9,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
1,
4,
7,
3,
4,
9,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
1,
3,
5,
3,
3,
6,
&randomizer,
chunk,
false,
random,
&box_limit,
);
let slab = Block::SMOOTH_STONE_SLAB.default_state;
inner.fill_with_outline(chunk, &box_limit, false, 1, 3, 4, 3, 3, 4, slab, slab);
inner.fill_with_outline(chunk, &box_limit, false, 1, 4, 6, 3, 4, 6, slab, slab);
inner.fill_outline_random(
5,
1,
7,
7,
1,
8,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_with_outline(chunk, &box_limit, false, 5, 1, 9, 7, 1, 9, slab, slab);
inner.fill_with_outline(chunk, &box_limit, false, 5, 2, 7, 7, 2, 7, slab, slab);
inner.fill_with_outline(chunk, &box_limit, false, 4, 5, 7, 4, 5, 9, slab, slab);
inner.fill_with_outline(chunk, &box_limit, false, 8, 5, 7, 8, 5, 9, slab, slab);
// let double_slab = Block::SMOOTH_STONE_SLAB.default_state.with("type", "double");
// inner.fill_with_outline(chunk, &box_limit, false, 5, 5, 7, 7, 5, 9, &double_slab, &double_slab);
// let torch = Block::WALL_TORCH.default_state.with("facing", "south");
// inner.add_block(chunk, &torch, 6, 5, 6, &box_limit);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,116 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct LeftTurnPiece {
pub piece: StrongholdPiece,
}
impl LeftTurnPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, 5, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdLeftTurn,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self { piece }))
}
}
impl StructurePieceBase for LeftTurnPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
let facing = self.piece.piece.facing.unwrap_or(BlockDirection::North);
if facing == BlockDirection::North || facing == BlockDirection::East {
self.piece
.fill_nw_opening(start, collector, random, 1, 1, pieces_to_process);
} else {
self.piece
.fill_se_opening(start, collector, random, 1, 1, pieces_to_process);
}
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let air = Block::AIR.default_state;
inner.fill_outline_random(
0,
0,
0,
4,
4,
4,
&randomizer,
chunk,
true,
random,
&box_limit,
);
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 1, 0);
let direction = inner.facing.unwrap_or(BlockDirection::North);
if direction == BlockDirection::North || direction == BlockDirection::East {
inner.fill_with_outline(chunk, &box_limit, false, 0, 1, 1, 0, 3, 3, air, air);
} else {
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 1, 4, 3, 3, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,233 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct LibraryPiece {
pub piece: StrongholdPiece,
pub tall: bool,
}
impl LibraryPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let mut bounding_box = BlockBox::rotated(x, y, z, -4, -1, 0, 14, 11, 15, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
bounding_box = BlockBox::rotated(x, y, z, -4, -1, 0, 14, 6, 15, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdLibrary,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
let tall = bounding_box.get_block_count_y() > 6;
Some(Box::new(Self { piece, tall }))
}
}
impl StructurePieceBase for LibraryPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let max_y = if self.tall { 10 } else { 5 }; // Java: i-1 where i is 11 or 6
// 1. Main Shell
inner.fill_outline_random(
0,
0,
0,
13,
max_y,
14,
&randomizer,
chunk,
true,
random,
&box_limit,
);
// 2. Entrance
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 4, 1, 0);
// 3. Cobwebs (Random probability placement)
inner.fill_with_outline_under_sea_level(
chunk,
&box_limit,
random,
0.07,
2,
1,
1,
11,
4,
13,
Block::COBWEB.default_state,
Block::COBWEB.default_state,
false,
false,
);
// 4. Bookshelves, Pillars, and Wall Torches
let planks = Block::OAK_PLANKS.default_state;
let books = Block::BOOKSHELF.default_state;
// let torch_e = Block::WALL_TORCH.default_state.with("facing", "east");
// let torch_w = Block::WALL_TORCH.default_state.with("facing", "west");
for l in 1..=13 {
if (l - 1) % 4 == 0 {
// Pillars
inner.fill_with_outline(chunk, &box_limit, false, 1, 1, l, 1, 4, l, planks, planks);
inner.fill_with_outline(
chunk, &box_limit, false, 12, 1, l, 12, 4, l, planks, planks,
);
// Torches
// inner.add_block(chunk, &torch_e, 2, 3, l, &box_limit);
// inner.add_block(chunk, &torch_w, 11, 3, l, &box_limit);
if self.tall {
inner.fill_with_outline(
chunk, &box_limit, false, 1, 6, l, 1, 9, l, planks, planks,
);
inner.fill_with_outline(
chunk, &box_limit, false, 12, 6, l, 12, 9, l, planks, planks,
);
}
} else {
// Shelf Walls
inner.fill_with_outline(chunk, &box_limit, false, 1, 1, l, 1, 4, l, books, books);
inner.fill_with_outline(chunk, &box_limit, false, 12, 1, l, 12, 4, l, books, books);
if self.tall {
inner.fill_with_outline(
chunk, &box_limit, false, 1, 6, l, 1, 9, l, books, books,
);
inner.fill_with_outline(
chunk, &box_limit, false, 12, 6, l, 12, 9, l, books, books,
);
}
}
}
// 5. Central Freestanding Shelves
for l in (3..12).step_by(2) {
inner.fill_with_outline(chunk, &box_limit, false, 3, 1, l, 4, 3, l, books, books);
inner.fill_with_outline(chunk, &box_limit, false, 6, 1, l, 7, 3, l, books, books);
inner.fill_with_outline(chunk, &box_limit, false, 9, 1, l, 10, 3, l, books, books);
}
if self.tall {
// Upper Walkways
inner.fill_with_outline(chunk, &box_limit, false, 1, 5, 1, 3, 5, 13, planks, planks);
inner.fill_with_outline(
chunk, &box_limit, false, 10, 5, 1, 12, 5, 13, planks, planks,
);
inner.fill_with_outline(chunk, &box_limit, false, 4, 5, 1, 9, 5, 2, planks, planks);
inner.fill_with_outline(chunk, &box_limit, false, 4, 5, 12, 9, 5, 13, planks, planks);
// Walkway corners
inner.add_block(chunk, planks, 9, 5, 11, &box_limit);
inner.add_block(chunk, planks, 8, 5, 11, &box_limit);
inner.add_block(chunk, planks, 9, 5, 10, &box_limit);
// Fences / Railings
// let f_ns = Block::OAK_FENCE.default_state.with("north", "true").with("south", "true");
// let f_ew = Block::OAK_FENCE.default_state.with("east", "true").with("west", "true");
// inner.fill_with_outline(chunk, &box_limit, false, 3, 6, 3, 3, 6, 11, &f_ns, &f_ns);
// inner.fill_with_outline(chunk, &box_limit, false, 10, 6, 3, 10, 6, 9, &f_ns, &f_ns);
// inner.fill_with_outline(chunk, &box_limit, false, 4, 6, 2, 9, 6, 2, &f_ew, &f_ew);
// inner.fill_with_outline(chunk, &box_limit, false, 4, 6, 12, 7, 6, 12, &f_ew, &f_ew);
// // Complex fence junctions (matching the m-loop and specific offsets)
// inner.add_block(chunk, &Block::OAK_FENCE.default_state.with("north", "true").with("east", "true"), 3, 6, 2, &box_limit);
// inner.add_block(chunk, &Block::OAK_FENCE.default_state.with("south", "true").with("east", "true"), 3, 6, 12, &box_limit);
// inner.add_block(chunk, &Block::OAK_FENCE.default_state.with("north", "true").with("west", "true"), 10, 6, 2, &box_limit);
// for m in 0..=2 {
// inner.add_block(chunk, &Block::OAK_FENCE.default_state.with("south", "true").with("west", "true"), 8 + m, 6, 12 - m, &box_limit);
// if m != 2 {
// inner.add_block(chunk, &Block::OAK_FENCE.default_state.with("north", "true").with("east", "true"), 8 + m, 6, 11 - m, &box_limit);
// }
// }
// // Ladder
// let ladder = Block::LADDER.default_state.with("facing", "south");
// for y in 1..=7 {
// inner.add_block(chunk, &ladder, 10, y, 13, &box_limit);
// }
// // Chandelier (The central structure)
// let f_e = Block::OAK_FENCE.default_state.with("east", "true");
// let f_w = Block::OAK_FENCE.default_state.with("west", "true");
// inner.add_block(chunk, &f_e, 6, 9, 7, &box_limit);
// inner.add_block(chunk, &f_w, 7, 9, 7, &box_limit);
// inner.add_block(chunk, &f_e, 6, 8, 7, &box_limit);
// inner.add_block(chunk, &f_w, 7, 8, 7, &box_limit);
// let f_full = f_ns.with("west", "true").with("east", "true");
// inner.add_block(chunk, &f_full, 6, 7, 7, &box_limit);
// inner.add_block(chunk, &f_full, 7, 7, 7, &box_limit);
// Chandelier Torches
let t = Block::TORCH.default_state;
inner.add_block(chunk, t, 5, 8, 7, &box_limit);
inner.add_block(chunk, t, 8, 8, 7, &box_limit);
inner.add_block(chunk, t, 6, 8, 6, &box_limit);
inner.add_block(chunk, t, 6, 8, 8, &box_limit);
}
// 6. Chests
// inner.add_chest(chunk, &box_limit, random, 3, 3, 5, "stronghold_library");
// if self.tall {
// inner.add_block(chunk, &Block::AIR.default_state, 12, 9, 1, &box_limit); // Clear space above top chest
// inner.add_chest(chunk, &box_limit, random, 12, 8, 1, "stronghold_library");
// }
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,724 @@
use std::sync::{Arc, Mutex};
use pumpkin_data::{Block, BlockState};
use pumpkin_util::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
// Removed unused rand imports to avoid confusion with Pumpkin's random
use serde::Deserialize;
use crate::{
ProtoChunk,
generation::{
positions::chunk_pos::{get_offset_x, get_offset_z},
structure::{
piece::StructurePieceType,
structures::{
BlockRandomizer, StructureGenerator, StructureGeneratorContext, StructurePiece,
StructurePieceBase, StructurePiecesCollector, StructurePosition,
stronghold::{
chest_corridor::ChestCorridorPiece,
corridor::{CorridorPiece, SmallCorridorPiece},
five_way_crossing::FiveWayCrossingPiece,
left_turn::LeftTurnPiece,
library::LibraryPiece,
portal_room::PortalRoomPiece,
prison_hall::PrisonHallPiece,
right_turn::RightTurnPiece,
spiral_staircase::SpiralStaircasePiece,
square_room::SquareRoomPiece,
stairs::StairsPiece,
},
},
},
},
};
// Assuming these modules exist as per your import
pub mod chest_corridor;
pub mod corridor;
pub mod five_way_crossing;
pub mod left_turn;
pub mod library;
pub mod portal_room;
pub mod prison_hall;
pub mod right_turn;
pub mod spiral_staircase;
pub mod square_room;
pub mod stairs;
#[derive(Deserialize)]
pub struct StrongholdGenerator;
impl StructureGenerator for StrongholdGenerator {
fn get_structure_position(
&self,
context: StructureGeneratorContext,
) -> Option<StructurePosition> {
let mut _i = 0;
// The collector must be shared/mutable during generation
let mut collector = StructurePiecesCollector::default();
let mut random = context.random;
// Loop until we have a valid stronghold (Vanilla loop logic)
loop {
collector.clear();
_i += 1;
let start_x = get_offset_x(context.chunk_x, 2);
let start_z = get_offset_z(context.chunk_z, 2);
// Create the Start Piece (SpiralStaircase)
// Note: new_start must return a concrete struct that implements StructurePieceBase
let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z);
// We need to Box the start piece to treat it uniformly in the list
let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone());
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> =
vec![start_box.clone_box()];
collector.add_piece(start_box);
// 1. Initial Fill
// We cast strictly to SpiralStaircasePiece here because the start piece is special
start_piece.fill_openings(
&start_piece.piece.piece,
&mut random,
&mut collector,
&mut pieces_to_process,
);
// 2. The Growth Loop
while !pieces_to_process.is_empty() {
// Pick a random piece from the "to process" list
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx);
piece.fill_openings(
&start_piece.piece.piece,
&mut random,
&mut collector,
&mut pieces_to_process,
);
}
collector.shift_into(context.sea_level, context.min_y, &mut random, 10);
let has_portal_room = collector.pieces.iter().any(|p| {
p.get_structure_piece().r#type == StructurePieceType::StrongholdPortalRoom
});
if !collector.is_empty() && has_portal_room || collector.pieces.len() >= 100 {
break;
}
}
Some(StructurePosition {
start_pos: BlockPos::new(context.chunk_x, 0, context.chunk_z),
collector: Arc::new(Mutex::new(collector)),
})
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum EntranceType {
Opening = 0,
WoodDoor = 1,
Grates = 2,
IronDoor = 3,
}
impl EntranceType {
pub fn get_random(random: &mut impl RandomImpl) -> Self {
match random.next_bounded_i32(5) {
2 => EntranceType::WoodDoor,
3 => EntranceType::Grates,
4 => EntranceType::IronDoor,
_ => EntranceType::Opening,
}
}
}
struct StoneBrickRandomizer;
impl BlockRandomizer for StoneBrickRandomizer {
fn get_block(&self, rng: &mut RandomGenerator, is_border: bool) -> &BlockState {
if !is_border {
return Block::AIR.default_state;
}
let roll = rng.next_f32();
if roll < 0.2 {
Block::CRACKED_STONE_BRICKS.default_state
} else if roll < 0.5 {
Block::MOSSY_STONE_BRICKS.default_state
} else {
Block::STONE_BRICKS.default_state
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StrongholdPieceType {
Corridor,
PrisonHall,
LeftTurn,
RightTurn,
SquareRoom,
Stairs,
SpiralStaircase,
FiveWayCrossing,
ChestCorridor,
Library,
PortalRoom,
}
impl StrongholdPieceType {
pub fn as_structure_type(&self) -> StructurePieceType {
match self {
Self::Corridor => StructurePieceType::StrongholdCorridor,
Self::PrisonHall => StructurePieceType::StrongholdPrisonHall,
Self::LeftTurn => StructurePieceType::StrongholdLeftTurn,
Self::RightTurn => StructurePieceType::StrongholdRightTurn,
Self::SquareRoom => StructurePieceType::StrongholdSquareRoom,
Self::Stairs => StructurePieceType::StrongholdStairs,
Self::SpiralStaircase => StructurePieceType::StrongholdSpiralStaircase,
Self::FiveWayCrossing => StructurePieceType::StrongholdFiveWayCrossing,
Self::ChestCorridor => StructurePieceType::StrongholdChestCorridor,
Self::Library => StructurePieceType::StrongholdLibrary,
Self::PortalRoom => StructurePieceType::StrongholdPortalRoom,
}
}
}
#[derive(Clone)]
pub struct PieceWeight {
pub piece_type: StrongholdPieceType,
pub weight: i32,
pub limit: i32,
}
impl PieceWeight {
fn new(piece_type: StrongholdPieceType, weight: i32, limit: i32) -> Self {
Self {
piece_type,
weight,
limit,
}
}
fn can_generate(&self, _chain_length: u32) -> bool {
self.limit == 0 //|| self.generated_count < self.limit
}
}
const POSSIBLE_PIECES: &[PieceWeight] = &[
PieceWeight {
piece_type: StrongholdPieceType::Corridor,
weight: 40,
limit: 0,
},
PieceWeight {
piece_type: StrongholdPieceType::PrisonHall,
weight: 5,
limit: 5,
},
PieceWeight {
piece_type: StrongholdPieceType::LeftTurn,
weight: 20,
limit: 0,
},
PieceWeight {
piece_type: StrongholdPieceType::RightTurn,
weight: 20,
limit: 0,
},
PieceWeight {
piece_type: StrongholdPieceType::SquareRoom,
weight: 10,
limit: 6,
},
PieceWeight {
piece_type: StrongholdPieceType::Stairs,
weight: 5,
limit: 5,
},
PieceWeight {
piece_type: StrongholdPieceType::SpiralStaircase,
weight: 5,
limit: 5,
},
PieceWeight {
piece_type: StrongholdPieceType::FiveWayCrossing,
weight: 5,
limit: 4,
},
PieceWeight {
piece_type: StrongholdPieceType::ChestCorridor,
weight: 5,
limit: 4,
},
PieceWeight {
piece_type: StrongholdPieceType::Library,
weight: 10,
limit: 2,
},
PieceWeight {
piece_type: StrongholdPieceType::PortalRoom,
weight: 20,
limit: 1,
},
];
// Pre-calculate total weight (const loop because iter().sum() isn't always const yet)
const TOTAL_WEIGHT: i32 = {
let mut sum = 0;
let mut i = 0;
while i < POSSIBLE_PIECES.len() {
sum += POSSIBLE_PIECES[i].weight;
i += 1;
}
sum
};
#[derive(Clone)]
pub struct StrongholdPiece {
pub piece: StructurePiece,
pub entry_door: EntranceType,
}
impl StrongholdPiece {
pub fn new(r#type: StructurePieceType, chain_length: u32, bbox: BlockBox) -> Self {
Self {
piece: StructurePiece::new(r#type, bbox, chain_length),
entry_door: EntranceType::Opening,
}
}
fn is_in_bounds(bb: &BlockBox) -> bool {
bb.min.y > 10
}
fn generate_entrance(
&self,
chunk: &mut ProtoChunk,
box_limit: &BlockBox,
entrance_type: EntranceType,
x: i32,
y: i32,
z: i32,
) {
match entrance_type {
EntranceType::Opening => {
let air = Block::AIR.default_state;
self.piece.fill_with_outline(
chunk,
box_limit,
false,
x,
y,
z,
x + 2,
y + 2,
z,
air,
air,
);
}
EntranceType::WoodDoor => {
let stone = Block::STONE_BRICKS.default_state;
let _door_lower = Block::OAK_DOOR.default_state;
//let door_upper = Block::OAK_DOOR.default_state.with("half", "upper");
// Frame
self.piece.add_block(chunk, stone, x, y, z, box_limit);
self.piece.add_block(chunk, stone, x, y + 1, z, box_limit);
self.piece.add_block(chunk, stone, x, y + 2, z, box_limit);
self.piece
.add_block(chunk, stone, x + 1, y + 2, z, box_limit);
self.piece
.add_block(chunk, stone, x + 2, y + 2, z, box_limit);
self.piece
.add_block(chunk, stone, x + 2, y + 1, z, box_limit);
self.piece.add_block(chunk, stone, x + 2, y, z, box_limit);
// Door
// self.piece.add_block(chunk, &door_lower, x + 1, y, z, box_limit);
// self.piece.add_block(chunk, &door_upper, x + 1, y + 1, z, box_limit);
}
EntranceType::Grates => {
let air = Block::CAVE_AIR.default_state;
// let bar_w = Block::IRON_BARS.default_state.with("west", "true");
// let bar_e = Block::IRON_BARS.default_state.with("east", "true");
// let bar_ew = Block::IRON_BARS.default_state.with("east", "true").with("west", "true");
self.piece.add_block(chunk, air, x + 1, y, z, box_limit);
self.piece.add_block(chunk, air, x + 1, y + 1, z, box_limit);
// Left side (West connection)
// self.piece.add_block(chunk, &bar_w, x, y, z, box_limit);
// self.piece.add_block(chunk, &bar_w, x, y + 1, z, box_limit);
// // Top beam (Connected East-West)
// self.piece.add_block(chunk, &bar_ew, x, y + 2, z, box_limit);
// self.piece.add_block(chunk, &bar_ew, x + 1, y + 2, z, box_limit);
// self.piece.add_block(chunk, &bar_ew, x + 2, y + 2, z, box_limit);
// // Right side (East connection)
// self.piece.add_block(chunk, &bar_e, x + 2, y + 1, z, box_limit);
// self.piece.add_block(chunk, &bar_e, x + 2, y, z, box_limit);
}
EntranceType::IronDoor => {
let stone = Block::STONE_BRICKS.default_state;
// let door_lower = Block::IRON_DOOR.default_state;
// let door_upper = Block::IRON_DOOR.default_state.with("half", "upper");
// let button_n = Block::STONE_BUTTON.default_state.with("facing", "north");
// let button_s = Block::STONE_BUTTON.default_state.with("facing", "south");
// Frame
self.piece.fill_with_outline(
chunk,
box_limit,
false,
x,
y,
z,
x,
y + 2,
z,
stone,
stone,
);
self.piece
.add_block(chunk, stone, x + 1, y + 2, z, box_limit);
self.piece.fill_with_outline(
chunk,
box_limit,
false,
x + 2,
y,
z,
x + 2,
y + 2,
z,
stone,
stone,
);
// Door
// self.piece.add_block(chunk, &door_lower, x + 1, y, z, box_limit);
// self.piece.add_block(chunk, &door_upper, x + 1, y + 1, z, box_limit);
// // Buttons
// self.piece.add_block(chunk, &button_n, x + 2, y + 1, z + 1, box_limit);
// self.piece.add_block(chunk, &button_s, x + 2, y + 1, z - 1, box_limit);
}
}
}
#[expect(clippy::too_many_arguments)]
pub fn fill_forward_opening(
&self,
start: &StructurePiece,
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
left_right_offset: i32,
height_offset: i32,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
piece: Option<StrongholdPieceType>,
) {
if let Some(facing) = &self.piece.facing {
let bounding_box = self.piece.bounding_box;
let (nx, ny, nz) = match facing {
BlockDirection::North => (
bounding_box.min.x + left_right_offset,
bounding_box.min.y + height_offset,
bounding_box.min.z - 1,
),
BlockDirection::South => (
bounding_box.min.x + left_right_offset,
bounding_box.min.y + height_offset,
bounding_box.max.z + 1,
),
BlockDirection::West => (
bounding_box.min.x - 1,
bounding_box.min.y + height_offset,
bounding_box.min.z + left_right_offset,
),
BlockDirection::East => (
bounding_box.max.x + 1,
bounding_box.min.y + height_offset,
bounding_box.min.z + left_right_offset,
),
_ => return,
};
if let Some(next) = Self::piece_generator(
start,
collector,
random,
nx,
ny,
nz,
facing,
self.piece.chain_length,
piece,
) {
// IMPORTANT: The generator already adds it to the collector if successful.
// We just need to add it to the processing queue.
pieces_to_process.push(next);
}
}
}
pub fn fill_nw_opening(
&self,
start: &StructurePiece,
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
height_offset: i32,
left_right_offset: i32,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
if let Some(facing) = &self.piece.facing {
let bounding_box = self.piece.bounding_box;
// Java switch(direction) logic translated to world coordinates
let (nx, ny, nz, next_facing) = match facing {
BlockDirection::North | BlockDirection::South => (
bounding_box.min.x - 1,
bounding_box.min.y + height_offset,
bounding_box.min.z + left_right_offset,
BlockDirection::West,
),
BlockDirection::West | BlockDirection::East => (
bounding_box.min.x + left_right_offset,
bounding_box.min.y + height_offset,
bounding_box.min.z - 1,
BlockDirection::North,
),
_ => return,
};
if let Some(next) = Self::piece_generator(
start,
collector,
random,
nx,
ny,
nz,
&next_facing,
self.piece.chain_length,
None, // Dynamic piece selection
) {
pieces_to_process.push(next);
}
}
}
pub fn fill_se_opening(
&self,
start: &StructurePiece,
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
height_offset: i32,
left_right_offset: i32,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
if let Some(facing) = &self.piece.facing {
let bounding_box = self.piece.bounding_box;
let (nx, ny, nz, next_facing) = match facing {
BlockDirection::North | BlockDirection::South => (
bounding_box.max.x + 1,
bounding_box.min.y + height_offset,
bounding_box.min.z + left_right_offset,
BlockDirection::East,
),
BlockDirection::West | BlockDirection::East => (
bounding_box.min.x + left_right_offset,
bounding_box.min.y + height_offset,
bounding_box.max.z + 1,
BlockDirection::South,
),
_ => return,
};
if let Some(next) = Self::piece_generator(
start,
collector,
random,
nx,
ny,
nz,
&next_facing,
self.piece.chain_length,
None,
) {
pieces_to_process.push(next);
}
}
}
#[expect(clippy::too_many_arguments)]
pub fn piece_generator(
start: &StructurePiece,
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: &BlockDirection,
chain_length: u32,
piece_type: Option<StrongholdPieceType>,
) -> Option<Box<dyn StructurePieceBase>> {
// 1. Hard limits
if chain_length > 50 {
return None;
}
// Get bounds from the underlying StructurePiece of the start wrapper
let start_box = start.bounding_box;
if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 {
return None;
}
if let Some(piece_type) = piece_type {
let piece = Self::create_piece(
&piece_type,
collector,
random,
x,
y,
z,
orientation,
chain_length,
);
if let Some(piece) = piece {
collector.add_piece(piece.clone_box());
return Some(piece);
}
}
// 2. Pick the piece
let picked_piece =
Self::pick_piece(collector, random, x, y, z, orientation, chain_length + 1);
if let Some(ref p) = picked_piece {
collector.add_piece(p.clone_box());
}
picked_piece
}
fn pick_piece(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: &BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let mut attempt = 0;
while attempt < 5 {
attempt += 1;
let mut j = random.next_bounded_i32(TOTAL_WEIGHT);
for piece_data in POSSIBLE_PIECES.iter() {
j -= piece_data.weight;
if j >= 0 {
continue;
}
if piece_data.limit > 0 {
let type_to_check = piece_data.piece_type.as_structure_type();
let current_count = collector
.pieces
.iter()
.filter(|p| p.get_structure_piece().r#type == type_to_check)
.count();
if current_count >= piece_data.limit as usize {
continue;
}
}
let piece = Self::create_piece(
&piece_data.piece_type,
collector,
random,
x,
y,
z,
orientation,
chain_length,
);
if let Some(p) = piece {
return Some(p);
}
}
}
// Fallback: Small Corridor
if let Some(bbox) = SmallCorridorPiece::create_box(collector, x, y, z, orientation) {
return Some(Box::new(SmallCorridorPiece::new(
chain_length,
bbox,
*orientation,
)));
}
None
}
#[expect(clippy::too_many_arguments)]
fn create_piece(
piece_type: &StrongholdPieceType,
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: &BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
match piece_type {
StrongholdPieceType::FiveWayCrossing => {
FiveWayCrossingPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::Corridor => {
CorridorPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::SquareRoom => {
SquareRoomPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::PortalRoom => {
PortalRoomPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::SpiralStaircase => {
SpiralStaircasePiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::PrisonHall => {
PrisonHallPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::LeftTurn => {
LeftTurnPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::RightTurn => {
RightTurnPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::Stairs => {
StairsPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::ChestCorridor => {
ChestCorridorPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
StrongholdPieceType::Library => {
LibraryPiece::create(collector, random, x, y, z, *orientation, chain_length)
}
}
}
}

View File

@ -0,0 +1,262 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct PortalRoomPiece {
pub piece: StrongholdPiece,
pub spawner_placed: bool,
}
impl PortalRoomPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
_random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -4, -1, 0, 11, 8, 16, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdPortalRoom,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
Some(Box::new(Self {
piece,
spawner_placed: false,
}))
}
}
impl StructurePieceBase for PortalRoomPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
// 1. Main Shell (11x8x16)
inner.fill_outline_random(
0,
0,
0,
10,
7,
15,
&randomizer,
chunk,
false,
random,
&box_limit,
);
// 2. Entrance (Grates)
p.generate_entrance(chunk, &box_limit, EntranceType::Grates, 4, 1, 0);
// 3. Upper Walkways
inner.fill_outline_random(
1,
6,
1,
1,
6,
14,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
9,
6,
1,
9,
6,
14,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
2,
6,
1,
8,
6,
2,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
2,
6,
14,
8,
6,
14,
&randomizer,
chunk,
false,
random,
&box_limit,
);
// 4. Lava Pools (Side pools and central pool)
let lava = Block::LAVA.default_state;
inner.fill_outline_random(
1,
1,
1,
2,
1,
4,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
8,
1,
1,
9,
1,
4,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_with_outline(chunk, &box_limit, false, 1, 1, 1, 1, 1, 3, lava, lava);
inner.fill_with_outline(chunk, &box_limit, false, 9, 1, 1, 9, 1, 3, lava, lava);
inner.fill_outline_random(
3,
1,
8,
7,
1,
12,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 9, 6, 1, 11, lava, lava);
// 5. Iron Bars (Directional panes)
// let bar_ns = Block::IRON_BARS.default_state.with("north", "true").with("south", "true");
// let bar_we = Block::IRON_BARS.default_state.with("west", "true").with("east", "true");
// for j in (3..14).step_by(2) {
// inner.fill_with_outline(chunk, &box_limit, false, 0, 3, j, 0, 4, j, &bar_ns, &bar_ns);
// inner.fill_with_outline(chunk, &box_limit, false, 10, 3, j, 10, 4, j, &bar_ns, &bar_ns);
// }
// for j in (2..9).step_by(2) {
// inner.fill_with_outline(chunk, &box_limit, false, j, 3, 15, j, 4, 15, &bar_we, &bar_we);
// }
// // 6. Stairs leading to Portal
// let stairs_n = Block::STONE_BRICK_STAIRS.default_state.with("facing", "north");
// inner.fill_outline_random(4, 1, 5, 6, 1, 7, &randomizer, chunk, false, random, &box_limit);
// inner.fill_outline_random(4, 2, 6, 6, 2, 7, &randomizer, chunk, false, random, &box_limit);
// inner.fill_outline_random(4, 3, 7, 6, 3, 7, &randomizer, chunk, false, random, &box_limit);
// for k in 4..=6 {
// inner.add_block(chunk, &stairs_n, k, 1, 4, &box_limit);
// inner.add_block(chunk, &stairs_n, k, 2, 5, &box_limit);
// inner.add_block(chunk, &stairs_n, k, 3, 6, &box_limit);
// }
// 7. End Portal Frames and Eyes
let eyes: [bool; 12] = std::array::from_fn(|_| random.next_f32() > 0.9);
let all_eyes = eyes.iter().all(|&eye| eye);
// let f_north = Block::END_PORTAL_FRAME.default_state.with("facing", "north");
// let f_south = Block::END_PORTAL_FRAME.default_state.with("facing", "south");
// let f_east = Block::END_PORTAL_FRAME.default_state.with("facing", "east");
// let f_west = Block::END_PORTAL_FRAME.default_state.with("facing", "west");
// Helper to stringify eye boolean for state mapping
//let eye_str = |b: bool| if b { "true" } else { "false" };
// inner.add_block(chunk, &f_north.with("eye", eye_str(eyes[0])), 4, 3, 8, &box_limit);
// inner.add_block(chunk, &f_north.with("eye", eye_str(eyes[1])), 5, 3, 8, &box_limit);
// inner.add_block(chunk, &f_north.with("eye", eye_str(eyes[2])), 6, 3, 8, &box_limit);
// inner.add_block(chunk, &f_south.with("eye", eye_str(eyes[3])), 4, 3, 12, &box_limit);
// inner.add_block(chunk, &f_south.with("eye", eye_str(eyes[4])), 5, 3, 12, &box_limit);
// inner.add_block(chunk, &f_south.with("eye", eye_str(eyes[5])), 6, 3, 12, &box_limit);
// inner.add_block(chunk, &f_east.with("eye", eye_str(eyes[6])), 3, 3, 9, &box_limit);
// inner.add_block(chunk, &f_east.with("eye", eye_str(eyes[7])), 3, 3, 10, &box_limit);
// inner.add_block(chunk, &f_east.with("eye", eye_str(eyes[8])), 3, 3, 11, &box_limit);
// inner.add_block(chunk, &f_west.with("eye", eye_str(eyes[9])), 7, 3, 9, &box_limit);
// inner.add_block(chunk, &f_west.with("eye", eye_str(eyes[10])), 7, 3, 10, &box_limit);
// inner.add_block(chunk, &f_west.with("eye", eye_str(eyes[11])), 7, 3, 11, &box_limit);
if all_eyes {
let portal = Block::END_PORTAL.default_state;
inner.fill_with_outline(chunk, &box_limit, false, 4, 3, 9, 6, 3, 11, portal, portal);
}
// 8. Spawner (Silverfish)
if !self.spawner_placed {
let pos = inner.offset_pos(5, 3, 6);
if box_limit.contains_pos(&pos) {
self.spawner_placed = true;
let spawner = Block::SPAWNER.default_state;
inner.add_block(chunk, spawner, 5, 3, 6, &box_limit);
// In a real implementation, you'd add the TileEntityData for Silverfish here
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,194 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct PrisonHallPiece {
piece: StrongholdPiece,
}
impl PrisonHallPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
// Main Outline 9x5x11 (0..8, 0..4, 0..10)
let bounding_box = BlockBox::rotated(x, y, z, -1, -1, 0, 9, 5, 11, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdPrisonHall,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self { piece }))
}
}
impl StructurePieceBase for PrisonHallPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
// Prison hall usually only has one forward exit
self.piece
.fill_forward_opening(start, collector, random, 1, 1, pieces_to_process, None);
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let air = Block::AIR.default_state;
// 1. Main Shell 9x5x11
inner.fill_outline_random(
0,
0,
0,
8,
4,
10,
&randomizer,
chunk,
true,
random,
&box_limit,
);
// 2. Entrance (Z=0)
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 1, 0);
// 3. Exit (Forward/Z=10) - Using fill_with_outline (cant_replace_air = false)
inner.fill_with_outline(chunk, &box_limit, false, 1, 1, 10, 3, 3, 10, air, air);
// 4. Cell Stone Separators (X=4)
// Note: Java passes 'false' for cant_replace_air here
inner.fill_outline_random(
4,
1,
1,
4,
3,
1,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
4,
1,
3,
4,
3,
3,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
4,
1,
7,
4,
3,
7,
&randomizer,
chunk,
false,
random,
&box_limit,
);
inner.fill_outline_random(
4,
1,
9,
4,
3,
9,
&randomizer,
chunk,
false,
random,
&box_limit,
);
// 5. Iron Bars with Directional Properties
// let bars_ns = Block::IRON_BARS.default_state.with("north", "true").with("south", "true");
// let bars_nse = bars_ns.with("east", "true");
// let bars_we = Block::IRON_BARS.default_state.with("west", "true").with("east", "true");
// for i in 1..=3 {
// inner.add_block(chunk, &bars_ns, 4, i, 4, &box_limit);
// inner.add_block(chunk, &bars_nse, 4, i, 5, &box_limit);
// inner.add_block(chunk, &bars_ns, 4, i, 6, &box_limit);
// inner.add_block(chunk, &bars_we, 5, i, 5, &box_limit);
// inner.add_block(chunk, &bars_we, 6, i, 5, &box_limit);
// inner.add_block(chunk, &bars_we, 7, i, 5, &box_limit);
// }
// // Top bars above the doors
// inner.add_block(chunk, &bars_ns, 4, 3, 2, &box_limit);
// inner.add_block(chunk, &bars_ns, 4, 3, 8, &box_limit);
// // 6. Iron Doors (2-block high structure)
// let door_bottom = Block::IRON_DOOR.default_state.with("facing", "west").with("half", "lower");
// let door_top = Block::IRON_DOOR.default_state.with("facing", "west").with("half", "upper");
// // Door 1
// inner.add_block(chunk, &door_bottom, 4, 1, 2, &box_limit);
// inner.add_block(chunk, &door_top, 4, 2, 2, &box_limit);
// // Door 2
// inner.add_block(chunk, &door_bottom, 4, 1, 8, &box_limit);
// inner.add_block(chunk, &door_top, 4, 2, 8, &box_limit);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,124 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct RightTurnPiece {
pub piece: StrongholdPiece,
}
impl RightTurnPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
// 5x5x5 box
let bounding_box = BlockBox::rotated(x, y, z, -1, -1, 0, 5, 5, 5, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdRightTurn,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self { piece }))
}
}
impl StructurePieceBase for RightTurnPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
let facing = self.piece.piece.facing.unwrap_or(BlockDirection::North);
// For a Right Turn, we swap the logic from the Left Turn:
// If facing North/East, we go "South-East", otherwise "North-West"
if facing == BlockDirection::North || facing == BlockDirection::East {
self.piece
.fill_se_opening(start, collector, random, 1, 1, pieces_to_process);
} else {
self.piece
.fill_nw_opening(start, collector, random, 1, 1, pieces_to_process);
}
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
// 1. 5x5x5 Outline (Using randomizer)
// Note: Use your existing fill_outline_random for this part
inner.fill_outline_random(
0,
0,
0,
4,
4,
4,
&randomizer,
chunk,
true,
random,
&box_limit,
);
// 2. Entrance (Z=0)
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 1, 0);
// 3. Exit logic based on Facing
let air = Block::AIR.default_state;
let facing = inner.facing.unwrap_or(BlockDirection::North);
if facing == BlockDirection::North || facing == BlockDirection::East {
// Exit on X=4 wall
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 1, 4, 3, 3, air, air);
} else {
inner.fill_with_outline(chunk, &box_limit, false, 0, 1, 1, 0, 3, 3, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,187 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{
EntranceType, StoneBrickRandomizer, StrongholdPiece, StrongholdPieceType,
},
},
},
};
pub struct SpiralStaircasePiece {
pub piece: StrongholdPiece,
orientation: BlockDirection,
is_structure_start: bool,
// Start-specific fields
pub last_piece_data_idx: Option<usize>,
pub portal_room_exists: bool,
pub pieces: Vec<Box<dyn StructurePieceBase>>,
}
impl Clone for SpiralStaircasePiece {
fn clone(&self) -> Self {
Self {
// StrongholdPiece and other types must derive Clone
piece: self.piece.clone(),
orientation: self.orientation,
is_structure_start: self.is_structure_start,
last_piece_data_idx: self.last_piece_data_idx,
portal_room_exists: self.portal_room_exists,
// Use the clone_box helper we added to the trait!
pieces: self.pieces.iter().map(|p| p.clone_box()).collect(),
}
}
}
impl SpiralStaircasePiece {
/// Matches Start(Random random, int i, int j)
pub fn new_start(random: &mut impl RandomImpl, x: i32, z: i32) -> Self {
let orientation = StructurePiece::get_random_horizontal_direction(random);
let bounding_box = BlockBox::create_box(x, 64, z, orientation.get_axis(), 5, 11, 5);
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdSpiralStaircase,
0,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
Self {
piece,
orientation,
is_structure_start: true,
last_piece_data_idx: None,
portal_room_exists: false,
pieces: Vec::new(),
}
}
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -1, -7, 0, 5, 11, 5, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdSpiralStaircase,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self {
piece,
orientation,
is_structure_start: false,
last_piece_data_idx: None,
portal_room_exists: false,
pieces: Vec::new(),
}))
}
pub fn has_portal_room(&self) -> bool {
self.portal_room_exists
}
}
impl StructurePieceBase for SpiralStaircasePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
self.piece.fill_forward_opening(
start,
collector,
random,
1,
1,
pieces_to_process,
Some(StrongholdPieceType::FiveWayCrossing),
);
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
// 1. fillWithOutline (5x11x5 box)
p.piece.fill_outline_random(
0,
0,
0,
4,
10,
4,
&randomizer,
chunk,
true,
random,
&box_limit,
);
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 7, 0);
p.generate_entrance(chunk, &box_limit, EntranceType::Opening, 1, 1, 4);
// 4. Spiral Staircase Blocks
let stone = Block::STONE_BRICKS.default_state;
let slab = Block::SMOOTH_STONE_SLAB.default_state;
// Use the inner StructurePiece for block placement helpers
let inner = &p.piece;
inner.add_block(chunk, stone, 2, 6, 1, &box_limit);
inner.add_block(chunk, stone, 1, 5, 1, &box_limit);
inner.add_block(chunk, slab, 1, 6, 1, &box_limit);
inner.add_block(chunk, stone, 1, 5, 2, &box_limit);
inner.add_block(chunk, stone, 1, 4, 3, &box_limit);
inner.add_block(chunk, slab, 1, 5, 3, &box_limit);
inner.add_block(chunk, stone, 2, 4, 3, &box_limit);
inner.add_block(chunk, stone, 3, 3, 3, &box_limit);
inner.add_block(chunk, slab, 3, 4, 3, &box_limit);
inner.add_block(chunk, stone, 3, 3, 2, &box_limit);
inner.add_block(chunk, stone, 3, 2, 1, &box_limit);
inner.add_block(chunk, slab, 3, 3, 1, &box_limit);
inner.add_block(chunk, stone, 2, 2, 1, &box_limit);
inner.add_block(chunk, stone, 1, 1, 1, &box_limit);
inner.add_block(chunk, slab, 1, 2, 1, &box_limit);
// Final bottom blocks from Java source
inner.add_block(chunk, stone, 1, 1, 2, &box_limit);
inner.add_block(chunk, slab, 1, 1, 3, &box_limit);
}
}

View File

@ -0,0 +1,256 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct SquareRoomPiece {
piece: StrongholdPiece,
room_type: u32,
}
impl SquareRoomPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -4, -1, 0, 11, 7, 11, &orientation);
// 2. Check world bounds and intersections
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
// 3. Construct the piece
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdSquareRoom,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
let room_type = random.next_bounded_i32(5) as u32;
Some(Box::new(Self { piece, room_type }))
}
}
impl StructurePieceBase for SquareRoomPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
// 1. Forward Opening
// Java: this.fillForwardOpening((Start)start, holder, random, 4, 1);
self.piece.fill_forward_opening(
start,
collector,
random,
4, // left_right_offset
1, // height_offset
pieces_to_process,
None,
);
// 2. Left Opening (NW)
// Java: this.fillNWOpening((Start)start, holder, random, 1, 4);
self.piece.fill_nw_opening(
start,
collector,
random,
1, // height_offset
4, // left_right_offset
pieces_to_process,
);
// 3. Right Opening (SE)
// Java: this.fillSEOpening((Start)start, holder, random, 1, 4);
self.piece.fill_se_opening(
start,
collector,
random,
1, // height_offset
4, // left_right_offset
pieces_to_process,
);
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
let air = Block::AIR.default_state;
// 1. Main Outline (10x6x10) - Matches Java: fillWithOutline(..., 0,0,0, 10,6,10, ...)
inner.fill_outline_random(
0,
0,
0,
10,
6,
10,
&randomizer,
chunk,
true,
random,
&box_limit,
);
// 2. Main Entrance (Z=0, aligned to X=4)
// Matches Java: generateEntrance(..., 4, 1, 0)
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 4, 1, 0);
// 3. Clear Exits
// Forward Exit (Z=10) -> fillWithOutline(..., 4,1,10, 6,3,10, AIR, AIR, false)
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 10, 6, 3, 10, air, air);
// Left Side Exit (X=0) -> fillWithOutline(..., 0,1,4, 0,3,6, AIR, AIR, false)
inner.fill_with_outline(chunk, &box_limit, false, 0, 1, 4, 0, 3, 6, air, air);
// Right Side Exit (X=10) -> fillWithOutline(..., 10,1,4, 10,3,6, AIR, AIR, false)
inner.fill_with_outline(chunk, &box_limit, false, 10, 1, 4, 10, 3, 6, air, air);
// 4. Room Specific Decorations
match self.room_type {
0 => {
let stone_brick = Block::STONE_BRICKS.default_state;
let smooth_slab = Block::SMOOTH_STONE_SLAB.default_state;
// Central Pillar
inner.add_block(chunk, stone_brick, 5, 1, 5, &box_limit);
inner.add_block(chunk, stone_brick, 5, 2, 5, &box_limit);
inner.add_block(chunk, stone_brick, 5, 3, 5, &box_limit);
// // Torches
// inner.add_block(chunk, &Block::WALL_TORCH.default_state.with("facing", "west"), 4, 3, 5, &box_limit);
// inner.add_block(chunk, &Block::WALL_TORCH.default_state.with("facing", "east"), 6, 3, 5, &box_limit);
// inner.add_block(chunk, &Block::WALL_TORCH.default_state.with("facing", "south"), 5, 3, 4, &box_limit);
// inner.add_block(chunk, &Block::WALL_TORCH.default_state.with("facing", "north"), 5, 3, 6, &box_limit);
// Floor Pattern (Slabs)
inner.add_block(chunk, smooth_slab, 4, 1, 4, &box_limit);
inner.add_block(chunk, smooth_slab, 4, 1, 5, &box_limit);
inner.add_block(chunk, smooth_slab, 4, 1, 6, &box_limit);
inner.add_block(chunk, smooth_slab, 6, 1, 4, &box_limit);
inner.add_block(chunk, smooth_slab, 6, 1, 5, &box_limit);
inner.add_block(chunk, smooth_slab, 6, 1, 6, &box_limit);
inner.add_block(chunk, smooth_slab, 5, 1, 4, &box_limit);
inner.add_block(chunk, smooth_slab, 5, 1, 6, &box_limit);
}
1 => {
let stone_brick = Block::STONE_BRICKS.default_state;
// Ring of bricks
for i in 0..5 {
inner.add_block(chunk, stone_brick, 3, 1, 3 + i, &box_limit);
inner.add_block(chunk, stone_brick, 7, 1, 3 + i, &box_limit);
inner.add_block(chunk, stone_brick, 3 + i, 1, 3, &box_limit);
inner.add_block(chunk, stone_brick, 3 + i, 1, 7, &box_limit);
}
// Central Water Column
inner.add_block(chunk, stone_brick, 5, 1, 5, &box_limit);
inner.add_block(chunk, stone_brick, 5, 2, 5, &box_limit);
inner.add_block(chunk, stone_brick, 5, 3, 5, &box_limit);
inner.add_block(chunk, Block::WATER.default_state, 5, 4, 5, &box_limit);
}
2 => {
let cobble = Block::COBBLESTONE.default_state;
// Cobblestone shelves/walls
for i in 1..=9 {
inner.add_block(chunk, cobble, 1, 3, i, &box_limit);
inner.add_block(chunk, cobble, 9, 3, i, &box_limit);
}
for i in 1..=9 {
inner.add_block(chunk, cobble, i, 3, 1, &box_limit);
inner.add_block(chunk, cobble, i, 3, 9, &box_limit);
}
// Central structure supports
inner.add_block(chunk, cobble, 5, 1, 4, &box_limit);
inner.add_block(chunk, cobble, 5, 1, 6, &box_limit);
inner.add_block(chunk, cobble, 5, 3, 4, &box_limit);
inner.add_block(chunk, cobble, 5, 3, 6, &box_limit);
inner.add_block(chunk, cobble, 4, 1, 5, &box_limit);
inner.add_block(chunk, cobble, 6, 1, 5, &box_limit);
inner.add_block(chunk, cobble, 4, 3, 5, &box_limit);
inner.add_block(chunk, cobble, 6, 3, 5, &box_limit);
// Pillars
for i in 1..=3 {
inner.add_block(chunk, cobble, 4, i, 4, &box_limit);
inner.add_block(chunk, cobble, 6, i, 4, &box_limit);
inner.add_block(chunk, cobble, 4, i, 6, &box_limit);
inner.add_block(chunk, cobble, 6, i, 6, &box_limit);
}
inner.add_block(chunk, Block::WALL_TORCH.default_state, 5, 3, 5, &box_limit);
// Upper floor (Oak Planks)
let oak = Block::OAK_PLANKS.default_state;
for i in 2..=8 {
inner.add_block(chunk, oak, 2, 3, i, &box_limit);
inner.add_block(chunk, oak, 3, 3, i, &box_limit);
// Holes for ladders/structure
if i <= 3 || i >= 7 {
inner.add_block(chunk, oak, 4, 3, i, &box_limit);
inner.add_block(chunk, oak, 5, 3, i, &box_limit);
inner.add_block(chunk, oak, 6, 3, i, &box_limit);
}
inner.add_block(chunk, oak, 7, 3, i, &box_limit);
inner.add_block(chunk, oak, 8, 3, i, &box_limit);
}
// Ladder
// let ladder = Block::LADDER.default_state.with("facing", "west");
// inner.add_block(chunk, &ladder, 9, 1, 3, &box_limit);
// inner.add_block(chunk, &ladder, 9, 2, 3, &box_limit);
// inner.add_block(chunk, &ladder, 9, 3, 3, &box_limit);
// Chest
// Note: Ensure you have `LootTables` defined or use a string ID
// p.add_chest(chunk, &box_limit, random, 3, 4, 8, LootTables::StrongholdCrossingChest);
inner.add_block(chunk, Block::CHEST.default_state, 3, 4, 8, &box_limit);
}
_ => {}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -0,0 +1,124 @@
use pumpkin_data::Block;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
generation::structure::{
piece::StructurePieceType,
structures::{
StructurePiece, StructurePieceBase, StructurePiecesCollector,
stronghold::{EntranceType, StoneBrickRandomizer, StrongholdPiece},
},
},
};
#[derive(Clone)]
pub struct StairsPiece {
pub piece: StrongholdPiece,
}
impl StairsPiece {
pub fn create(
collector: &mut StructurePiecesCollector,
random: &mut impl RandomImpl,
x: i32,
y: i32,
z: i32,
orientation: BlockDirection,
chain_length: u32,
) -> Option<Box<dyn StructurePieceBase>> {
let bounding_box = BlockBox::rotated(x, y, z, -1, -7, 0, 5, 11, 8, &orientation);
if !StrongholdPiece::is_in_bounds(&bounding_box)
|| collector.get_intersecting(&bounding_box).is_some()
{
return None;
}
let mut piece = StrongholdPiece::new(
StructurePieceType::StrongholdStairs,
chain_length,
bounding_box,
);
piece.piece.set_facing(Some(orientation));
piece.entry_door = EntranceType::get_random(random);
Some(Box::new(Self { piece }))
}
}
impl StructurePieceBase for StairsPiece {
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece.piece
}
fn fill_openings(
&self,
start: &StructurePiece,
random: &mut RandomGenerator,
collector: &mut StructurePiecesCollector,
pieces_to_process: &mut Vec<Box<dyn StructurePieceBase>>,
) {
self.piece
.fill_forward_opening(start, collector, random, 1, 1, pieces_to_process, None);
}
fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) {
let randomizer = StoneBrickRandomizer;
let box_limit = self.piece.piece.bounding_box;
let p = &self.piece;
let inner = &p.piece;
// 1. Outer Box 5x11x8
inner.fill_outline_random(
0,
0,
0,
4,
10,
7,
&randomizer,
chunk,
true,
random,
&box_limit,
);
// 2. Entrance (Top)
p.generate_entrance(chunk, &box_limit, self.piece.entry_door, 1, 7, 0);
// 3. Exit (Bottom - Opening only)
p.generate_entrance(chunk, &box_limit, EntranceType::Opening, 1, 1, 7);
// 4. Stairs Generation
let stair_state = Block::COBBLESTONE_STAIRS.default_state;
let stone_bricks = Block::STONE_BRICKS.default_state;
for i in 0..6 {
// Place the stair blocks
inner.add_block(chunk, stair_state, 1, 6 - i, 1 + i, &box_limit);
inner.add_block(chunk, stair_state, 2, 6 - i, 1 + i, &box_limit);
inner.add_block(chunk, stair_state, 3, 6 - i, 1 + i, &box_limit);
// Fill underneath the stairs for stability
if i < 5 {
inner.add_block(chunk, stone_bricks, 1, 5 - i, 1 + i, &box_limit);
inner.add_block(chunk, stone_bricks, 2, 5 - i, 1 + i, &box_limit);
inner.add_block(chunk, stone_bricks, 3, 5 - i, 1 + i, &box_limit);
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -1,204 +1,240 @@
use std::sync::Arc;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_util::{ use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
HeightMap,
math::{
block_box::BlockBox,
position::BlockPos,
vector3::{Axis, Vector3},
},
};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::{
positions::chunk_pos::{get_center_x, get_center_z}, positions::chunk_pos::{get_center_x, get_center_z},
structure::structures::{ structure::{
StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, fill, piece::StructurePieceType,
fill_downwards, shiftable_piece::ShiftableStructurePiece,
structures::{
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
StructurePiecesCollector, StructurePosition,
},
}, },
}, },
}; };
#[derive(Deserialize, Clone)] #[derive(Deserialize)]
pub struct SwampHutGenerator; pub struct SwampHutGenerator;
impl StructureGenerator for SwampHutGenerator { impl StructureGenerator for SwampHutGenerator {
fn get_structure_position( fn get_structure_position(
&self, &self,
_seed: i64, mut context: StructureGeneratorContext,
chunk_x: i32,
chunk_z: i32,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let x = get_center_x(chunk_x); let x = get_center_x(context.chunk_x);
let z = get_center_z(chunk_z); let z = get_center_z(context.chunk_z);
// TODO: random axis
let bounding_box = BlockBox::create_box(x, 64, z, Axis::X, 7, 7, 9);
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(SwampHutPiece { bounding_box })); collector.add_piece(Box::new(SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece::new(
StructurePieceType::SwampHut,
x,
64,
z,
7,
7,
9,
StructurePiece::get_random_horizontal_direction(&mut context.random).get_axis(),
),
}));
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new(x, 64, z), start_pos: BlockPos::new(x, 64, z),
collector: collector.into(), collector: Arc::new(collector.into()),
}) })
} }
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct SwampHutPiece { pub struct SwampHutPiece {
bounding_box: BlockBox, shiftable_structure_piece: ShiftableStructurePiece,
} }
impl StructurePiece for SwampHutPiece { impl StructurePieceBase for SwampHutPiece {
fn bounding_box(&self) -> &BlockBox { fn clone_box(&self) -> Box<dyn StructurePieceBase> {
&self.bounding_box Box::new(self.clone())
} }
fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) {
let min = self.bounding_box.min; if !self
let origin_x = min.x; .shiftable_structure_piece
let origin_z = min.z; .adjust_to_average_height(chunk)
{
// Vanilla uses adjustToAverageHeight. return;
let surface_y = chunk.get_top_y(&HeightMap::OceanFloorWg, origin_x + 3, origin_z + 3);
let origin_y = surface_y + 1;
let spruce_planks = &Block::SPRUCE_PLANKS.default_state;
let oak_log = &Block::OAK_LOG.default_state;
let oak_fence = &Block::OAK_FENCE.default_state;
let air = &Block::AIR.default_state;
// 1. THE FLOOR
fill(
origin_x + 1,
origin_y + 1,
origin_z + 1,
origin_x + 5,
origin_y + 1,
origin_z + 7,
spruce_planks,
chunk,
);
// 2. THE CEILING
fill(
origin_x + 1,
origin_y + 4,
origin_z + 2,
origin_x + 5,
origin_y + 4,
origin_z + 7,
spruce_planks,
chunk,
);
// 3. THE ENTRANCE PLATFORM
fill(
origin_x + 2,
origin_y + 1,
origin_z,
origin_x + 4,
origin_y + 1,
origin_z,
spruce_planks,
chunk,
);
// Front Wall
fill(
origin_x + 2,
origin_y + 2,
origin_z + 2,
origin_x + 3,
origin_y + 3,
origin_z + 2,
spruce_planks,
chunk,
);
// Left Wall
fill(
origin_x + 1,
origin_y + 2,
origin_z + 3,
origin_x + 1,
origin_y + 3,
origin_z + 6,
spruce_planks,
chunk,
);
// Right Wall
fill(
origin_x + 5,
origin_y + 2,
origin_z + 3,
origin_x + 5,
origin_y + 3,
origin_z + 6,
spruce_planks,
chunk,
);
// Back Wall
fill(
origin_x + 2,
origin_y + 2,
origin_z + 7,
origin_x + 4,
origin_y + 3,
origin_z + 7,
spruce_planks,
chunk,
);
// 5. THE LOGS
let logs = [(1, 2), (5, 2), (1, 7), (5, 7)];
for (lx, lz) in logs {
fill(
origin_x + lx,
origin_y,
origin_z + lz,
origin_x + lx,
origin_y + 3,
origin_z + lz,
oak_log,
chunk,
);
fill_downwards(origin_x + lx, origin_y - 1, origin_z + lz, oak_log, chunk);
} }
// Placed at Y=2 (feet level) or Y=3 (eye level) let box_limit = self.shiftable_structure_piece.piece.bounding_box;
let y_floor = origin_y + 2; let p = &self.shiftable_structure_piece.piece;
let y_eye = origin_y + 3;
chunk.set_block_state( let spruce_planks = Block::SPRUCE_PLANKS.default_state;
&Vector3::new(origin_x + 3, y_floor, origin_z + 6), let oak_log = Block::OAK_LOG.default_state;
Block::CRAFTING_TABLE.default_state, let oak_fence = Block::OAK_FENCE.default_state;
let air = Block::AIR.default_state;
p.fill_with_outline(
chunk,
&box_limit,
false,
1,
1,
1,
5,
1,
7,
spruce_planks,
spruce_planks,
); );
chunk.set_block_state( p.fill_with_outline(
&Vector3::new(origin_x + 4, y_floor, origin_z + 6), chunk,
Block::CAULDRON.default_state, &box_limit,
false,
1,
4,
2,
5,
4,
7,
spruce_planks,
spruce_planks,
); );
chunk.set_block_state( p.fill_with_outline(
&Vector3::new(origin_x + 1, y_eye, origin_z + 5), chunk,
&box_limit,
false,
2,
1,
0,
4,
1,
0,
spruce_planks,
spruce_planks,
);
p.fill_with_outline(
chunk,
&box_limit,
false,
2,
2,
2,
3,
3,
2,
spruce_planks,
spruce_planks,
);
p.fill_with_outline(
chunk,
&box_limit,
false,
1,
2,
3,
1,
3,
6,
spruce_planks,
spruce_planks,
);
p.fill_with_outline(
chunk,
&box_limit,
false,
5,
2,
3,
5,
3,
6,
spruce_planks,
spruce_planks,
);
p.fill_with_outline(
chunk,
&box_limit,
false,
2,
2,
7,
4,
3,
7,
spruce_planks,
spruce_planks,
);
p.fill_with_outline(chunk, &box_limit, false, 1, 0, 2, 1, 3, 2, oak_log, oak_log);
p.fill_with_outline(chunk, &box_limit, false, 5, 0, 2, 5, 3, 2, oak_log, oak_log);
p.fill_with_outline(chunk, &box_limit, false, 1, 0, 7, 1, 3, 7, oak_log, oak_log);
p.fill_with_outline(chunk, &box_limit, false, 5, 0, 7, 5, 3, 7, oak_log, oak_log);
p.add_block(chunk, oak_fence, 2, 3, 2, &box_limit);
p.add_block(chunk, oak_fence, 3, 3, 7, &box_limit);
p.add_block(chunk, air, 1, 3, 4, &box_limit);
p.add_block(chunk, air, 5, 3, 4, &box_limit);
p.add_block(chunk, air, 5, 3, 5, &box_limit);
p.add_block(
chunk,
Block::POTTED_RED_MUSHROOM.default_state, Block::POTTED_RED_MUSHROOM.default_state,
1,
3,
5,
&box_limit,
); );
p.add_block(
chunk,
Block::CRAFTING_TABLE.default_state,
3,
2,
6,
&box_limit,
);
p.add_block(chunk, Block::CAULDRON.default_state, 4, 2, 6, &box_limit);
p.add_block(chunk, oak_fence, 1, 2, 1, &box_limit);
p.add_block(chunk, oak_fence, 5, 2, 1, &box_limit);
// Fences & Gaps // let stairs_n = Block::SPRUCE_STAIRS.default_state.with("facing", "north");
chunk.set_block_state(&Vector3::new(origin_x + 2, y_eye, origin_z + 2), oak_fence); // Above the door // let stairs_e = Block::SPRUCE_STAIRS.default_state.with("facing", "east");
chunk.set_block_state(&Vector3::new(origin_x + 3, y_eye, origin_z + 7), oak_fence); // Back window // let stairs_w = Block::SPRUCE_STAIRS.default_state.with("facing", "west");
chunk.set_block_state( // let stairs_s = Block::SPRUCE_STAIRS.default_state.with("facing", "south");
&Vector3::new(origin_x + 1, y_floor, origin_z + 1),
oak_fence,
); // Balcony left
chunk.set_block_state(
&Vector3::new(origin_x + 5, y_floor, origin_z + 1),
oak_fence,
); // Balcony right
// Clear window air (Java: addBlock AIR at 1, 3, 4 and 5, 3, 4/5) // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, &stairs_n, &stairs_n);
chunk.set_block_state(&Vector3::new(origin_x + 1, y_eye, origin_z + 4), air); // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, &stairs_e, &stairs_e);
chunk.set_block_state(&Vector3::new(origin_x + 5, y_eye, origin_z + 4), air); // p.fill_with_outline(chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, &stairs_w, &stairs_w);
chunk.set_block_state(&Vector3::new(origin_x + 5, y_eye, origin_z + 5), air); // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 8, 6, 4, 8, &stairs_s, &stairs_s);
// p.add_block(chunk, &stairs_n.with("shape", "outer_right"), 0, 4, 1, &box_limit);
// p.add_block(chunk, &stairs_n.with("shape", "outer_left"), 6, 4, 1, &box_limit);
// p.add_block(chunk, &stairs_s.with("shape", "outer_left"), 0, 4, 8, &box_limit);
// p.add_block(chunk, &stairs_s.with("shape", "outer_right"), 6, 4, 8, &box_limit);
for i in [2, 7] {
for j in [1, 5] {
p.fill_downwards(chunk, oak_log, j, -1, i, &box_limit);
}
}
// if !self.has_witch {
// let world_coords = p.get_world_coords(2, 2, 5);
// if box_limit.contains(world_coords.0, world_coords.1, world_coords.2) {
// self.has_witch = true;
// // TODO: chunk.add_entity(Witch, world_coords)
// }
// }
// TODO: self.spawn_cat(chunk, &box_limit);
}
fn get_structure_piece(&self) -> &super::StructurePiece {
&self.shiftable_structure_piece.piece
}
fn get_structure_piece_mut(&mut self) -> &mut super::StructurePiece {
&mut self.shiftable_structure_piece.piece
} }
} }

View File

@ -302,18 +302,22 @@ impl Level {
self.shut_down_chunk_system.store(true, Ordering::Relaxed); self.shut_down_chunk_system.store(true, Ordering::Relaxed);
self.level_channel.notify(); self.level_channel.notify();
{ let handles: Vec<_> = {
let mut lock = self.thread_tracker.lock().unwrap(); let mut lock = self.thread_tracker.lock().unwrap();
log::info!("Wait {} jobs stop", lock.len()); log::info!("Shutting down {} jobs", lock.len());
while let Some(i) = lock.pop() { lock.drain(..).collect()
log::info!( };
"Waiting Thread {:?} {} stop",
i.thread().id(), for handle in handles {
i.thread().name().unwrap_or("unknown") log::info!(
); "Waiting for thread {:?} ({}) to stop",
i.join().unwrap(); handle.thread().id(),
handle.thread().name().unwrap_or("unknown")
);
if let Err(e) = handle.join() {
log::error!("Thread panicked during execution: {:?}", e);
} }
log::info!("All Thread stop");
} }
log::info!("Wait chunk system tasks stop"); log::info!("Wait chunk system tasks stop");

View File

@ -33,7 +33,7 @@ impl NoteBlock {
} }
} }
fn get_note_pitch(note: u16) -> f32 { fn get_note_pitch(note: u16) -> f32 {
2.0f64.powf((f64::from(note) - 12.0) / 12.0) as f32 2.0f32.powf((f32::from(note) - 12.0) / 12.0)
} }
async fn get_state_with_instrument( async fn get_state_with_instrument(

View File

@ -44,8 +44,8 @@ impl LookControl {
x, x,
y, y,
z, z,
mob.get_max_look_yaw_change() as f32, mob.get_max_look_yaw_change(),
mob.get_max_look_pitch_change() as f32, mob.get_max_look_pitch_change(),
); );
} }
@ -105,7 +105,7 @@ impl LookControl {
let navigator = mob_entity.navigator.lock().await; let navigator = mob_entity.navigator.lock().await;
if !navigator.is_idle() { if !navigator.is_idle() {
let entity = &mob_entity.living_entity.entity; let entity = &mob_entity.living_entity.entity;
let max_head_rotation = mob.get_max_head_rotation() as f32; let max_head_rotation = mob.get_max_head_rotation();
entity.head_yaw.store(clamp_angle( entity.head_yaw.store(clamp_angle(
entity.head_yaw.load(), entity.head_yaw.load(),
entity.body_yaw.load(), entity.body_yaw.load(),

View File

@ -66,16 +66,16 @@ pub trait Mob: EntityBase + Send + Sync {
rand::rng() rand::rng()
} }
fn get_max_look_yaw_change(&self) -> i32 { fn get_max_look_yaw_change(&self) -> f32 {
10 10.0
} }
fn get_max_look_pitch_change(&self) -> i32 { fn get_max_look_pitch_change(&self) -> f32 {
40 40.0
} }
fn get_max_head_rotation(&self) -> i32 { fn get_max_head_rotation(&self) -> f32 {
75 75.0
} }
fn get_mob_entity(&self) -> &MobEntity; fn get_mob_entity(&self) -> &MobEntity;

View File

@ -1027,16 +1027,15 @@ impl Player {
} }
} }
#[expect(clippy::cast_precision_loss)]
pub async fn progress_motion(&self, delta_pos: Vector3<f64>) { pub async fn progress_motion(&self, delta_pos: Vector3<f64>) {
// TODO: Swimming, gliding... // TODO: Swimming, gliding...
if self.living_entity.entity.on_ground.load(Ordering::Relaxed) { if self.living_entity.entity.on_ground.load(Ordering::Relaxed) {
let delta = (delta_pos.horizontal_length() * 100.0).round() as i32; let delta = (delta_pos.horizontal_length() * 100.0).round() as f32;
if delta > 0 { if delta > 0.0 {
if self.living_entity.entity.sprinting.load(Ordering::Relaxed) { if self.living_entity.entity.sprinting.load(Ordering::Relaxed) {
self.add_exhaustion(0.1 * delta as f32 * 0.01).await; self.add_exhaustion(0.1 * delta * 0.01).await;
} else { } else {
self.add_exhaustion(0.0 * delta as f32 * 0.01).await; self.add_exhaustion(0.0 * delta * 0.01).await;
} }
} }
} }