diff --git a/Cargo.lock b/Cargo.lock index 88d805fda..6ab2f4512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,13 +125,12 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -210,9 +209,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base16ct" -version = "0.3.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" @@ -228,9 +227,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "bitflags" @@ -282,9 +281,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "shlex", @@ -401,9 +400,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "compression-core", "flate2", @@ -463,9 +462,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-oid" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "convert_case" @@ -625,12 +624,13 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossfire" -version = "2.1.9" +version = "2.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19636ec7667a64122ad23414cd773dcdd32532a7df7a4f68ad16bba0db5f02" +checksum = "fd901251b9b46c1752c85edfee0aee718c03a85a065f4126d32e5d6d419edf48" dependencies = [ "crossbeam-queue", "crossbeam-utils", + "enum_dispatch", "futures-core", "parking_lot", ] @@ -706,9 +706,9 @@ dependencies = [ [[package]] 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" -checksum = "e6165b8029cdc3e765b74d3548f85999ee799d5124877ce45c2c85ca78e4d4aa" +checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809" dependencies = [ "hybrid-array", ] @@ -755,7 +755,7 @@ version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" dependencies = [ - "const-oid 0.10.1", + "const-oid 0.10.2", "pem-rfc7468 1.0.0", "zeroize", ] @@ -810,8 +810,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9" dependencies = [ "block-buffer 0.11.0", - "const-oid 0.10.1", - "crypto-common 0.2.0-rc.8", + "const-oid 0.10.2", + "crypto-common 0.2.0-rc.9", "subtle", ] @@ -945,9 +945,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "flate2" @@ -1129,9 +1129,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1447,9 +1447,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1498,9 +1498,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -1511,9 +1511,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -1538,9 +1538,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -1593,9 +1593,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ "hashbrown 0.16.1", ] @@ -2050,9 +2050,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -2307,9 +2307,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -2322,8 +2322,8 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.10.0-rc.5" -source = "git+https://github.com/rust-random/rand#75fe38fff59e5abb16ed5a146d61960cad27dc0f" +version = "0.10.0-rc.6" +source = "git+https://github.com/rust-random/rand#da2211b80e7217403b2bd8305a98ae7bc61c1146" dependencies = [ "chacha20", "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" checksum = "e499c52862d75a86c0024cc99dcb6d7127d15af3beae7b03573d62fab7ade08a" dependencies = [ - "const-oid 0.10.1", + "const-oid 0.10.2", "crypto-bigint 0.7.0-rc.10", "crypto-primes", "digest 0.11.0-rc.5", @@ -2477,9 +2477,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -2607,9 +2607,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -2640,11 +2640,11 @@ dependencies = [ [[package]] name = "serdect" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" +checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" dependencies = [ - "base16ct 0.3.0", + "base16ct 1.0.0", "serde", ] @@ -2848,9 +2848,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.112" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3029,9 +3029,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -3040,9 +3040,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3054,9 +3054,9 @@ dependencies = [ [[package]] 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" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", @@ -3297,9 +3297,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -3683,18 +3683,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -3763,6 +3763,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/Cargo.toml b/Cargo.toml index 12411e2c4..010758ac8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ serde_json = "1.0" tempfile = { version = "3.24.0", default-features = false } proc-macro-error2 = "2" aes = "0.8" -async-compression = "0.4.36" +async-compression = "0.4.37" base64 = "0.22.1" bitflags = "2.10.0" cesu8 = "1.1" @@ -60,7 +60,7 @@ console-subscriber = { version = "0.5.0", default-features = false } crc-fast = "1.9.0" criterion = { version = "0.8", default-features = false } crossbeam-utils = "0.8.21" -crossfire = "2.1.9" +crossfire = "2.1.10" dashmap = "6.1" ecdsa = "0.16.9" enum_dispatch = "0.3.13" @@ -68,10 +68,10 @@ env_logger = "0.11.8" flate2 = "1.1.5" heck = "0.5" hmac = "=0.13.0-rc.3" -indexmap = "2.12" +indexmap = "2.13" itertools = "0.14.0" libloading = "0.8" -lru = "0.16.2" +lru = "0.16.3" lz4-java-wrc = "0.2.0" md5 = "0.8" num-bigint = "0.4" @@ -107,6 +107,6 @@ take_mut = "0.2.2" temp-dir = "0.1.16" thread_local = "1.1.9" time = "0.3" -tokio-util = "0.7.17" +tokio-util = "0.7.18" toml = "0.9" ureq = "3.1.4" diff --git a/README.md b/README.md index 5b77a9cdb..5730fb3cb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # 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) [![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) diff --git a/egg-pumpkin.json b/egg-pumpkin.json index f2c4eea87..61241144a 100644 --- a/egg-pumpkin.json +++ b/egg-pumpkin.json @@ -22,7 +22,7 @@ }, "scripts": { "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", "entrypoint": "ash" } diff --git a/pumpkin-util/src/lib.rs b/pumpkin-util/src/lib.rs index 2b7509d74..de2148887 100644 --- a/pumpkin-util/src/lib.rs +++ b/pumpkin-util/src/lib.rs @@ -5,6 +5,8 @@ pub use difficulty::Difficulty; pub use gamemode::GameMode; pub use permission::PermissionLvl; +use crate::math::vector3::Axis; + pub mod biome; pub mod difficulty; pub mod gamemode; @@ -83,6 +85,26 @@ pub enum ProfileAction { 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 /// the same time pub struct MutableSplitSlice<'a, T> { diff --git a/pumpkin-util/src/math/block_box.rs b/pumpkin-util/src/math/block_box.rs index 44c332fc6..b781ba732 100644 --- a/pumpkin-util/src/math/block_box.rs +++ b/pumpkin-util/src/math/block_box.rs @@ -1,6 +1,9 @@ -use crate::math::{ - position::BlockPos, - vector3::{Axis, Vector3}, +use crate::{ + BlockDirection, + math::{ + position::BlockPos, + vector3::{Axis, Vector3}, + }, }; #[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 { Self { 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) -> 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 { self.min.x < other.max.x && self.max.x > other.min.x && self.min.y < other.max.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(boxes: I) -> Option + where + I: IntoIterator, + { + let mut iter = boxes.into_iter(); + let mut result = iter.next()?; // Return None if empty + + for b in iter { + result.encompass(&b); + } + Some(result) + } } diff --git a/pumpkin-util/src/random/mod.rs b/pumpkin-util/src/random/mod.rs index 59e0c3f33..f6422461f 100644 --- a/pumpkin-util/src/random/mod.rs +++ b/pumpkin-util/src/random/mod.rs @@ -62,10 +62,10 @@ macro_rules! population_seed_fn { // TODO: Write unit test for this #[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 - .wrapping_add(index as u64) - .wrapping_add(10_000u64.wrapping_mul(step as u64)) + .wrapping_add(index) + .wrapping_add(10_000u64.wrapping_mul(step)) } #[inline] diff --git a/pumpkin-world/src/biome/mod.rs b/pumpkin-world/src/biome/mod.rs index 6fcda0da5..03942790d 100644 --- a/pumpkin-world/src/biome/mod.rs +++ b/pumpkin-world/src/biome/mod.rs @@ -66,12 +66,9 @@ mod test { use crate::{ GENERATION_SETTINGS, GeneratorSetting, GlobalRandomConfig, ProtoChunk, chunk::palette::BIOME_NETWORK_MAX_BITS, - generation::{ - noise::router::{ - multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, - proto_noise_router::ProtoNoiseRouters, - }, - proto_chunk::TerrainCache, + generation::noise::router::{ + multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, + proto_noise_router::ProtoNoiseRouters, }, }; @@ -109,7 +106,7 @@ mod test { let surface_settings = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .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(); for data in expected_data.into_iter() { diff --git a/pumpkin-world/src/chunk_system.rs b/pumpkin-world/src/chunk_system.rs index ea26b429c..739648c76 100644 --- a/pumpkin-world/src/chunk_system.rs +++ b/pumpkin-world/src/chunk_system.rs @@ -621,6 +621,8 @@ impl StagedChunkEnum { } const fn get_direct_dependencies(self) -> &'static [StagedChunkEnum] { 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::StructureStart => &[Self::Biomes], Self::StructureReferences => &[Self::StructureStart], @@ -774,8 +776,14 @@ impl 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) { - let proto_chunk = self.get_proto_chunk_mut(); + let proto_chunk = self.get_proto_chunk(); let total_sections = dimension.height as usize / 16; let mut sections = ChunkSections::new( @@ -853,7 +861,7 @@ impl Chunk { struct Cache { x: i32, - y: i32, + z: i32, size: i32, pub chunks: Vec, } @@ -903,6 +911,33 @@ impl BlockAccessor 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 { let mid = ((self.size * self.size) >> 1) as usize; self.chunks[mid].get_proto_chunk_mut() @@ -941,7 +976,7 @@ impl GenerationCache for Cache { fn get_block_state(&self, pos: &Vector3) -> RawBlockState { 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 >= 0 && dz >= 0); if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { @@ -949,7 +984,7 @@ impl GenerationCache for Cache { log::error!( "illegal get_block_state {pos:?} cache pos ({}, {}) size {}", self.x, - self.y, + self.z, self.size ); return RawBlockState::AIR; @@ -969,7 +1004,7 @@ impl GenerationCache for Cache { } fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState) { 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 >= 0 && dz >= 0); if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) { @@ -977,7 +1012,7 @@ impl GenerationCache for Cache { log::error!( "illegal set_block_state {pos:?} cache pos ({}, {}) size {}", self.x, - self.y, + self.z, self.size ); return; @@ -1013,7 +1048,7 @@ impl GenerationCache for Cache { fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 { 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 >= 0 && dy >= 0); 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 { 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 >= 0 && dy >= 0); 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 { 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 >= 0 && dy >= 0); 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 { 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 >= 0 && dy >= 0); 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 { 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 >= 0 && dy >= 0); match &self.chunks[(dx * self.size + dy) as usize] { @@ -1109,10 +1144,10 @@ impl GenerationCache for Cache { } impl Cache { - fn new(x: i32, y: i32, size: i32) -> Cache { + fn new(x: i32, z: i32, size: i32) -> Cache { Cache { x, - y, + z, size, chunks: Vec::with_capacity((size * size) as usize), } @@ -1133,10 +1168,8 @@ impl Cache { Empty => panic!("empty stage"), StagedChunkEnum::StructureStart => self.chunks[mid] .get_proto_chunk_mut() - .set_structure_starts(random_config), - StagedChunkEnum::StructureReferences => self.chunks[mid] - .get_proto_chunk_mut() - .set_structure_references(), + .set_structure_starts(random_config, settings), + StagedChunkEnum::StructureReferences => ProtoChunk::set_structure_references(self), Biomes => self.chunks[mid] .get_proto_chunk_mut() .step_to_biomes(dimension, noise_router), @@ -1155,8 +1188,9 @@ impl Cache { ProtoChunk::generate_features_and_structure(self, block_registry, random_config) } Full => { - debug_assert_eq!(self.chunks[mid].get_proto_chunk_mut().stage, Features); - self.chunks[mid].get_proto_chunk_mut().stage = Full; + let chunk = self.chunks[mid].get_proto_chunk_mut(); + debug_assert_eq!(chunk.stage, Features); + chunk.stage = Full; self.chunks[mid].upgrade_to_level_chunk(&dimension); } StagedChunkEnum::None => {} @@ -1532,9 +1566,9 @@ impl GenerationSchedule { let dependency = stage.get_direct_dependencies(); let radius = stage.get_direct_radius(); for dx in -radius..=radius { - for dy in -radius..=radius { - let new_pos = pos.add_raw(dx, dy); - let req_stage = dependency[dx.abs().max(dy.abs()) as usize]; + for dz in -radius..=radius { + let new_pos = pos.add_raw(dx, dz); + let req_stage = dependency[dx.abs().max(dz.abs()) as usize]; if new_pos == pos { // TODO holder.occupied_by = self @@ -1883,7 +1917,7 @@ impl GenerationSchedule { let mut dx = 0; let mut dy = 0; 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 { Chunk::Level(chunk) => { let mut holder = self.chunk_map.remove(&new_pos).unwrap(); diff --git a/pumpkin-world/src/generation/feature/features/ore.rs b/pumpkin-world/src/generation/feature/features/ore.rs index 95df1d9ef..1f997acb4 100644 --- a/pumpkin-world/src/generation/feature/features/ore.rs +++ b/pumpkin-world/src/generation/feature/features/ore.rs @@ -1,5 +1,4 @@ use core::f32; -use std::collections::HashSet; use pumpkin_data::{BlockDirection, BlockState}; use pumpkin_util::{ @@ -84,7 +83,8 @@ impl OreFeature { vertical_size: i32, ) -> bool { 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 j = self.size; 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 { continue; } + let base_idx = (t_val - x_bound) + (v_val - y_bound) * horizontal_size; + for aa_val in p_bound..=s_bound { 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 { @@ -160,14 +162,13 @@ impl OreFeature { continue; } - let ac = (t_val - x_bound) - + (v_val - y_bound) * horizontal_size - + (aa_val - z_bound) * horizontal_size * vertical_size; + let ac = (base_idx + (aa_val - z_bound) * horizontal_size * vertical_size) + as usize; - if bit_set.contains(&ac) { + if bit_set[ac] { continue; } - bit_set.insert(ac); + bit_set[ac] = true; mutable_pos.0.x = t_val; mutable_pos.0.y = v_val; @@ -177,12 +178,13 @@ impl OreFeature { // continue; // } - let ad = t_val; - let ae = v_val; - let af = aa_val; + // let ad = t_val; + // let ae = v_val; + // let af = aa_val; + // TODO: using a section would be faster - let block_state = - GenerationCache::get_block_state(chunk, &Vector3::new(ad, ae, af)); + let pos_vec = Vector3::new(t_val, v_val, aa_val); + let block_state = GenerationCache::get_block_state(chunk, &pos_vec); for target in &self.targets { if self.should_place( @@ -192,10 +194,7 @@ impl OreFeature { target, &mut mutable_pos, ) { - chunk.set_block_state( - &Vector3::new(ad, ae, af), - target.state.get_state(), - ); + chunk.set_block_state(&pos_vec, target.state.get_state()); placed_blocks_count += 1; break; // Equivalent to 'continue block11;' } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 9d1aa9592..e68bf9a0f 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::pin::Pin; use pumpkin_data::dimension::Dimension; @@ -7,7 +7,7 @@ use pumpkin_data::tag; use pumpkin_data::{ 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::{ HeightMap, 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::structure::placement::StructurePlacementCalculator; 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::{ BlockStateId, ProtoNoiseRouters, biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier}, @@ -49,6 +49,11 @@ use crate::{ pub trait GenerationCache: HeightLimitView + BlockAccessor { 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) -> RawBlockState; fn get_fluid_and_fluid_state(&self, position: &Vector3) -> (Fluid, FluidState); fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState); @@ -134,8 +139,7 @@ pub struct ProtoChunk { flat_ocean_floor_height_map: Box<[i16]>, pub flat_motion_blocking_height_map: Box<[i16]>, pub flat_motion_blocking_no_leaves_height_map: Box<[i16]>, - // may want to use chunk status - structure_starts: HashMap, + structure_starts: HashMap, // Height of the chunk for indexing height: u16, @@ -463,7 +467,8 @@ impl ProtoChunk { let blocks_movement = blocks_movement(block_state, block); if blocks_movement { 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); if !block.has_tag(&tag::Block::MINECRAFT_LEAVES) { { @@ -634,8 +639,6 @@ impl ProtoChunk { dimension, ) }; - //dbg!("Populating biome: {:?} -> {:?}", biome_pos, biome); - let index = self.local_biome_pos_to_biome_index( x, start_biome_y + y - biome_coords::from_block(min_y as i32), @@ -906,96 +909,221 @@ impl ProtoChunk { block_registry: &dyn BlockRegistryExt, random_config: &GlobalRandomConfig, ) { - let chunk = cache.get_center_chunk_mut(); - debug_assert_eq!(chunk.stage, StagedChunkEnum::Surface); - let min_y = chunk.bottom_y(); - let height = chunk.height(); + let (center_x, center_z, min_y, height, biomes_in_chunk) = { + let chunk = cache.get_center_chunk(); + let mut biomes = HashSet::new(); + // bad + for biome in chunk.flat_biome_map.clone() { + biomes.insert(biome); + } - let bottom_section = section_coords::block_to_section(min_y) as i32; - let block_pos = BlockPos(Vector3::new( - section_coords::section_to_block(chunk.x), - bottom_section, - section_coords::section_to_block(chunk.z), - )); + (chunk.x, chunk.z, chunk.bottom_y(), chunk.height(), biomes) + }; + 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 = Xoroshiro::get_population_seed(random_config.seed, block_pos.0.x, block_pos.0.z); - let _chunk_box = chunk.get_block_box_for_chunk(); - for (_structure_config, instance) in chunk.structure_starts.clone() { - dbg!("generating"); - match instance { - 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); + const MAX_STEPS: usize = 11; + + for step in 0..MAX_STEPS { + let decorator_seed = get_decorator_seed(population_seed, 0, step as u64); 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) { - for (_set_name, set) in STRUCTURE_SETS.iter() { - let calculator = StructurePlacementCalculator { - seed: random_config.seed as i64, - }; + let mut tasks = Vec::new(); - 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( - random_config.seed as i64, - self.x, - self.z, - self, - ); + let center_chunk = cache.get_center_chunk(); + for (id, instance) in ¢er_chunk.structure_starts { + // Check if this structure belongs to the current step + if let Some(s) = STRUCTURES.get(id) + && s.step.ordinal() != step + { + continue; + } - if let Some(pos) = position - && let Some(structure_config) = STRUCTURES.get(&structure_entry.structure) - { - self.structure_starts.insert( - structure_config.clone(), - StructureInstance::Start(pos, structure_entry.structure.clone()), + match instance { + StructureInstance::Start(pos) => { + tasks.push(pos.collector.clone()); + } + StructureInstance::Reference(origin_pos) => { + 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; } - pub fn set_structure_references(&mut self) { - self.stage = StagedChunkEnum::StructureReferences; + fn try_set_structure_start( + &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 { - let x = start_block_x(self.x); - let z = start_block_z(self.z); - let bottom = self.bottom_y() as i32 + 1; - let top = self.top_y() as i32; - BlockBox::new(x, bottom, z, x + 15, top, z + 15) + pub fn set_structure_references(cache: &mut T) { + let (center_x, center_z) = { + let chunk = cache.get_center_chunk(); + (chunk.x, chunk.z) + }; + + 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 { diff --git a/pumpkin-world/src/generation/structure/mod.rs b/pumpkin-world/src/generation/structure/mod.rs index a6bd199ab..6615a24d2 100644 --- a/pumpkin-world/src/generation/structure/mod.rs +++ b/pumpkin-world/src/generation/structure/mod.rs @@ -11,14 +11,17 @@ use crate::{ structure::{ placement::StructurePlacement, structures::{ - StructureGenerator, StructurePosition, buried_treasure::BuriedTreasureGenerator, - nether_fortress::NetherFortressGenerator, swamp_hut::SwampHutGenerator, + StructureGenerator, StructureGeneratorContext, StructurePosition, + buried_treasure::BuriedTreasureGenerator, create_chunk_random, + stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator, }, }, }, }; +pub mod piece; pub mod placement; +pub mod shiftable_piece; pub mod structures; #[derive(Deserialize)] @@ -27,49 +30,49 @@ pub struct StructureSet { pub structures: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] pub struct WeightedEntry { pub structure: StructureKeys, pub weight: u32, } -#[derive(Deserialize, Clone)] -pub enum StructureType { - #[serde(rename = "minecraft:buried_treasure")] - BuriedTreasure(BuriedTreasureGenerator), - #[serde(rename = "minecraft:desert_pyramid")] - DesertPyramid, - #[serde(rename = "minecraft:end_city")] - EndCity, - #[serde(rename = "minecraft:fortress")] - NetherFortress(NetherFortressGenerator), - #[serde(rename = "minecraft:igloo")] - Igloo, - #[serde(rename = "minecraft:jigsaw")] - Jigsaw, - #[serde(rename = "minecraft:jungle_temple")] - JungleTemple, - #[serde(rename = "minecraft:mineshaft")] - Mineshaft, - #[serde(rename = "minecraft:nether_fossil")] - NetherFossil, - #[serde(rename = "minecraft:ocean_monument")] - OceanMonument, - #[serde(rename = "minecraft:ocean_ruin")] - OceanRuin, - #[serde(rename = "minecraft:ruined_portal")] - RuinedPortal, - #[serde(rename = "minecraft:shipwreck")] - Shipwreck, - #[serde(rename = "minecraft:stronghold")] - Stronghold, - #[serde(rename = "minecraft:swamp_hut")] - SwampHut(SwampHutGenerator), - #[serde(rename = "minecraft:woodland_mansion")] - WoodlandMansion, -} +// #[derive(Deserialize)] +// pub enum StructureType { +// #[serde(rename = "minecraft:buried_treasure")] +// BuriedTreasure(BuriedTreasureGenerator), +// #[serde(rename = "minecraft:desert_pyramid")] +// DesertPyramid, +// #[serde(rename = "minecraft:end_city")] +// EndCity, +// #[serde(rename = "minecraft:fortress")] +// NetherFortress(NetherFortressGenerator), +// #[serde(rename = "minecraft:igloo")] +// Igloo, +// #[serde(rename = "minecraft:jigsaw")] +// Jigsaw, +// #[serde(rename = "minecraft:jungle_temple")] +// JungleTemple, +// #[serde(rename = "minecraft:mineshaft")] +// Mineshaft, +// #[serde(rename = "minecraft:nether_fossil")] +// NetherFossil, +// #[serde(rename = "minecraft:ocean_monument")] +// OceanMonument, +// #[serde(rename = "minecraft:ocean_ruin")] +// OceanRuin, +// #[serde(rename = "minecraft:ruined_portal")] +// RuinedPortal, +// #[serde(rename = "minecraft:shipwreck")] +// Shipwreck, +// #[serde(rename = "minecraft:stronghold")] +// Stronghold, +// #[serde(rename = "minecraft:swamp_hut")] +// SwampHut(SwampHutGenerator), +// #[serde(rename = "minecraft:woodland_mansion")] +// WoodlandMansion, +// } -#[derive(Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)] pub enum StructureKeys { #[serde(rename = "minecraft:pillager_outpost")] PillagerOutpost, @@ -147,44 +150,49 @@ pub struct EmptyStruct {} impl StructureKeys { pub fn try_generate( &self, + structure: &Structure, seed: i64, - chunk_x: i32, - chunk_z: i32, - chunk_ref_for_biomes: &ProtoChunk, + chunk: &ProtoChunk, + sea_level: i32, ) -> Option { + 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 { - StructureKeys::BuriedTreasure => BuriedTreasureGenerator::get_structure_position( - &BuriedTreasureGenerator, - seed, - chunk_x, - chunk_z, - ), - StructureKeys::Fortress => NetherFortressGenerator::get_structure_position( - &NetherFortressGenerator, - seed, - chunk_x, - chunk_z, - ), - StructureKeys::SwampHut => SwampHutGenerator::get_structure_position( - &SwampHutGenerator, - seed, - chunk_x, - chunk_z, - ), + StructureKeys::BuriedTreasure => { + BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context) + } + // StructureKeys::Fortress => { + // NetherFortressGenerator::get_structure_position(&NetherFortressGenerator, context) + // } + StructureKeys::SwampHut => { + SwampHutGenerator::get_structure_position(&SwampHutGenerator, context) + } + StructureKeys::Stronghold => { + StrongholdGenerator::get_structure_position(&StrongholdGenerator, context) + } + // StructureKeys::DesertPyramid => DesertTempleGenerator::get_structure_position( + // &DesertTempleGenerator, + // context + // ), // TODO _ => None, }; - if let Some(pos) = structure_pos - && let Some(structure_data) = STRUCTURES.get(self) - { + if let Some(pos) = structure_pos { // 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.y), 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 if biomes.contains(¤t_biome.registry_id) { 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)] 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> = LazyLock::new( diff --git a/pumpkin-world/src/generation/structure/piece.rs b/pumpkin-world/src/generation/structure/piece.rs new file mode 100644 index 000000000..c4424a94e --- /dev/null +++ b/pumpkin-world/src/generation/structure/piece.rs @@ -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, +} diff --git a/pumpkin-world/src/generation/structure/placement.rs b/pumpkin-world/src/generation/structure/placement.rs index ddd196134..b68874bf3 100644 --- a/pumpkin-world/src/generation/structure/placement.rs +++ b/pumpkin-world/src/generation/structure/placement.rs @@ -19,12 +19,12 @@ pub struct StructurePlacement { impl StructurePlacement { pub fn should_generate( &self, - calculator: StructurePlacementCalculator, + calculator: &StructurePlacementCalculator, chunk_x: i32, chunk_z: i32, ) -> bool { 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) // TODO: add exclusion_zone, only used for pillager_outposts } @@ -100,7 +100,7 @@ pub enum StructurePlacementType { #[serde(rename = "minecraft:random_spread")] RandomSpread(RandomSpreadStructurePlacement), #[serde(rename = "minecraft:concentric_rings")] - ConcentricRings, + ConcentricRings(ConcentricRingsStructurePlacement), } impl StructurePlacementType { @@ -115,7 +115,10 @@ impl StructurePlacementType { StructurePlacementType::RandomSpread(placement) => { 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, } +#[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)] #[serde(rename_all = "lowercase")] pub enum SpreadType { @@ -174,6 +216,12 @@ pub struct StructurePlacementCalculator { pub seed: i64, } +impl StructurePlacementCalculator { + pub fn new(seed: i64) -> Self { + Self { seed } + } +} + #[cfg(test)] mod tests { use pumpkin_util::random::{ diff --git a/pumpkin-world/src/generation/structure/shiftable_piece.rs b/pumpkin-world/src/generation/structure/shiftable_piece.rs index e69de29bb..cabcd1dda 100644 --- a/pumpkin-world/src/generation/structure/shiftable_piece.rs +++ b/pumpkin-world/src/generation/structure/shiftable_piece.rs @@ -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 + } +} diff --git a/pumpkin-world/src/generation/structure/structures/buried_treasure.rs b/pumpkin-world/src/generation/structure/structures/buried_treasure.rs index 8a1379d1a..75fc7b34e 100644 --- a/pumpkin-world/src/generation/structure/structures/buried_treasure.rs +++ b/pumpkin-world/src/generation/structure/structures/buried_treasure.rs @@ -1,7 +1,10 @@ +use std::sync::Arc; + use pumpkin_data::{Block, BlockDirection}; use pumpkin_util::{ HeightMap, math::{block_box::BlockBox, position::BlockPos}, + random::RandomGenerator, }; use serde::Deserialize; @@ -9,59 +12,61 @@ use crate::{ ProtoChunk, generation::{ positions::chunk_pos::{get_center_x, get_center_z}, - structure::structures::{ - StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, + structure::{ + piece::StructurePieceType, + structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructurePiecesCollector, StructurePosition, + }, }, }, }; -#[derive(Deserialize, Clone)] +#[derive(Deserialize)] pub struct BuriedTreasureGenerator; impl StructureGenerator for BuriedTreasureGenerator { fn get_structure_position( &self, - _seed: i64, - chunk_x: i32, - chunk_z: i32, + context: StructureGeneratorContext, ) -> Option { - let x = get_center_x(chunk_x); - let z = get_center_z(chunk_z); + let x = get_center_x(context.chunk_x); + let z = get_center_z(context.chunk_z); let bounding_box = BlockBox::new(x, -64, z, x, 320, z); let mut collector = StructurePiecesCollector::default(); collector.add_piece(Box::new(BuriedTreasurePiece { - target_x: x, - target_z: z, - bounding_box, + piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0), })); Some(StructurePosition { start_pos: BlockPos::new(x, 90, z), - collector: collector.into(), + collector: Arc::new(collector.into()), }) } } -/// The Logic for actually placing the chest -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct BuriedTreasurePiece { - target_x: i32, - target_z: i32, - bounding_box: BlockBox, + piece: StructurePiece, } -impl StructurePiece for BuriedTreasurePiece { - fn bounding_box(&self) -> &BlockBox { - &self.bounding_box +impl StructurePieceBase for BuriedTreasurePiece { + fn clone_box(&self) -> Box { + Box::new((*self).clone()) } - fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { - let y = chunk.get_top_y(&HeightMap::OceanFloorWg, self.target_x, self.target_z); + fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + 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; for _ in (bottom_y..=y).rev() { @@ -111,6 +116,14 @@ impl StructurePiece for BuriedTreasurePiece { 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 { diff --git a/pumpkin-world/src/generation/structure/structures/mod.rs b/pumpkin-world/src/generation/structure/structures/mod.rs index 4a8892227..f3daf8c5e 100644 --- a/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/mod.rs @@ -1,175 +1,542 @@ -use std::sync::Arc; +use std::sync::{Arc, Mutex}; +use pumpkin_data::Block; 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::{ 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 nether_fortress; +pub mod stronghold; pub mod swamp_hut; -#[expect(clippy::too_many_arguments)] -pub fn fill_with_outline( - 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); - } +pub trait BlockRandomizer { + fn get_block(&self, rng: &mut RandomGenerator, is_border: bool) -> &BlockState; } /// Represents a single component of a structure (e.g., a room, a bridge). -pub trait StructurePiece: Send + Sync { - /// The bounding box of this specific piece. - fn bounding_box(&self) -> &BlockBox; +pub trait StructurePieceBase: Send + Sync { + fn get_structure_piece(&self) -> &StructurePiece; + + 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; /// 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>, + ) { + } +} + +#[derive(Clone)] +pub struct StructurePiece { + pub r#type: StructurePieceType, + pub bounding_box: BlockBox, + pub facing: Option, + 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) { + self.facing = facing; + } + + fn offset_pos(&self, x: i32, y: i32, z: i32) -> Vector3 { + 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(¤t_pos) { + chunk.set_block_state(¤t_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 { + 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. #[derive(Default)] pub struct StructurePiecesCollector { - pub pieces: Vec>, + pub pieces: Vec>, + cached_box: Option, } impl StructurePiecesCollector { pub fn new() -> Self { - Self { pieces: Vec::new() } + Self { + pieces: Vec::new(), + cached_box: None, + } } - pub fn add_piece(&mut self, piece: Box) { + pub fn add_piece(&mut self, piece: Box) { self.pieces.push(piece); } + #[expect(clippy::borrowed_box)] + pub fn get_intersecting( + &self, + box_to_check: &BlockBox, + ) -> Option<&Box> { + 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. - pub fn generate_in_chunk(&self, chunk: &mut ProtoChunk, seed: i64) { - let chunk_x = chunk.x; - let chunk_z = chunk.z; + pub fn generate_in_chunk( + &mut self, + 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( - chunk_x * 16, - chunk.bottom_y() as i32, - chunk_z * 16, - (chunk_x * 16) + 15, - chunk.top_y() as i32, - (chunk_z * 16) + 15, + chunk_x, + chunk.bottom_y() as i32 + 1, + chunk_z, + chunk_x + 15, + chunk.bottom_y() as i32 + chunk.top_y() as i32 - 1, + chunk_z + 15, ); - for piece in &self.pieces { + for piece in self.pieces.iter_mut() { 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)] pub struct StructurePosition { pub start_pos: BlockPos, - pub collector: Arc, + pub collector: Arc>, +} + +impl StructurePosition { + pub fn get_bounding_box(&self) -> BlockBox { + self.collector.lock().unwrap().get_bounding_box() + } } pub trait StructureGenerator { fn get_structure_position( &self, - seed: i64, - chunk_x: i32, - chunk_z: i32, + context: StructureGeneratorContext, ) -> Option; } +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)] pub enum StructureInstance { /// This chunk is the "owner" of the structure. - Start(StructurePosition, StructureKeys), + Start(StructurePosition), /// This chunk just contains a piece of a structure starting elsewhere. /// Stores the BlockPos of the 'Start' so you can look it up. Reference(BlockPos), diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress.rs index 044abcfdd..745f5e5e6 100644 --- a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress.rs @@ -1,191 +1,188 @@ -use pumpkin_data::Block; -use pumpkin_util::math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}; -use serde::Deserialize; +// use std::sync::Arc; -use crate::{ - ProtoChunk, - generation::{ - positions::chunk_pos, - structure::structures::{ - StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, fill, - fill_downwards, fill_with_outline, - }, - }, -}; +// use pumpkin_data::Block; +// use pumpkin_util::{ +// math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, +// random::RandomGenerator, +// }; +// use serde::Deserialize; -#[derive(Deserialize, Clone, Debug)] -pub struct NetherFortressGenerator; +// use crate::{ +// ProtoChunk, +// generation::{ +// positions::chunk_pos, +// structure::structures::{ +// StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, +// StructurePiecesCollector, StructurePosition, +// }, +// }, +// }; -impl StructureGenerator for NetherFortressGenerator { - fn get_structure_position( - &self, - _seed: i64, - chunk_x: i32, - chunk_z: i32, - ) -> Option { - let start_x = chunk_pos::start_block_x(chunk_x); - let start_z = chunk_pos::start_block_z(chunk_z); - let start_y = 64; +// #[derive(Deserialize, Clone, Debug)] +// pub struct NetherFortressGenerator; - let mut collector = StructurePiecesCollector::default(); +// impl StructureGenerator for NetherFortressGenerator { +// fn get_structure_position( +// &self, +// context: StructureGeneratorContext, +// ) -> Option { +// 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 box_min = BlockPos::new(start_x, start_y, start_z); - let box_max = BlockPos::new(start_x + 8, start_y + 10, start_z + 8); +// let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(NetherFortressPiece { - bounding_box: BlockBox::new( - box_min.0.x, - box_min.0.y, - box_min.0.z, - box_max.0.x, - box_max.0.y, - box_max.0.z, - ), - piece_type: NetherFortressPieceType::BridgePlatform, - })); +// // Add a Bridge Platform piece +// 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); - Some(StructurePosition { - start_pos: box_min, - collector: collector.into(), - }) - } -} +// todo!(); -#[derive(Debug, Clone)] -pub enum NetherFortressPieceType { - BridgePlatform, - SmallCorridor, -} +// Some(StructurePosition { +// start_pos: box_min, +// collector: Arc::new(collector.into()), +// }) +// } +// } -#[derive(Debug, Clone)] -pub struct NetherFortressPiece { - pub bounding_box: BlockBox, - pub piece_type: NetherFortressPieceType, -} +// #[derive(Debug, Clone)] +// pub enum NetherFortressPieceType { +// BridgePlatform, +// SmallCorridor, +// } -impl StructurePiece for NetherFortressPiece { - fn bounding_box(&self) -> &BlockBox { - &self.bounding_box - } +// struct NetherFortressPiece { +// piece: StructurePiece, +// piece_type: NetherFortressPieceType, +// } - fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { - let origin_x = self.bounding_box.min.x; - let origin_y = self.bounding_box.min.y; - let origin_z = self.bounding_box.min.z; +// impl StructurePieceBase for NetherFortressPiece { +// fn place(&mut self, chunk: &mut ProtoChunk, _seed: i64) { +// let bounding_box = self.piece.bounding_box; +// 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| { - fill( - origin_x + min_x, - origin_y + min_y, - origin_z + min_z, - origin_x + max_x, - origin_y + max_y, - origin_z + max_z, - block, - c, - ) - }; +// let fill_rel = |min_x, min_y, min_z, max_x, max_y, max_z, block, c: &mut ProtoChunk| { +// fill( +// origin_x + min_x, +// origin_y + min_y, +// origin_z + min_z, +// origin_x + max_x, +// origin_y + max_y, +// origin_z + max_z, +// block, +// c, +// ) +// }; - let fill_outline_rel = - |min_x, min_y, min_z, max_x, max_y, max_z, outline, inside, c: &mut ProtoChunk| { - fill_with_outline( - origin_x + min_x, - origin_y + min_y, - origin_z + min_z, - origin_x + max_x, - origin_y + max_y, - origin_z + max_z, - outline, - inside, - c, - ) - }; +// let fill_outline_rel = +// |min_x, min_y, min_z, max_x, max_y, max_z, outline, inside, c: &mut ProtoChunk| { +// fill_with_outline( +// origin_x + min_x, +// origin_y + min_y, +// origin_z + min_z, +// origin_x + max_x, +// origin_y + max_y, +// origin_z + max_z, +// outline, +// inside, +// c, +// ) +// }; - match self.piece_type { - NetherFortressPieceType::BridgePlatform => { - 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, 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, 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(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(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(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); +// match self.piece_type { +// NetherFortressPieceType::BridgePlatform => { +// 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, 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, 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(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(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(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); - 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(5, 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(1, 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(6, 6, 3), fence); - 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(0, 6, 4, 0, 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(6, 6, 8), fence); +// chunk.set_block_state(&offset(0, 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); - 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(1, 7, 8), fence); +// 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(2, 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(2, 8, 8), fence); +// chunk.set_block_state(&offset(3, 8, 8), fence); +// chunk.set_block_state(&offset(4, 8, 8), fence); - // Blaze Spawner - chunk.set_block_state(&offset(3, 5, 5), Block::SPAWNER.default_state); +// // Blaze Spawner +// chunk.set_block_state(&offset(3, 5, 5), Block::SPAWNER.default_state); - // Pillars down - for x in 0..6 { - for z in 0..6 { - fill_downwards( - origin_x + x, - origin_y - 1, - origin_z + z, - Block::NETHER_BRICKS.default_state, - chunk, - ); - } - } - } - NetherFortressPieceType::SmallCorridor => { - 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); +// // Pillars down +// for x in 0..6 { +// for z in 0..6 { +// fill_downwards( +// origin_x + x, +// origin_y - 1, +// origin_z + z, +// Block::NETHER_BRICKS.default_state, +// chunk, +// ); +// } +// } +// } +// NetherFortressPieceType::SmallCorridor => { +// 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); - 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(4, 2, 0, 4, 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(0, 3, 1, 0, 4, 1, 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, 3, 4, 4, 3, fence, chunk); +// fill_rel(0, 3, 1, 0, 4, 1, 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, 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 j in 0..=4 { - fill_downwards( - origin_x + i, - origin_y - 1, - origin_z + j, - Block::NETHER_BRICKS.default_state, - chunk, - ); - } - } - } - } - } -} +// for i in 0..=4 { +// for j in 0..=4 { +// fill_downwards( +// origin_x + i, +// origin_y - 1, +// origin_z + j, +// Block::NETHER_BRICKS.default_state, +// chunk, +// ); +// } +// } +// } +// } +// } + +// fn get_structure_piece(&self) -> &super::StructurePiece { +// &self.piece +// } + +// fn get_structure_piece_mut(&mut self) -> &mut super::StructurePiece { +// &mut self.piece +// } +// } diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs b/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs new file mode 100644 index 000000000..8cd98c58a --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs @@ -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> { + 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>, + ) { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs b/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs new file mode 100644 index 000000000..b7037a9b5 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs @@ -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> { + 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>, + ) { + 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 { + 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 { + // 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 { + 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); + } + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs b/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs new file mode 100644 index 000000000..136cfd5e5 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs @@ -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> { + 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>, + ) { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs b/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs new file mode 100644 index 000000000..2d6abc009 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs @@ -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> { + 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>, + ) { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/library.rs b/pumpkin-world/src/generation/structure/structures/stronghold/library.rs new file mode 100644 index 000000000..fbfd63ce3 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/library.rs @@ -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> { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs b/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs new file mode 100644 index 000000000..e40fa2b30 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs @@ -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 { + 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 = Box::new(start_piece.clone()); + + let mut pieces_to_process: Vec> = + 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>, + piece: Option, + ) { + 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>, + ) { + 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>, + ) { + 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, + ) -> Option> { + // 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> { + 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> { + 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) + } + } + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs b/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs new file mode 100644 index 000000000..da46a34ee --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs @@ -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> { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs b/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs new file mode 100644 index 000000000..03e987cd9 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs @@ -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> { + // 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>, + ) { + // 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs b/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs new file mode 100644 index 000000000..c12d43c0d --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs @@ -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> { + // 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>, + ) { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs new file mode 100644 index 000000000..961c5d3db --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs @@ -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, + pub portal_room_exists: bool, + pub pieces: Vec>, +} +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> { + 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 { + 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>, + ) { + 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); + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs b/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs new file mode 100644 index 000000000..04c222c83 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs @@ -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> { + 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>, + ) { + // 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs b/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs new file mode 100644 index 000000000..ec6f8327d --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs @@ -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> { + 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>, + ) { + 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 { + Box::new((*self).clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/swamp_hut.rs b/pumpkin-world/src/generation/structure/structures/swamp_hut.rs index 88888d4e3..68e9e67d7 100644 --- a/pumpkin-world/src/generation/structure/structures/swamp_hut.rs +++ b/pumpkin-world/src/generation/structure/structures/swamp_hut.rs @@ -1,204 +1,240 @@ +use std::sync::Arc; + use pumpkin_data::Block; -use pumpkin_util::{ - HeightMap, - math::{ - block_box::BlockBox, - position::BlockPos, - vector3::{Axis, Vector3}, - }, -}; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use serde::Deserialize; use crate::{ ProtoChunk, generation::{ positions::chunk_pos::{get_center_x, get_center_z}, - structure::structures::{ - StructureGenerator, StructurePiece, StructurePiecesCollector, StructurePosition, fill, - fill_downwards, + structure::{ + piece::StructurePieceType, + shiftable_piece::ShiftableStructurePiece, + structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructurePiecesCollector, StructurePosition, + }, }, }, }; -#[derive(Deserialize, Clone)] +#[derive(Deserialize)] pub struct SwampHutGenerator; impl StructureGenerator for SwampHutGenerator { fn get_structure_position( &self, - _seed: i64, - chunk_x: i32, - chunk_z: i32, + mut context: StructureGeneratorContext, ) -> Option { - let x = get_center_x(chunk_x); - let z = get_center_z(chunk_z); - - // TODO: random axis - let bounding_box = BlockBox::create_box(x, 64, z, Axis::X, 7, 7, 9); + let x = get_center_x(context.chunk_x); + let z = get_center_z(context.chunk_z); 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 { start_pos: BlockPos::new(x, 64, z), - collector: collector.into(), + collector: Arc::new(collector.into()), }) } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct SwampHutPiece { - bounding_box: BlockBox, + shiftable_structure_piece: ShiftableStructurePiece, } -impl StructurePiece for SwampHutPiece { - fn bounding_box(&self) -> &BlockBox { - &self.bounding_box +impl StructurePieceBase for SwampHutPiece { + fn clone_box(&self) -> Box { + Box::new(self.clone()) } - fn place(&self, chunk: &mut ProtoChunk, _seed: i64) { - let min = self.bounding_box.min; - let origin_x = min.x; - let origin_z = min.z; - - // Vanilla uses adjustToAverageHeight. - 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); + fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + if !self + .shiftable_structure_piece + .adjust_to_average_height(chunk) + { + return; } - // Placed at Y=2 (feet level) or Y=3 (eye level) - let y_floor = origin_y + 2; - let y_eye = origin_y + 3; + let box_limit = self.shiftable_structure_piece.piece.bounding_box; + let p = &self.shiftable_structure_piece.piece; - chunk.set_block_state( - &Vector3::new(origin_x + 3, y_floor, origin_z + 6), - Block::CRAFTING_TABLE.default_state, + 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; + + p.fill_with_outline( + chunk, + &box_limit, + false, + 1, + 1, + 1, + 5, + 1, + 7, + spruce_planks, + spruce_planks, ); - chunk.set_block_state( - &Vector3::new(origin_x + 4, y_floor, origin_z + 6), - Block::CAULDRON.default_state, + p.fill_with_outline( + chunk, + &box_limit, + false, + 1, + 4, + 2, + 5, + 4, + 7, + spruce_planks, + spruce_planks, ); - chunk.set_block_state( - &Vector3::new(origin_x + 1, y_eye, origin_z + 5), + p.fill_with_outline( + 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, + 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 - chunk.set_block_state(&Vector3::new(origin_x + 2, y_eye, origin_z + 2), oak_fence); // Above the door - chunk.set_block_state(&Vector3::new(origin_x + 3, y_eye, origin_z + 7), oak_fence); // Back window - chunk.set_block_state( - &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 + // let stairs_n = Block::SPRUCE_STAIRS.default_state.with("facing", "north"); + // let stairs_e = Block::SPRUCE_STAIRS.default_state.with("facing", "east"); + // let stairs_w = Block::SPRUCE_STAIRS.default_state.with("facing", "west"); + // let stairs_s = Block::SPRUCE_STAIRS.default_state.with("facing", "south"); - // Clear window air (Java: addBlock AIR at 1, 3, 4 and 5, 3, 4/5) - chunk.set_block_state(&Vector3::new(origin_x + 1, y_eye, origin_z + 4), air); - chunk.set_block_state(&Vector3::new(origin_x + 5, y_eye, origin_z + 4), air); - 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, 1, 6, 4, 1, &stairs_n, &stairs_n); + // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, &stairs_e, &stairs_e); + // p.fill_with_outline(chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, &stairs_w, &stairs_w); + // 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 } } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index a56c4b805..a283b180c 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -302,18 +302,22 @@ impl Level { self.shut_down_chunk_system.store(true, Ordering::Relaxed); self.level_channel.notify(); - { + let handles: Vec<_> = { let mut lock = self.thread_tracker.lock().unwrap(); - log::info!("Wait {} jobs stop", lock.len()); - while let Some(i) = lock.pop() { - log::info!( - "Waiting Thread {:?} {} stop", - i.thread().id(), - i.thread().name().unwrap_or("unknown") - ); - i.join().unwrap(); + log::info!("Shutting down {} jobs", lock.len()); + lock.drain(..).collect() + }; + + for handle in handles { + log::info!( + "Waiting for thread {:?} ({}) to stop", + 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"); diff --git a/pumpkin/src/block/blocks/note.rs b/pumpkin/src/block/blocks/note.rs index 95486c45c..855b74775 100644 --- a/pumpkin/src/block/blocks/note.rs +++ b/pumpkin/src/block/blocks/note.rs @@ -33,7 +33,7 @@ impl NoteBlock { } } 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( diff --git a/pumpkin/src/entity/ai/control/look_control.rs b/pumpkin/src/entity/ai/control/look_control.rs index eb8c41db0..7e7124970 100644 --- a/pumpkin/src/entity/ai/control/look_control.rs +++ b/pumpkin/src/entity/ai/control/look_control.rs @@ -44,8 +44,8 @@ impl LookControl { x, y, z, - mob.get_max_look_yaw_change() as f32, - mob.get_max_look_pitch_change() as f32, + mob.get_max_look_yaw_change(), + mob.get_max_look_pitch_change(), ); } @@ -105,7 +105,7 @@ impl LookControl { let navigator = mob_entity.navigator.lock().await; if !navigator.is_idle() { 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.load(), entity.body_yaw.load(), diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index c33ed73eb..19184f4ec 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -66,16 +66,16 @@ pub trait Mob: EntityBase + Send + Sync { rand::rng() } - fn get_max_look_yaw_change(&self) -> i32 { - 10 + fn get_max_look_yaw_change(&self) -> f32 { + 10.0 } - fn get_max_look_pitch_change(&self) -> i32 { - 40 + fn get_max_look_pitch_change(&self) -> f32 { + 40.0 } - fn get_max_head_rotation(&self) -> i32 { - 75 + fn get_max_head_rotation(&self) -> f32 { + 75.0 } fn get_mob_entity(&self) -> &MobEntity; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 0f7965721..f769546f7 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1027,16 +1027,15 @@ impl Player { } } - #[expect(clippy::cast_precision_loss)] pub async fn progress_motion(&self, delta_pos: Vector3) { // TODO: Swimming, gliding... if self.living_entity.entity.on_ground.load(Ordering::Relaxed) { - let delta = (delta_pos.horizontal_length() * 100.0).round() as i32; - if delta > 0 { + let delta = (delta_pos.horizontal_length() * 100.0).round() as f32; + if delta > 0.0 { 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 { - self.add_exhaustion(0.0 * delta as f32 * 0.01).await; + self.add_exhaustion(0.0 * delta * 0.01).await; } } }