From abccd3f7000aecbb1c2aa635be9e3069ecb40378 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 5 Jun 2025 17:03:52 +0200 Subject: [PATCH] add Chunk features (#685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * inital chunk features * add more things great title i know * add some features still no result, but we are getting there... * make it parse * add simple BlockPredicate * add simple random selector * new things * change stuff * base tree impl * change things * add more heightmaps * fix master merge * steps towards proper random * fix upper level block placement logic * Make it compile * fix block dir serde name * Added Tree Foliage * more Tree work * Implement would_survive check I don't say its good, but it works :D * Fixed biome placement modifier (#836) * Implement all tree foliages * Fix grass, flowers, seagrass, seapickles... * Add Ores * add Bamboo, Vine, Spring feature Also more fixes * Corals * Add Tree decoration & more Many many fixes * fix vines * fixed bamboo * fix some clippy warns * fix trees outside of chunk * make it compile again upsi * fix clippy warns * fix InsideWorldBoundsBlockPredicate * set dirt under tress trunk dirt, not grass * more trees work --------- Co-authored-by: kralverde Co-authored-by: Laurent Stéphenne --- Cargo.lock | 493 ++++++++-------- Cargo.toml | 2 +- ...features.json => configured_features.json} | 0 pumpkin-data/build/block.rs | 15 +- pumpkin-data/build/noise_parameter.rs | 11 +- pumpkin-data/src/block_direction.rs | 13 +- .../src/client/config/update_tags.rs | 12 +- pumpkin-util/src/lib.rs | 7 + pumpkin-util/src/math/int_provider.rs | 129 +++- pumpkin-util/src/math/mod.rs | 1 + pumpkin-util/src/math/pool.rs | 55 ++ pumpkin-util/src/math/position.rs | 12 + pumpkin-util/src/math/vector3.rs | 33 ++ pumpkin-util/src/random/legacy_rand.rs | 4 +- pumpkin-util/src/random/mod.rs | 24 + pumpkin-util/src/random/xoroshiro128.rs | 4 + pumpkin-world/benches/chunk_io.rs | 34 +- pumpkin-world/src/block/mod.rs | 38 +- pumpkin-world/src/block/state.rs | 23 +- pumpkin-world/src/chunk/format/anvil.rs | 43 +- pumpkin-world/src/chunk/format/linear.rs | 33 +- pumpkin-world/src/chunk/format/mod.rs | 14 +- pumpkin-world/src/chunk/mod.rs | 2 +- pumpkin-world/src/chunk/palette.rs | 14 +- pumpkin-world/src/dimension.rs | 13 +- .../src/generation/block_predicate.rs | 277 +++++++++ .../src/generation/block_state_provider.rs | 228 +++++++ .../generation/feature/configured_features.rs | 385 ++++++++++++ .../src/generation/feature/features/bamboo.rs | 98 +++ .../feature/features/basalt_columns.rs | 6 + .../feature/features/basalt_pillar.rs | 6 + .../feature/features/block_column.rs | 114 ++++ .../generation/feature/features/block_pile.rs | 6 + .../generation/feature/features/blue_ice.rs | 6 + .../feature/features/bonus_chest.rs | 6 + .../feature/features/chorus_plant.rs | 6 + .../feature/features/coral/coral_claw.rs | 78 +++ .../feature/features/coral/coral_mushroom.rs | 56 ++ .../feature/features/coral/coral_tree.rs | 61 ++ .../generation/feature/features/coral/mod.rs | 100 ++++ .../feature/features/delta_feature.rs | 6 + .../feature/features/desert_well.rs | 152 +++++ .../src/generation/feature/features/disk.rs | 6 + .../feature/features/drip_stone/cluster.rs | 6 + .../feature/features/drip_stone/large.rs | 6 + .../feature/features/drip_stone/mod.rs | 30 + .../feature/features/drip_stone/small.rs | 82 +++ .../feature/features/end_gateway.rs | 6 + .../generation/feature/features/end_island.rs | 6 + .../feature/features/end_platform.rs | 6 + .../generation/feature/features/end_spike.rs | 6 + .../feature/features/fallen_tree.rs | 27 + .../generation/feature/features/fill_layer.rs | 6 + .../feature/features/forest_rock.rs | 6 + .../src/generation/feature/features/fossil.rs | 6 + .../feature/features/freeze_top_layer.rs | 6 + .../src/generation/feature/features/geode.rs | 6 + .../feature/features/glowstone_blob.rs | 6 + .../feature/features/huge_brown_mushroom.rs | 6 + .../feature/features/huge_fungus.rs | 6 + .../feature/features/huge_red_mushroom.rs | 6 + .../generation/feature/features/ice_spike.rs | 6 + .../generation/feature/features/iceberg.rs | 6 + .../src/generation/feature/features/kelp.rs | 6 + .../src/generation/feature/features/lake.rs | 6 + .../src/generation/feature/features/mod.rs | 56 ++ .../feature/features/monster_room.rs | 6 + .../feature/features/multiface_growth.rs | 6 + .../features/nether_forest_vegetation.rs | 6 + .../features/netherrack_replace_blobs.rs | 6 + .../src/generation/feature/features/ore.rs | 252 ++++++++ .../features/random_boolean_selector.rs | 51 ++ .../feature/features/random_patch.rs | 62 ++ .../feature/features/random_selector.rs | 67 +++ .../feature/features/replace_single_block.rs | 6 + .../feature/features/root_system.rs | 6 + .../feature/features/scattered_ore.rs | 6 + .../feature/features/sculk_patch.rs | 6 + .../generation/feature/features/sea_pickle.rs | 51 ++ .../generation/feature/features/seagrass.rs | 60 ++ .../feature/features/simple_block.rs | 41 ++ .../features/simple_random_selector.rs | 46 ++ .../feature/features/spring_feature.rs | 130 ++++ .../features/tree/decorator/alter_ground.rs | 4 + .../tree/decorator/attached_to_leaves.rs | 4 + .../tree/decorator/attached_to_logs.rs | 37 ++ .../features/tree/decorator/beehive.rs | 6 + .../feature/features/tree/decorator/cocoa.rs | 4 + .../features/tree/decorator/creaking_heart.rs | 4 + .../features/tree/decorator/leave_vine.rs | 4 + .../feature/features/tree/decorator/mod.rs | 96 +++ .../features/tree/decorator/pale_moss.rs | 4 + .../tree/decorator/place_on_ground.rs | 83 +++ .../features/tree/decorator/trunk_vine.rs | 69 +++ .../feature/features/tree/foliage/acacia.rs | 85 +++ .../feature/features/tree/foliage/blob.rs | 63 ++ .../feature/features/tree/foliage/bush.rs | 63 ++ .../feature/features/tree/foliage/cherry.rs | 131 +++++ .../feature/features/tree/foliage/dark_oak.rs | 159 +++++ .../feature/features/tree/foliage/fancy.rs | 68 +++ .../feature/features/tree/foliage/jungle.rs | 70 +++ .../features/tree/foliage/mega_pine.rs | 79 +++ .../feature/features/tree/foliage/mod.rs | 365 ++++++++++++ .../feature/features/tree/foliage/pine.rs | 71 +++ .../features/tree/foliage/random_spread.rs | 46 ++ .../feature/features/tree/foliage/spruce.rs | 74 +++ .../generation/feature/features/tree/mod.rs | 153 +++++ .../feature/features/tree/trunk/bending.rs | 74 +++ .../feature/features/tree/trunk/cherry.rs | 4 + .../feature/features/tree/trunk/dark_oak.rs | 145 +++++ .../feature/features/tree/trunk/fancy.rs | 288 +++++++++ .../feature/features/tree/trunk/forking.rs | 4 + .../feature/features/tree/trunk/giant.rs | 97 +++ .../features/tree/trunk/mega_jungle.rs | 77 +++ .../feature/features/tree/trunk/mod.rs | 245 ++++++++ .../feature/features/tree/trunk/straight.rs | 39 ++ .../features/tree/trunk/upwards_branching.rs | 4 + .../feature/features/twisting_vines.rs | 6 + .../feature/features/underwater_magma.rs | 6 + .../feature/features/vegetation_patch.rs | 6 + .../src/generation/feature/features/vines.rs | 53 ++ .../feature/features/void_start_platform.rs | 6 + .../features/waterlogged_vegetation_patch.rs | 6 + .../feature/features/weeping_vines.rs | 6 + pumpkin-world/src/generation/feature/mod.rs | 7 + .../src/generation/feature/placed_features.rs | 556 ++++++++++++++++++ pumpkin-world/src/generation/feature/size.rs | 64 ++ pumpkin-world/src/generation/generator.rs | 13 - .../src/generation/height_provider.rs | 60 +- .../src/generation/implementation/mod.rs | 32 +- pumpkin-world/src/generation/mod.rs | 13 +- pumpkin-world/src/generation/proto_chunk.rs | 151 ++++- .../src/generation/rule/block_match.rs | 14 + .../src/generation/rule/block_state_match.rs | 15 + pumpkin-world/src/generation/rule/mod.rs | 44 ++ .../src/generation/rule/random_block_match.rs | 17 + .../rule/random_block_state_match.rs | 17 + .../src/generation/rule/tag_match.rs | 16 + pumpkin-world/src/generation/rule_test.rs | 41 ++ pumpkin-world/src/generation/surface/mod.rs | 2 +- pumpkin-world/src/generation/surface/rule.rs | 24 +- .../src/generation/surface/terrain.rs | 11 +- pumpkin-world/src/generation/y_offset.rs | 4 +- pumpkin-world/src/level.rs | 147 ++++- pumpkin-world/src/lib.rs | 12 + pumpkin-world/src/world.rs | 27 +- pumpkin/Cargo.toml | 10 +- pumpkin/src/block/blocks/bamboo.rs | 35 ++ pumpkin/src/block/blocks/bed.rs | 27 +- pumpkin/src/block/blocks/cactus.rs | 17 +- pumpkin/src/block/blocks/dirt_path.rs | 12 +- pumpkin/src/block/blocks/doors.rs | 12 +- pumpkin/src/block/blocks/farmland.rs | 12 +- pumpkin/src/block/blocks/fire/fire.rs | 16 +- pumpkin/src/block/blocks/fire/mod.rs | 8 +- pumpkin/src/block/blocks/fire/soul_fire.rs | 14 +- pumpkin/src/block/blocks/mod.rs | 4 +- pumpkin/src/block/blocks/plant/bush.rs | 41 ++ .../src/block/blocks/plant/dry_vegetation.rs | 43 ++ pumpkin/src/block/blocks/plant/flower.rs | 41 ++ pumpkin/src/block/blocks/plant/flowerbed.rs | 41 ++ pumpkin/src/block/blocks/plant/leaf_litter.rs | 40 ++ .../src/block/blocks/{ => plant}/lily_pad.rs | 0 pumpkin/src/block/blocks/plant/mod.rs | 10 + .../src/block/blocks/plant/mushroom_plant.rs | 50 ++ pumpkin/src/block/blocks/plant/sapling.rs | 41 ++ pumpkin/src/block/blocks/plant/short_plant.rs | 41 ++ pumpkin/src/block/blocks/plant/tall_plant.rs | 67 +++ .../blocks/redstone/rails/activator_rail.rs | 12 +- .../blocks/redstone/rails/detector_rail.rs | 12 +- .../blocks/redstone/rails/powered_rail.rs | 12 +- .../src/block/blocks/redstone/rails/rail.rs | 12 +- .../block/blocks/redstone/redstone_torch.rs | 11 +- .../block/blocks/redstone/redstone_wire.rs | 13 +- pumpkin/src/block/blocks/sugar_cane.rs | 21 +- pumpkin/src/block/blocks/tnt.rs | 2 +- pumpkin/src/block/blocks/torches.rs | 14 +- pumpkin/src/block/blocks/vine.rs | 41 ++ pumpkin/src/block/fluid/flowing.rs | 45 +- pumpkin/src/block/loot.rs | 4 +- pumpkin/src/block/mod.rs | 40 +- pumpkin/src/block/pumpkin_block.rs | 11 +- pumpkin/src/block/registry.rs | 46 +- pumpkin/src/command/commands/playsound.rs | 4 +- pumpkin/src/entity/mod.rs | 23 +- pumpkin/src/item/items/bucket.rs | 233 ++------ pumpkin/src/item/items/flint_and_steel.rs | 2 +- pumpkin/src/net/packet/play.rs | 16 +- pumpkin/src/net/proxy/velocity.rs | 4 +- pumpkin/src/net/query.rs | 2 +- pumpkin/src/plugin/loader/native.rs | 1 + pumpkin/src/server/key_store.rs | 7 +- pumpkin/src/server/mod.rs | 8 +- pumpkin/src/server/seasonal_events.rs | 4 +- pumpkin/src/world/mod.rs | 257 ++------ pumpkin/src/world/weather.rs | 10 +- 196 files changed, 8281 insertions(+), 1006 deletions(-) rename assets/{gen_features.json => configured_features.json} (100%) create mode 100644 pumpkin-util/src/math/pool.rs create mode 100644 pumpkin-world/src/generation/block_predicate.rs create mode 100644 pumpkin-world/src/generation/block_state_provider.rs create mode 100644 pumpkin-world/src/generation/feature/configured_features.rs create mode 100644 pumpkin-world/src/generation/feature/features/bamboo.rs create mode 100644 pumpkin-world/src/generation/feature/features/basalt_columns.rs create mode 100644 pumpkin-world/src/generation/feature/features/basalt_pillar.rs create mode 100644 pumpkin-world/src/generation/feature/features/block_column.rs create mode 100644 pumpkin-world/src/generation/feature/features/block_pile.rs create mode 100644 pumpkin-world/src/generation/feature/features/blue_ice.rs create mode 100644 pumpkin-world/src/generation/feature/features/bonus_chest.rs create mode 100644 pumpkin-world/src/generation/feature/features/chorus_plant.rs create mode 100644 pumpkin-world/src/generation/feature/features/coral/coral_claw.rs create mode 100644 pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs create mode 100644 pumpkin-world/src/generation/feature/features/coral/coral_tree.rs create mode 100644 pumpkin-world/src/generation/feature/features/coral/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/delta_feature.rs create mode 100644 pumpkin-world/src/generation/feature/features/desert_well.rs create mode 100644 pumpkin-world/src/generation/feature/features/disk.rs create mode 100644 pumpkin-world/src/generation/feature/features/drip_stone/cluster.rs create mode 100644 pumpkin-world/src/generation/feature/features/drip_stone/large.rs create mode 100644 pumpkin-world/src/generation/feature/features/drip_stone/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/drip_stone/small.rs create mode 100644 pumpkin-world/src/generation/feature/features/end_gateway.rs create mode 100644 pumpkin-world/src/generation/feature/features/end_island.rs create mode 100644 pumpkin-world/src/generation/feature/features/end_platform.rs create mode 100644 pumpkin-world/src/generation/feature/features/end_spike.rs create mode 100644 pumpkin-world/src/generation/feature/features/fallen_tree.rs create mode 100644 pumpkin-world/src/generation/feature/features/fill_layer.rs create mode 100644 pumpkin-world/src/generation/feature/features/forest_rock.rs create mode 100644 pumpkin-world/src/generation/feature/features/fossil.rs create mode 100644 pumpkin-world/src/generation/feature/features/freeze_top_layer.rs create mode 100644 pumpkin-world/src/generation/feature/features/geode.rs create mode 100644 pumpkin-world/src/generation/feature/features/glowstone_blob.rs create mode 100644 pumpkin-world/src/generation/feature/features/huge_brown_mushroom.rs create mode 100644 pumpkin-world/src/generation/feature/features/huge_fungus.rs create mode 100644 pumpkin-world/src/generation/feature/features/huge_red_mushroom.rs create mode 100644 pumpkin-world/src/generation/feature/features/ice_spike.rs create mode 100644 pumpkin-world/src/generation/feature/features/iceberg.rs create mode 100644 pumpkin-world/src/generation/feature/features/kelp.rs create mode 100644 pumpkin-world/src/generation/feature/features/lake.rs create mode 100644 pumpkin-world/src/generation/feature/features/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/monster_room.rs create mode 100644 pumpkin-world/src/generation/feature/features/multiface_growth.rs create mode 100644 pumpkin-world/src/generation/feature/features/nether_forest_vegetation.rs create mode 100644 pumpkin-world/src/generation/feature/features/netherrack_replace_blobs.rs create mode 100644 pumpkin-world/src/generation/feature/features/ore.rs create mode 100644 pumpkin-world/src/generation/feature/features/random_boolean_selector.rs create mode 100644 pumpkin-world/src/generation/feature/features/random_patch.rs create mode 100644 pumpkin-world/src/generation/feature/features/random_selector.rs create mode 100644 pumpkin-world/src/generation/feature/features/replace_single_block.rs create mode 100644 pumpkin-world/src/generation/feature/features/root_system.rs create mode 100644 pumpkin-world/src/generation/feature/features/scattered_ore.rs create mode 100644 pumpkin-world/src/generation/feature/features/sculk_patch.rs create mode 100644 pumpkin-world/src/generation/feature/features/sea_pickle.rs create mode 100644 pumpkin-world/src/generation/feature/features/seagrass.rs create mode 100644 pumpkin-world/src/generation/feature/features/simple_block.rs create mode 100644 pumpkin-world/src/generation/feature/features/simple_random_selector.rs create mode 100644 pumpkin-world/src/generation/feature/features/spring_feature.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/leave_vine.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/decorator/trunk_vine.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/acacia.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/blob.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/bush.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/cherry.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/dark_oak.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/fancy.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/jungle.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/random_spread.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/foliage/spruce.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/fancy.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/mega_jungle.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/straight.rs create mode 100644 pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs create mode 100644 pumpkin-world/src/generation/feature/features/twisting_vines.rs create mode 100644 pumpkin-world/src/generation/feature/features/underwater_magma.rs create mode 100644 pumpkin-world/src/generation/feature/features/vegetation_patch.rs create mode 100644 pumpkin-world/src/generation/feature/features/vines.rs create mode 100644 pumpkin-world/src/generation/feature/features/void_start_platform.rs create mode 100644 pumpkin-world/src/generation/feature/features/waterlogged_vegetation_patch.rs create mode 100644 pumpkin-world/src/generation/feature/features/weeping_vines.rs create mode 100644 pumpkin-world/src/generation/feature/mod.rs create mode 100644 pumpkin-world/src/generation/feature/placed_features.rs create mode 100644 pumpkin-world/src/generation/feature/size.rs delete mode 100644 pumpkin-world/src/generation/generator.rs create mode 100644 pumpkin-world/src/generation/rule/block_match.rs create mode 100644 pumpkin-world/src/generation/rule/block_state_match.rs create mode 100644 pumpkin-world/src/generation/rule/mod.rs create mode 100644 pumpkin-world/src/generation/rule/random_block_match.rs create mode 100644 pumpkin-world/src/generation/rule/random_block_state_match.rs create mode 100644 pumpkin-world/src/generation/rule/tag_match.rs create mode 100644 pumpkin-world/src/generation/rule_test.rs create mode 100644 pumpkin/src/block/blocks/bamboo.rs create mode 100644 pumpkin/src/block/blocks/plant/bush.rs create mode 100644 pumpkin/src/block/blocks/plant/dry_vegetation.rs create mode 100644 pumpkin/src/block/blocks/plant/flower.rs create mode 100644 pumpkin/src/block/blocks/plant/flowerbed.rs create mode 100644 pumpkin/src/block/blocks/plant/leaf_litter.rs rename pumpkin/src/block/blocks/{ => plant}/lily_pad.rs (100%) create mode 100644 pumpkin/src/block/blocks/plant/mod.rs create mode 100644 pumpkin/src/block/blocks/plant/mushroom_plant.rs create mode 100644 pumpkin/src/block/blocks/plant/sapling.rs create mode 100644 pumpkin/src/block/blocks/plant/short_plant.rs create mode 100644 pumpkin/src/block/blocks/plant/tall_plant.rs create mode 100644 pumpkin/src/block/blocks/vine.rs diff --git a/Cargo.lock b/Cargo.lock index 31e9e8bdc..d51ebb544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,12 +114,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "once_cell_polyfill", + "once_cell", "windows-sys 0.59.0", ] @@ -174,12 +174,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.22.1" @@ -213,21 +207,18 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.11.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94" -dependencies = [ - "hybrid-array", -] - [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -242,9 +233,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.25" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -326,24 +317,24 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.6", + "crypto-common", "inout", ] [[package]] name = "clap" -version = "4.5.39" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstyle", "clap_lex", @@ -372,9 +363,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.10.1" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -543,19 +534,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" -[[package]] -name = "crypto-bigint" -version = "0.7.0-pre.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edaae5fb9dac79a07260e0b2006799ff4f1d342ab243fd7d0892215113b27904" -dependencies = [ - "num-traits", - "rand_core", - "serdect", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -566,26 +544,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "crypto-primes" -version = "0.7.0-pre.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae744b9f528151f8c440cf67498f24d2d1ac0ab536b5ce7b1f87a7a5961bd1c1" -dependencies = [ - "crypto-bigint", - "libm", - "rand_core", -] - [[package]] name = "dashmap" version = "6.1.0" @@ -602,9 +560,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0-rc.3" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00a9651bf9c00a38b7c383073cb22fb42f20a5f978c9f97ad5c7128cbd3c1bd" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -674,19 +632,9 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.6", -] - -[[package]] -name = "digest" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460dd7f37e4950526b54a5a6b1f41b6c8e763c58eb9a8fc8fc05ba5c2f44ca7b" -dependencies = [ - "block-buffer 0.11.0-rc.4", + "block-buffer", "const-oid", - "crypto-common 0.2.0-rc.3", + "crypto-common", "subtle", ] @@ -718,9 +666,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fe7d068ca6b3a5782ca5ec9afc244acd99dd441e4686a83b1c3973aba1d489" +checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" dependencies = [ "cc", "memchr", @@ -1043,11 +991,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hmac" -version = "0.13.0-rc.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc6a2fcc35ab09136c6df2cdf9ca49790701420a3a6b5db0987dddbabc79b21" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.11.0-rc.0", + "digest", ] [[package]] @@ -1090,15 +1038,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "hybrid-array" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af" -dependencies = [ - "typenum", -] - [[package]] name = "hyper" version = "1.6.0" @@ -1121,10 +1060,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ + "futures-util", "http", "hyper", "hyper-util", @@ -1133,33 +1073,27 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ - "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -1235,9 +1169,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", @@ -1251,9 +1185,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" @@ -1319,16 +1253,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1370,9 +1294,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -1383,9 +1307,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -1417,6 +1341,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1426,9 +1353,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", "windows-targets 0.53.0", @@ -1460,9 +1387,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1515,7 +1442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", ] [[package]] @@ -1554,14 +1481,14 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mio" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1574,6 +1501,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1600,6 +1544,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1607,6 +1562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1633,12 +1589,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - [[package]] name = "oorandom" version = "11.1.5" @@ -1647,9 +1597,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1657,9 +1607,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", @@ -1670,9 +1620,9 @@ dependencies = [ [[package]] name = "pem-rfc7468" -version = "1.0.0-rc.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e58fab693c712c0d4e88f8eb3087b6521d060bcaf76aeb20cb192d809115ba" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] @@ -1725,7 +1675,7 @@ checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -1762,9 +1712,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" -version = "0.8.0-rc.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e16d93c725fa250577ffdec06ebbff4cae3625b0e2881ac43a5427797ee8d3" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", @@ -1773,9 +1723,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1843d4345dfe1a55e487db747a04c01af50415b03e937410e0a41d8cc24ec7" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", @@ -1903,7 +1853,7 @@ dependencies = [ "pumpkin-registry", "pumpkin-util", "pumpkin-world", - "rand", + "rand 0.8.5", "rayon", "reqwest", "rsa", @@ -1913,7 +1863,7 @@ dependencies = [ "serde", "serde_json", "sha1", - "sha2 0.11.0-rc.0", + "sha2", "simplelog", "tauri-winres", "tempfile", @@ -2049,7 +1999,7 @@ dependencies = [ "proc-macro2", "pumpkin-nbt", "quote", - "rand", + "rand 0.9.1", "serde", "serde_json", "syn", @@ -2087,7 +2037,7 @@ dependencies = [ "serde", "serde_json", "serde_json5", - "sha2 0.10.9", + "sha2", "temp-dir", "thiserror", "thread_local", @@ -2126,7 +2076,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand", + "rand 0.9.1", "ring", "rustc-hash 2.1.1", "rustls", @@ -2167,14 +2117,35 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -2184,7 +2155,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -2256,13 +2236,14 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.18" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -2279,21 +2260,23 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-rustls", "tower", - "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.26.11", + "windows-registry", ] [[package]] @@ -2312,17 +2295,18 @@ dependencies = [ [[package]] name = "rsa" -version = "0.10.0-rc.0" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30f0ad781aea19fe741d7a901b2ad8b4271ac3516e7045b8ecff74e201968fe" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", - "crypto-bigint", - "crypto-primes", - "digest 0.11.0-rc.0", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sha1", "signature", "spki", @@ -2393,6 +2377,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -2416,9 +2409,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rustyline-async" @@ -2526,25 +2519,15 @@ dependencies = [ "serde", ] -[[package]] -name = "serdect" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" -dependencies = [ - "base16ct", - "serde", -] - [[package]] name = "sha1" -version = "0.11.0-rc.0" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9318facddf9ac32a33527066936837e189b3f23ced6edc1603720ead5e2b3d" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.0", + "digest", ] [[package]] @@ -2555,18 +2538,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.0", + "digest", ] [[package]] @@ -2607,12 +2579,12 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ae074ff622614874804868b07d9cb786223082c9fe726a6653608f32f37b02" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.11.0-rc.0", - "rand_core", + "digest", + "rand_core 0.6.4", ] [[package]] @@ -2662,19 +2634,25 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.10" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] -name = "spki" -version = "0.8.0-rc.2" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f0e2bdca9b00f5be6dd3bb6647d50fd0f24a508a95f78e3bb2fe98d0403c85" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -2905,9 +2883,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -3012,24 +2990,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "tower-http" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" -dependencies = [ - "bitflags 2.9.1", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.3" @@ -3128,15 +3088,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom 0.3.3", - "js-sys", "md-5", "serde", - "wasm-bindgen", ] [[package]] @@ -3290,6 +3248,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + [[package]] name = "webpki-roots" version = "1.0.0" @@ -3332,15 +3299,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.2" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings 0.4.1", ] [[package]] @@ -3384,9 +3351,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" dependencies = [ "windows-link", ] @@ -3402,13 +3369,22 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3427,6 +3403,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3459,6 +3450,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3471,6 +3468,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3483,6 +3486,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3507,6 +3516,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3519,6 +3534,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3531,6 +3552,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3543,6 +3570,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3566,12 +3599,12 @@ dependencies = [ [[package]] name = "winreg" -version = "0.55.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 19905457c..26a1b9904 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ futures = "0.3" rayon = "1.10" crossbeam = "0.8" -uuid = { version = "1.17", features = ["serde", "v3", "v4"] } +uuid = { version = "1.16", features = ["serde", "v3", "v4"] } derive_more = { version = "2.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/assets/gen_features.json b/assets/configured_features.json similarity index 100% rename from assets/gen_features.json rename to assets/configured_features.json diff --git a/pumpkin-data/build/block.rs b/pumpkin-data/build/block.rs index 131173b38..0923d3039 100644 --- a/pumpkin-data/build/block.rs +++ b/pumpkin-data/build/block.rs @@ -275,15 +275,20 @@ impl ToTokens for BlockPropertyStruct { } } + #[inline] + fn handles_block_id(block_id: u16) -> bool where Self: Sized { + [#(#block_ids),*].contains(&block_id) + } + fn to_state_id(&self, block: &Block) -> u16 { - if ![#(#block_ids),*].contains(&block.id) { + if !Self::handles_block_id(block.id) { panic!("{} is not a valid block for {}", &block.name, #struct_name); } block.states[0].id + self.to_index() } fn from_state_id(state_id: u16, block: &Block) -> Self { - if ![#(#block_ids),*].contains(&block.id) { + if !Self::handles_block_id(block.id) { panic!("{} is not a valid block for {}", &block.name, #struct_name); } if state_id >= block.states[0].id && state_id <= block.states.last().unwrap().id { @@ -295,7 +300,7 @@ impl ToTokens for BlockPropertyStruct { } fn default(block: &Block) -> Self { - if ![#(#block_ids),*].contains(&block.id) { + if !Self::handles_block_id(block.id) { panic!("{} is not a valid block for {}", &block.name, #struct_name); } Self::from_state_id(block.default_state_id, block) @@ -307,7 +312,6 @@ impl ToTokens for BlockPropertyStruct { #(#to_props_values)* props } - fn from_props(props: Vec<(&str, &str)>, block: &Block) -> Self { if ![#(#block_ids),*].contains(&block.id) { panic!("{} is not a valid block for {}", &block.name, #struct_name); @@ -1316,6 +1320,9 @@ pub(crate) fn build() -> TokenStream { // Convert an index back to properties. fn from_index(index: u16) -> Self where Self: Sized; + // Check if a block uses this property + fn handles_block_id(block_id: u16) -> bool where Self: Sized; + // Convert properties to a state id. fn to_state_id(&self, block: &Block) -> u16; // Convert a state id back to properties. diff --git a/pumpkin-data/build/noise_parameter.rs b/pumpkin-data/build/noise_parameter.rs index 118979350..3d2fea5d9 100644 --- a/pumpkin-data/build/noise_parameter.rs +++ b/pumpkin-data/build/noise_parameter.rs @@ -1,20 +1,13 @@ use std::collections::HashMap; use proc_macro2::TokenStream; +use pumpkin_util::DoublePerlinNoiseParametersCodec; use quote::{format_ident, quote}; -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct DoublePerlinNoiseParameters { - #[serde(rename = "firstOctave")] - first_octave: i32, - amplitudes: Vec, -} pub(crate) fn build() -> TokenStream { println!("cargo:rerun-if-changed=../assets/noise_parameters.json"); - let json: HashMap = + let json: HashMap = serde_json::from_str(include_str!("../../assets/noise_parameters.json")) .expect("Failed to parse noise_parameters.json"); let mut variants = TokenStream::new(); diff --git a/pumpkin-data/src/block_direction.rs b/pumpkin-data/src/block_direction.rs index b0f83afbd..648566320 100644 --- a/pumpkin-data/src/block_direction.rs +++ b/pumpkin-data/src/block_direction.rs @@ -1,8 +1,13 @@ use crate::block_properties::{Axis, Facing, HorizontalFacing}; -use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::{ + math::vector3::Vector3, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; #[repr(u8)] -#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)] +#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum BlockDirection { Down = 0, Up, @@ -54,6 +59,10 @@ impl BlockDirection { } } + pub fn random(random: &mut RandomGenerator) -> Self { + Self::all()[random.next_bounded_i32(Self::all().len() as i32 - 1) as usize] + } + pub fn by_index(index: usize) -> Option { Self::all().get(index % Self::all().len()).cloned() } diff --git a/pumpkin-protocol/src/client/config/update_tags.rs b/pumpkin-protocol/src/client/config/update_tags.rs index fd34cb41b..3a9c60505 100644 --- a/pumpkin-protocol/src/client/config/update_tags.rs +++ b/pumpkin-protocol/src/client/config/update_tags.rs @@ -1,5 +1,11 @@ use std::io::Write; +use crate::{ + ClientPacket, + codec::identifier::Identifier, + ser::{NetworkWriteExt, WritingError}, +}; + use pumpkin_data::{ block_properties::get_block, fluid::Fluid, @@ -8,12 +14,6 @@ use pumpkin_data::{ }; use pumpkin_macros::packet; -use crate::{ - ClientPacket, - codec::identifier::Identifier, - ser::{NetworkWriteExt, WritingError}, -}; - #[packet(CONFIG_UPDATE_TAGS)] pub struct CUpdateTags<'a> { tags: &'a [pumpkin_data::tag::RegistryKey], diff --git a/pumpkin-util/src/lib.rs b/pumpkin-util/src/lib.rs index 1875b5c22..eb97ed9df 100644 --- a/pumpkin-util/src/lib.rs +++ b/pumpkin-util/src/lib.rs @@ -107,6 +107,13 @@ impl Index for MutableSplitSlice<'_, T> { } } +#[derive(Deserialize, Clone)] +pub struct DoublePerlinNoiseParametersCodec { + #[serde(rename = "firstOctave")] + pub first_octave: i32, + pub amplitudes: Vec, +} + impl IndexMut for MutableSplitSlice<'_, T> { #[allow(clippy::comparison_chain)] fn index_mut(&mut self, index: usize) -> &mut Self::Output { diff --git a/pumpkin-util/src/math/int_provider.rs b/pumpkin-util/src/math/int_provider.rs index ae9b275e3..d16023379 100644 --- a/pumpkin-util/src/math/int_provider.rs +++ b/pumpkin-util/src/math/int_provider.rs @@ -3,12 +3,23 @@ use quote::{ToTokens, quote}; use serde::Deserialize; use syn::LitInt; +use crate::random::{RandomGenerator, RandomImpl}; + +use super::pool::{Pool, Weighted}; + #[derive(Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum NormalIntProvider { #[serde(rename = "minecraft:uniform")] Uniform(UniformIntProvider), - // TODO: Add more... + #[serde(rename = "minecraft:weighted_list")] + WeightedList(WeightedListIntProvider), + #[serde(rename = "minecraft:clamped")] + Clamped(ClampedIntProvider), + #[serde(rename = "minecraft:clamped_normal")] + ClampedNormal(ClampedNormalIntProvider), + #[serde(rename = "minecraft:biased_to_bottom")] + BiasedToBottom(BiasedToBottomIntProvider), // TODO: Add more... } impl ToTokens for NormalIntProvider { @@ -19,6 +30,10 @@ impl ToTokens for NormalIntProvider { NormalIntProvider::Uniform(#uniform) }); } + NormalIntProvider::WeightedList(_) => todo!(), + NormalIntProvider::Clamped(_) => todo!(), + NormalIntProvider::BiasedToBottom(_biased_to_bottom_int_provider) => todo!(), + NormalIntProvider::ClampedNormal(_clamped_int_provider) => todo!(), } } } @@ -50,15 +65,23 @@ impl IntProvider { match self { IntProvider::Object(int_provider) => match int_provider { NormalIntProvider::Uniform(uniform) => uniform.get_min(), + NormalIntProvider::WeightedList(provider) => provider.get_min(), + NormalIntProvider::Clamped(provider) => provider.get_min(), + NormalIntProvider::BiasedToBottom(provider) => provider.get_min(), + NormalIntProvider::ClampedNormal(provider) => provider.get_min(), }, IntProvider::Constant(i) => *i, } } - pub fn get(&self) -> i32 { + pub fn get(&self, random: &mut RandomGenerator) -> i32 { match self { IntProvider::Object(int_provider) => match int_provider { - NormalIntProvider::Uniform(uniform) => uniform.get(), + NormalIntProvider::Uniform(uniform) => uniform.get(random), + NormalIntProvider::WeightedList(provider) => provider.get(random), + NormalIntProvider::Clamped(provider) => provider.get(random), + NormalIntProvider::BiasedToBottom(provider) => provider.get(random), + NormalIntProvider::ClampedNormal(provider) => provider.get(random), }, IntProvider::Constant(i) => *i, } @@ -68,12 +91,108 @@ impl IntProvider { match self { IntProvider::Object(int_provider) => match int_provider { NormalIntProvider::Uniform(uniform) => uniform.get_max(), + NormalIntProvider::WeightedList(provider) => provider.get_max(), + NormalIntProvider::Clamped(provider) => provider.get_max(), + NormalIntProvider::BiasedToBottom(provider) => provider.get_max(), + NormalIntProvider::ClampedNormal(provider) => provider.get_max(), }, IntProvider::Constant(i) => *i, } } } +#[derive(Deserialize, Clone, Debug)] +pub struct ClampedNormalIntProvider { + mean: f32, + deviation: f32, + min_inclusive: i32, + max_inclusive: i32, +} + +impl ClampedNormalIntProvider { + pub fn get_min(&self) -> i32 { + self.min_inclusive + } + pub fn get(&self, random: &mut RandomGenerator) -> i32 { + (self.mean + random.next_gaussian() as f32 * self.deviation) + .clamp(self.min_inclusive as f32, self.max_inclusive as f32) as i32 + } + pub fn get_max(&self) -> i32 { + self.max_inclusive + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct BiasedToBottomIntProvider { + min_inclusive: i32, + max_inclusive: i32, +} + +impl BiasedToBottomIntProvider { + pub fn get_min(&self) -> i32 { + self.min_inclusive + } + pub fn get(&self, random: &mut RandomGenerator) -> i32 { + // TODO: not sure if this is called first this matches vanilla + let first_gen = random.next_bounded_i32(self.max_inclusive - self.min_inclusive + 1) + 1; + self.min_inclusive + random.next_bounded_i32(first_gen) + } + pub fn get_max(&self) -> i32 { + self.max_inclusive + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct ClampedIntProvider { + source: Box, + min_inclusive: i32, + max_inclusive: i32, +} + +impl ClampedIntProvider { + pub fn get_min(&self) -> i32 { + self.min_inclusive.max(self.source.get_min()) + } + pub fn get(&self, random: &mut RandomGenerator) -> i32 { + self.source + .get(random) + .clamp(self.min_inclusive, self.max_inclusive) + } + pub fn get_max(&self) -> i32 { + self.max_inclusive.min(self.source.get_max()) + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct WeightedListIntProvider { + distribution: Vec>, +} + +impl WeightedListIntProvider { + pub fn get_min(&self) -> i32 { + let mut min = i32::MAX; + for dist in &self.distribution { + let dmin = dist.data.get_min(); + min = min.min(dmin); + } + min + } + pub fn get(&self, random: &mut RandomGenerator) -> i32 { + if let Some(int) = Pool.get(&self.distribution, random) { + return int.get(random); + } + 0 + } + pub fn get_max(&self) -> i32 { + let mut max = i32::MIN; + for dist in &self.distribution { + let dmax = dist.data.get_max(); + max = max.max(dmax); + } + max + } +} + #[derive(Deserialize, Clone, Debug)] pub struct UniformIntProvider { pub min_inclusive: i32, @@ -95,8 +214,8 @@ impl UniformIntProvider { pub fn get_min(&self) -> i32 { self.min_inclusive } - pub fn get(&self) -> i32 { - rand::random_range(self.min_inclusive..self.max_inclusive) + pub fn get(&self, random: &mut RandomGenerator) -> i32 { + random.next_inbetween_i32(self.min_inclusive, self.max_inclusive) } pub fn get_max(&self) -> i32 { self.max_inclusive diff --git a/pumpkin-util/src/math/mod.rs b/pumpkin-util/src/math/mod.rs index feadb15b3..c72bb63a7 100644 --- a/pumpkin-util/src/math/mod.rs +++ b/pumpkin-util/src/math/mod.rs @@ -4,6 +4,7 @@ pub mod boundingbox; pub mod experience; pub mod float_provider; pub mod int_provider; +pub mod pool; pub mod position; pub mod vector2; pub mod vector3; diff --git a/pumpkin-util/src/math/pool.rs b/pumpkin-util/src/math/pool.rs new file mode 100644 index 000000000..8535ba456 --- /dev/null +++ b/pumpkin-util/src/math/pool.rs @@ -0,0 +1,55 @@ +use serde::Deserialize; + +use crate::random::{RandomGenerator, RandomImpl}; + +#[derive(Deserialize, Clone, Debug)] +pub struct Pool; + +impl Pool { + pub fn get( + &self, + distribution: &[Weighted], + random: &mut RandomGenerator, + ) -> Option { + let mut total_weight = 0; + for dist in distribution { + total_weight += dist.weight; + } + let index = random.next_bounded_i32(total_weight); + if total_weight < 64 { + return Some(FlattenedContent::get(index, distribution, total_weight)); + } else { + // WrappedContent + for dist in distribution { + if index - dist.weight >= 0 { + continue; + } + return Some(dist.data.clone()); + } + } + None + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct Weighted { + pub data: E, + pub weight: i32, +} + +struct FlattenedContent; + +impl FlattenedContent { + pub fn get(index: i32, entries: &[Weighted], total_weight: i32) -> E { + let mut final_entries = Vec::with_capacity(total_weight as usize); + let mut cur_index = 0; + for entry in entries { + let weight = entry.weight; + for i in cur_index..cur_index + weight { + final_entries.insert(i as usize, entry.data.clone()); + } + cur_index += weight; + } + final_entries[index as usize].clone() + } +} diff --git a/pumpkin-util/src/math/position.rs b/pumpkin-util/src/math/position.rs index ea9677f1e..5fb8c03e3 100644 --- a/pumpkin-util/src/math/position.rs +++ b/pumpkin-util/src/math/position.rs @@ -156,6 +156,10 @@ impl BlockPos { BlockPos(self.0 + offset) } + pub fn add(&self, x: i32, y: i32, z: i32) -> Self { + BlockPos::new(self.0.x + x, self.0.y + y, self.0.z + z) + } + pub fn offset_dir(&self, offset: Vector3, direction: i32) -> Self { BlockPos(Vector3::new( self.0.x + offset.x * direction, @@ -168,9 +172,17 @@ impl BlockPos { self.offset(Vector3::new(0, 1, 0)) } + pub fn up_height(&self, height: i32) -> Self { + self.offset(Vector3::new(0, height, 0)) + } + pub fn down(&self) -> Self { self.offset(Vector3::new(0, -1, 0)) } + + pub fn down_height(&self, height: i32) -> Self { + self.offset(Vector3::new(0, -height, 0)) + } } impl Serialize for BlockPos { fn serialize(&self, serializer: S) -> Result diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index bab4da85b..677928e81 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -239,6 +239,39 @@ impl Math for i32 {} impl Math for i64 {} impl Math for u8 {} +impl<'de> serde::Deserialize<'de> for Vector3 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Vector3Visitor; + + impl<'de> serde::de::Visitor<'de> for Vector3Visitor { + type Value = Vector3; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid Vector") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + if let Some(x) = seq.next_element::()? { + if let Some(y) = seq.next_element::()? { + if let Some(z) = seq.next_element::()? { + return Ok(Vector3::new(x, y, z)); + } + } + } + Err(serde::de::Error::custom("Failed to read Vector")) + } + } + + deserializer.deserialize_seq(Vector3Visitor) + } +} + impl<'de> serde::Deserialize<'de> for Vector3 { fn deserialize(deserializer: D) -> Result where diff --git a/pumpkin-util/src/random/legacy_rand.rs b/pumpkin-util/src/random/legacy_rand.rs index 5f4921eef..5ec96c0b5 100644 --- a/pumpkin-util/src/random/legacy_rand.rs +++ b/pumpkin-util/src/random/legacy_rand.rs @@ -3,7 +3,7 @@ use super::{ hash_block_pos, }; -use crate::math::java_string_hash; +use crate::{math::java_string_hash, population_seed_fn}; pub struct LegacyRand { seed: u64, @@ -11,6 +11,8 @@ pub struct LegacyRand { } impl LegacyRand { + population_seed_fn!(); + pub fn from_seed(seed: u64) -> Self { LegacyRand { seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF, diff --git a/pumpkin-util/src/random/mod.rs b/pumpkin-util/src/random/mod.rs index dc621ce74..b9c0da66e 100644 --- a/pumpkin-util/src/random/mod.rs +++ b/pumpkin-util/src/random/mod.rs @@ -44,6 +44,30 @@ pub enum RandomDeriver { Legacy(LegacySplitter), } +// TODO: Write unit test for this +#[macro_export] +macro_rules! population_seed_fn { + () => { + pub fn get_population_seed(world_seed: u64, block_x: i32, block_z: i32) -> u64 { + let mut rand = Self::from_seed(world_seed); + let l = rand.next_i64() | 1; + let m = rand.next_i64() | 1; + let base = (block_x as i64) + .wrapping_mul(l) + .wrapping_add((block_z as i64).wrapping_mul(m)); + (base as u64) ^ world_seed + } + }; +} + +// TODO: Write unit test for this +#[inline] +pub fn get_decorator_seed(population_seed: u64, index: usize, step: usize) -> u64 { + population_seed + .wrapping_add(index as u64) + .wrapping_add(10_000u64.wrapping_mul(step as u64)) +} + #[enum_dispatch] pub trait RandomImpl { fn split(&mut self) -> Self; diff --git a/pumpkin-util/src/random/xoroshiro128.rs b/pumpkin-util/src/random/xoroshiro128.rs index 499db1fc9..a6168315b 100644 --- a/pumpkin-util/src/random/xoroshiro128.rs +++ b/pumpkin-util/src/random/xoroshiro128.rs @@ -1,3 +1,5 @@ +use crate::population_seed_fn; + use super::{ RandomDeriver, RandomDeriverImpl, RandomGenerator, RandomImpl, gaussian::GaussianGenerator, hash_block_pos, @@ -10,6 +12,8 @@ pub struct Xoroshiro { } impl Xoroshiro { + population_seed_fn!(); + pub fn from_seed(seed: u64) -> Self { let (lo, hi) = Self::mix_u64(seed); let lo = mix_stafford_13(lo); diff --git a/pumpkin-world/benches/chunk_io.rs b/pumpkin-world/benches/chunk_io.rs index 56c49ca7e..70e60e299 100644 --- a/pumpkin-world/benches/chunk_io.rs +++ b/pumpkin-world/benches/chunk_io.rs @@ -1,8 +1,16 @@ use std::{fs, path::PathBuf, sync::Arc}; +use async_trait::async_trait; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; -use pumpkin_util::math::vector2::Vector2; -use pumpkin_world::{chunk::ChunkData, dimension::Dimension, global_path, level::Level}; +use pumpkin_data::BlockDirection; +use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; +use pumpkin_world::{ + chunk::ChunkData, + dimension::Dimension, + global_path, + level::Level, + world::{BlockAccessor, BlockRegistryExt}, +}; use tokio::{runtime::Runtime, sync::RwLock}; async fn test_reads(level: &Arc, positions: Vec>) { @@ -69,6 +77,21 @@ const MAX_CHUNK: i32 = 16; // How many chunks to use on parallel tests //const CHUNKS_ON_PARALLEL: usize = 32; +struct BlockRegistry; + +#[async_trait] +impl BlockRegistryExt for BlockRegistry { + async fn can_place_at( + &self, + _block: &pumpkin_data::Block, + _block_accessor: &dyn BlockAccessor, + _block_pos: &BlockPos, + _face: BlockDirection, + ) -> bool { + true + } +} + fn initialize_level( async_handler: &Runtime, root_dir: PathBuf, @@ -78,10 +101,12 @@ fn initialize_level( let mut chunks = Vec::new(); async_handler.block_on(async { let (send, mut recv) = tokio::sync::mpsc::unbounded_channel(); + let block_registry = Arc::new(BlockRegistry); // Our data dir is empty, so we're generating new chunks here let level_to_save = Arc::new(Level::from_root_folder( root_dir.clone(), + block_registry, 123, Dimension::Overworld, )); @@ -193,6 +218,8 @@ fn bench_chunk_io(c: &mut Criterion) { n_chunks, chunks.len() ); + let block_registry = Arc::new(BlockRegistry); + write_group.bench_with_input( BenchmarkId::new("Single", n_chunks), &chunks, @@ -200,6 +227,7 @@ fn bench_chunk_io(c: &mut Criterion) { b.to_async(&async_handler).iter(async || { let level = Arc::new(Level::from_root_folder( root_dir.clone(), + block_registry.clone(), 123, Dimension::Overworld, )); @@ -222,6 +250,7 @@ fn bench_chunk_io(c: &mut Criterion) { n_chunks, positions.len() ); + let block_registry = Arc::new(BlockRegistry); read_group.bench_with_input( BenchmarkId::new("Single", n_chunks), @@ -230,6 +259,7 @@ fn bench_chunk_io(c: &mut Criterion) { b.to_async(&async_handler).iter(async || { let level = Arc::new(Level::from_root_folder( root_dir.clone(), + block_registry.clone(), 123, Dimension::Overworld, )); diff --git a/pumpkin-world/src/block/mod.rs b/pumpkin-world/src/block/mod.rs index 7599a72ba..848d5b36b 100644 --- a/pumpkin-world/src/block/mod.rs +++ b/pumpkin-world/src/block/mod.rs @@ -1,14 +1,46 @@ pub mod entities; pub mod state; -use serde::Deserialize; +use std::collections::HashMap; + +use pumpkin_data::{ + BlockState, + block_properties::{get_block, get_state_by_state_id}, +}; +use serde::{Deserialize, Serialize}; pub use state::RawBlockState; -#[derive(Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] pub struct BlockStateCodec { + /// Block name pub name: String, - // TODO: properties... + /// Key-value pairs of properties + #[serde(skip_serializing_if = "Option::is_none")] + pub properties: Option>, +} + +impl BlockStateCodec { + pub fn get_state(&self) -> Option { + let block = get_block(self.name.as_str()); + + if let Some(block) = block { + let mut state_id = block.default_state_id; + + if let Some(properties) = &self.properties { + let mut properties_vec: Vec<(&str, &str)> = Vec::with_capacity(properties.len()); + for (key, value) in properties { + properties_vec.push((key, value)); + } + let block_properties = block.from_properties(properties_vec).unwrap(); + state_id = block_properties.to_state_id(&block); + } + + return get_state_by_state_id(state_id); + } + + None + } } #[cfg(test)] diff --git a/pumpkin-world/src/block/state.rs b/pumpkin-world/src/block/state.rs index 6e515be4f..3f93ba670 100644 --- a/pumpkin-world/src/block/state.rs +++ b/pumpkin-world/src/block/state.rs @@ -1,6 +1,6 @@ use pumpkin_data::block_properties::{get_block, get_block_by_state_id, get_state_by_state_id}; -use crate::{BlockStateId, chunk::format::PaletteBlockEntry}; +use crate::BlockStateId; /// Instead of using a memory heavy normal BlockState This is used for internal representation in chunks to save memory #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -19,27 +19,6 @@ impl RawBlockState { }) } - pub fn from_palette(palette: &PaletteBlockEntry) -> Option { - let block = get_block(palette.name.as_str()); - - if let Some(block) = block { - let mut state_id = block.default_state_id; - - if let Some(properties) = &palette.properties { - let mut properties_vec: Vec<(&str, &str)> = Vec::with_capacity(properties.len()); - for (key, value) in properties { - properties_vec.push((key, value)); - } - let block_properties = block.from_properties(properties_vec).unwrap(); - state_id = block_properties.to_state_id(&block); - } - - return Some(Self { state_id }); - } - - None - } - pub fn to_state(&self) -> pumpkin_data::BlockState { get_state_by_state_id(self.state_id).unwrap() } diff --git a/pumpkin-world/src/chunk/format/anvil.rs b/pumpkin-world/src/chunk/format/anvil.rs index 963963bef..da975eddb 100644 --- a/pumpkin-world/src/chunk/format/anvil.rs +++ b/pumpkin-world/src/chunk/format/anvil.rs @@ -901,7 +901,10 @@ pub async fn chunk_to_bytes(chunk_data: &ChunkData) -> Result, ChunkSeri #[cfg(test)] mod tests { + use async_trait::async_trait; use pumpkin_config::{AdvancedConfiguration, advanced_config, override_config_for_testing}; + use pumpkin_data::BlockDirection; + use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use std::fs; use std::path::PathBuf; @@ -914,7 +917,23 @@ mod tests { use crate::chunk::io::{ChunkIO, LoadedData}; use crate::dimension::Dimension; use crate::generation::{Seed, get_world_gen}; - use crate::level::{LevelFolder, SyncChunk}; + use crate::level::{Level, LevelFolder, SyncChunk}; + use crate::world::{BlockAccessor, BlockRegistryExt}; + + struct BlockRegistry; + + #[async_trait] + impl BlockRegistryExt for BlockRegistry { + async fn can_place_at( + &self, + _block: &pumpkin_data::Block, + _block_accessor: &dyn BlockAccessor, + _block_pos: &BlockPos, + _face: BlockDirection, + ) -> bool { + true + } + } async fn get_chunks( saver: &ChunkFileManager, @@ -992,13 +1011,22 @@ mod tests { }; fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder"); let chunk_saver = ChunkFileManager::::default(); + let block_registry = Arc::new(BlockRegistry); // Generate chunks let mut chunks = vec![]; + let level = Arc::new(Level::from_root_folder( + temp_dir.path().to_path_buf(), + block_registry.clone(), + 0, + Dimension::Overworld, + )); for x in -5..5 { for y in -5..5 { let position = Vector2::new(x, y); - let chunk = generator.generate_chunk(&position); + let chunk = generator + .generate_chunk(&level, block_registry.as_ref(), &position) + .await; chunks.push((position, Arc::new(RwLock::new(chunk)))); } } @@ -1260,13 +1288,22 @@ mod tests { }; fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder"); let chunk_saver = ChunkFileManager::::default(); + let block_registry = Arc::new(BlockRegistry); // Generate chunks let mut chunks = vec![]; + let level = Arc::new(Level::from_root_folder( + temp_dir.path().to_path_buf(), + block_registry.clone(), + 0, + Dimension::Overworld, + )); for x in -5..5 { for y in -5..5 { let position = Vector2::new(x, y); - let chunk = generator.generate_chunk(&position); + let chunk = generator + .generate_chunk(&level, block_registry.as_ref(), &position) + .await; chunks.push((position, Arc::new(RwLock::new(chunk)))); } } diff --git a/pumpkin-world/src/chunk/format/linear.rs b/pumpkin-world/src/chunk/format/linear.rs index 9c1bda5b9..6138268f1 100644 --- a/pumpkin-world/src/chunk/format/linear.rs +++ b/pumpkin-world/src/chunk/format/linear.rs @@ -365,7 +365,10 @@ impl ChunkSerializer for LinearFile { #[cfg(test)] mod tests { + use async_trait::async_trait; use core::panic; + use pumpkin_data::BlockDirection; + use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use std::fs; use std::path::PathBuf; @@ -378,7 +381,23 @@ mod tests { use crate::chunk::io::{ChunkIO, LoadedData}; use crate::dimension::Dimension; use crate::generation::{Seed, get_world_gen}; - use crate::level::LevelFolder; + use crate::level::{Level, LevelFolder}; + use crate::world::{BlockAccessor, BlockRegistryExt}; + + struct BlockRegistry; + + #[async_trait] + impl BlockRegistryExt for BlockRegistry { + async fn can_place_at( + &self, + _block: &pumpkin_data::Block, + _block_accessor: &dyn BlockAccessor, + _block_pos: &BlockPos, + _face: BlockDirection, + ) -> bool { + true + } + } #[tokio::test(flavor = "multi_thread")] async fn not_existing() { @@ -419,13 +438,21 @@ mod tests { }; fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder"); let chunk_saver = ChunkFileManager::::default(); - + let block_registry = Arc::new(BlockRegistry); // Generate chunks let mut chunks = vec![]; + let level = Arc::new(Level::from_root_folder( + temp_dir.path().to_path_buf(), + block_registry.clone(), + 0, + Dimension::Overworld, + )); for x in -5..5 { for y in -5..5 { let position = Vector2::new(x, y); - let chunk = generator.generate_chunk(&position); + let chunk = generator + .generate_chunk(&level, block_registry.as_ref(), &position) + .await; chunks.push((position, Arc::new(RwLock::new(chunk)))); } } diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index 86e17301b..682fa4137 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -7,6 +7,8 @@ use crate::{block::entities::block_entity_from_nbt, generation::section_coords}; use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use serde::{Deserialize, Serialize}; +use crate::block::BlockStateCodec; + use super::{ ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections, ScheduledTick, SubChunk, TickPriority, @@ -216,7 +218,7 @@ pub struct ChunkSectionBlockStates { skip_serializing_if = "Option::is_none" )] pub(crate) data: Option>, - pub(crate) palette: Vec, + pub(crate) palette: Vec, } #[derive(Debug, Clone)] @@ -293,16 +295,6 @@ impl Default for LightContainer { } } -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct PaletteBlockEntry { - /// Block name - pub name: String, - /// Key-value pairs of properties - #[serde(skip_serializing_if = "Option::is_none")] - pub properties: Option>, -} - #[derive(Serialize, Deserialize, Debug)] struct SerializedScheduledTick { #[serde(rename = "x")] diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index c4857fdcc..a49bf7fe2 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -224,7 +224,7 @@ impl ChunkSections { block_state: BlockStateId, ) { let y = y - self.min_y; - debug_assert!(y >= 0); + debug_assert!(y > 0); let relative_y = y as usize; self.set_relative_block(relative_x, relative_y, relative_z, block_state); diff --git a/pumpkin-world/src/chunk/palette.rs b/pumpkin-world/src/chunk/palette.rs index 0205298bf..aa827d347 100644 --- a/pumpkin-world/src/chunk/palette.rs +++ b/pumpkin-world/src/chunk/palette.rs @@ -7,11 +7,9 @@ use std::{ use pumpkin_data::{Block, block_properties::get_state_by_state_id, chunk::Biome}; use pumpkin_util::encompassing_bits; -use crate::block::RawBlockState; +use crate::block::BlockStateCodec; -use super::format::{ - ChunkSectionBiomes, ChunkSectionBlockStates, PaletteBiomeEntry, PaletteBlockEntry, -}; +use super::format::{ChunkSectionBiomes, ChunkSectionBlockStates, PaletteBiomeEntry}; /// 3d array indexed by y,z,x type AbstractCube = [[[T; DIM]; DIM]; DIM]; @@ -401,8 +399,8 @@ impl BlockPalette { .palette .into_iter() .map(|entry| { - if let Some(block_state) = RawBlockState::from_palette(&entry) { - block_state.state_id + if let Some(block_state) = entry.get_state() { + block_state.id } else { log::warn!( "Could not find valid block state for {}. Defaulting...", @@ -436,10 +434,10 @@ impl BlockPalette { } } - fn block_state_id_to_palette_entry(registry_id: u16) -> PaletteBlockEntry { + fn block_state_id_to_palette_entry(registry_id: u16) -> BlockStateCodec { let block = Block::from_state_id(registry_id).unwrap(); - PaletteBlockEntry { + BlockStateCodec { name: block.name.into(), properties: { if let Some(properties) = block.properties(registry_id) { diff --git a/pumpkin-world/src/dimension.rs b/pumpkin-world/src/dimension.rs index 1b8a9b2a3..33dd14416 100644 --- a/pumpkin-world/src/dimension.rs +++ b/pumpkin-world/src/dimension.rs @@ -1,8 +1,8 @@ -use std::path::PathBuf; +use std::{path::PathBuf, sync::Arc}; use serde::Deserialize; -use crate::level::Level; +use crate::{level::Level, world::BlockRegistryExt}; #[derive(Clone, Copy, PartialEq, Eq, Hash, Deserialize, Debug)] #[serde(rename_all = "snake_case")] @@ -13,12 +13,17 @@ pub enum Dimension { } impl Dimension { - pub fn into_level(&self, mut base_directory: PathBuf, seed: i64) -> Level { + pub fn into_level( + &self, + mut base_directory: PathBuf, + block_registry: Arc, + seed: i64, + ) -> Level { match self { Dimension::Overworld => {} Dimension::Nether => base_directory.push("DIM-1"), Dimension::End => base_directory.push("DIM1"), } - Level::from_root_folder(base_directory, seed, *self) + Level::from_root_folder(base_directory, block_registry, seed, *self) } } diff --git a/pumpkin-world/src/generation/block_predicate.rs b/pumpkin-world/src/generation/block_predicate.rs new file mode 100644 index 000000000..5f8933dd2 --- /dev/null +++ b/pumpkin-world/src/generation/block_predicate.rs @@ -0,0 +1,277 @@ +use itertools::Itertools; +use pumpkin_data::{ + Block, BlockDirection, BlockState, block_properties::get_block_by_state_id, tag::Tagable, +}; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, block::BlockStateCodec, generation::height_limit::HeightLimitView, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct EmptyTODOStruct {} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum BlockPredicate { + #[serde(rename = "minecraft:matching_blocks")] + MatchingBlocks(MatchingBlocksBlockPredicate), + #[serde(rename = "minecraft:matching_block_tag")] + MatchingBlockTag(MatchingBlockTagPredicate), + #[serde(rename = "minecraft:matching_fluids")] + MatchingFluids(EmptyTODOStruct), + #[serde(rename = "minecraft:has_sturdy_face")] + HasSturdyFace(HasSturdyFacePredicate), + #[serde(rename = "minecraft:solid")] + Solid(SolidBlockPredicate), + #[serde(rename = "minecraft:replaceable")] + Replaceable(ReplaceableBlockPredicate), + #[serde(rename = "minecraft:would_survive")] + WouldSurvive(WouldSurviveBlockPredicate), + #[serde(rename = "minecraft:inside_world_bounds")] + InsideWorldBounds(InsideWorldBoundsBlockPredicate), + #[serde(rename = "minecraft:any_of")] + AnyOf(AnyOfBlockPredicate), + #[serde(rename = "minecraft:all_of")] + AllOf(AllOfBlockPredicate), + #[serde(rename = "minecraft:not")] + Not(NotBlockPredicate), + #[serde(rename = "minecraft:true")] + AlwaysTrue, + /// Not used + #[serde(rename = "minecraft:unobstructed")] + Unobstructed(EmptyTODOStruct), +} + +impl BlockPredicate { + pub async fn test( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk<'_>, + pos: &BlockPos, + ) -> bool { + match self { + BlockPredicate::MatchingBlocks(predicate) => predicate.test(chunk, pos), + BlockPredicate::MatchingBlockTag(predicate) => predicate.test(chunk, pos), + BlockPredicate::MatchingFluids(_predicate) => false, + BlockPredicate::HasSturdyFace(predicate) => predicate.test(chunk, pos), + BlockPredicate::Solid(predicate) => predicate.test(chunk, pos), + BlockPredicate::Replaceable(predicate) => predicate.test(chunk, pos), + BlockPredicate::WouldSurvive(predicate) => { + predicate.test(block_registry, chunk, pos).await + } + BlockPredicate::InsideWorldBounds(predicate) => predicate.test(chunk, pos), + BlockPredicate::AnyOf(predicate) => predicate.test(block_registry, chunk, pos).await, + BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos).await, + BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos).await, + BlockPredicate::AlwaysTrue => true, + BlockPredicate::Unobstructed(_predicate) => false, + } + } +} + +#[derive(Deserialize)] +pub struct MatchingBlocksBlockPredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, + blocks: MatchingBlocksWrapper, +} + +impl MatchingBlocksBlockPredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let block = self.offset.get_block(chunk, pos); + match &self.blocks { + MatchingBlocksWrapper::Single(single_block) => { + single_block.replace("minecraft:", "") == block.name + } + MatchingBlocksWrapper::Multiple(blocks) => blocks + .iter() + .map(|s| s.replace("minecraft:", "")) + .contains(block.name), + } + } +} + +#[derive(Deserialize)] +pub struct InsideWorldBoundsBlockPredicate { + offset: Vector3, +} + +impl InsideWorldBoundsBlockPredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let pos = pos.offset(self.offset); + !chunk.out_of_height(pos.0.y as i16) + } +} + +#[derive(Deserialize)] +pub struct MatchingBlockTagPredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, + tag: String, +} + +impl MatchingBlockTagPredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let block = self.offset.get_block(chunk, pos); + block.is_tagged_with(&self.tag).unwrap() + } +} + +#[derive(Deserialize)] +pub struct HasSturdyFacePredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, + direction: BlockDirection, +} + +impl HasSturdyFacePredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let state = self.offset.get_state(chunk, pos); + state.is_side_solid(self.direction) + } +} + +#[derive(Deserialize)] +pub struct AnyOfBlockPredicate { + predicates: Vec, +} + +impl AnyOfBlockPredicate { + pub async fn test( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk<'_>, + pos: &BlockPos, + ) -> bool { + for predicate in &self.predicates { + if !Box::pin(predicate.test(block_registry, chunk, pos)).await { + continue; + } + return true; + } + false + } +} + +#[derive(Deserialize)] +pub struct AllOfBlockPredicate { + predicates: Vec, +} + +impl AllOfBlockPredicate { + pub async fn test( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk<'_>, + pos: &BlockPos, + ) -> bool { + for predicate in &self.predicates { + if Box::pin(predicate.test(block_registry, chunk, pos)).await { + continue; + } + return false; + } + true + } +} + +#[derive(Deserialize)] +pub struct NotBlockPredicate { + predicate: Box, +} + +impl NotBlockPredicate { + pub async fn test( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk<'_>, + pos: &BlockPos, + ) -> bool { + !Box::pin(self.predicate.test(block_registry, chunk, pos)).await + } +} + +#[derive(Deserialize)] +pub struct SolidBlockPredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, +} + +impl SolidBlockPredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let state = self.offset.get_state(chunk, pos); + state.is_solid() + } +} + +#[derive(Deserialize)] +pub struct WouldSurviveBlockPredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, + state: BlockStateCodec, +} + +impl WouldSurviveBlockPredicate { + pub async fn test( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk<'_>, + pos: &BlockPos, + ) -> bool { + let state = self.state.get_state().unwrap(); + let pos = self.offset.get(pos); + return block_registry + .can_place_at( + &get_block_by_state_id(state.id).unwrap(), + chunk, + &pos, + BlockDirection::Up, + ) + .await; + } +} + +#[derive(Deserialize)] +pub struct ReplaceableBlockPredicate { + #[serde(flatten)] + offset: OffsetBlocksBlockPredicate, +} + +impl ReplaceableBlockPredicate { + pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool { + let state = self.offset.get_state(chunk, pos); + state.replaceable() + } +} + +#[derive(Deserialize)] +pub struct OffsetBlocksBlockPredicate { + offset: Option>, +} + +impl OffsetBlocksBlockPredicate { + pub fn get(&self, pos: &BlockPos) -> BlockPos { + if let Some(offset) = self.offset { + return pos.offset(offset); + } + *pos + } + pub fn get_block(&self, chunk: &ProtoChunk, pos: &BlockPos) -> Block { + let pos = self.get(pos); + chunk.get_block_state(&pos.0).to_block() + } + pub fn get_state(&self, chunk: &ProtoChunk, pos: &BlockPos) -> BlockState { + let pos = self.get(pos); + chunk.get_block_state(&pos.0).to_state() + } +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum MatchingBlocksWrapper { + Single(String), + Multiple(Vec), +} diff --git a/pumpkin-world/src/generation/block_state_provider.rs b/pumpkin-world/src/generation/block_state_provider.rs new file mode 100644 index 000000000..ce8161b4e --- /dev/null +++ b/pumpkin-world/src/generation/block_state_provider.rs @@ -0,0 +1,228 @@ +use pumpkin_data::{BlockState, chunk::DoublePerlinNoiseParameters}; +use pumpkin_util::{ + DoublePerlinNoiseParametersCodec, + math::{ + clamped_map, + int_provider::IntProvider, + pool::{Pool, Weighted}, + position::BlockPos, + vector3::Vector3, + }, + random::{RandomGenerator, RandomImpl, legacy_rand::LegacyRand}, +}; +use serde::Deserialize; + +use crate::block::BlockStateCodec; + +use super::noise::perlin::DoublePerlinNoiseSampler; + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum BlockStateProvider { + #[serde(rename = "minecraft:simple_state_provider")] + Simple(SimpleStateProvider), + #[serde(rename = "minecraft:weighted_state_provider")] + Weighted(WeightedBlockStateProvider), + #[serde(rename = "minecraft:noise_threshold_provider")] + NoiseThreshold(NoiseThresholdBlockStateProvider), + #[serde(rename = "minecraft:noise_provider")] + NoiseProvider(NoiseBlockStateProvider), + #[serde(rename = "minecraft:dual_noise_provider")] + DualNoise(DualNoiseBlockStateProvider), + #[serde(rename = "minecraft:rotated_block_provider")] + Pillar(PillarBlockStateProvider), + #[serde(rename = "minecraft:randomized_int_state_provider")] + RandomizedInt(RandomizedIntBlockStateProvider), +} + +impl BlockStateProvider { + pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState { + match self { + BlockStateProvider::NoiseThreshold(provider) => provider.get(random, pos), + BlockStateProvider::NoiseProvider(provider) => provider.get(pos), + BlockStateProvider::Simple(provider) => provider.get(pos), + BlockStateProvider::Weighted(provider) => provider.get(random), + BlockStateProvider::DualNoise(provider) => provider.get(pos), + BlockStateProvider::Pillar(provider) => provider.get(pos), + BlockStateProvider::RandomizedInt(provider) => provider.get(random, pos), + } + } +} + +#[derive(Deserialize)] +pub struct RandomizedIntBlockStateProvider { + source: Box, + property: String, + values: IntProvider, +} + +impl RandomizedIntBlockStateProvider { + pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState { + // TODO + self.source.get(random, pos) + } +} + +#[derive(Deserialize)] +pub struct PillarBlockStateProvider { + state: BlockStateCodec, +} + +impl PillarBlockStateProvider { + pub fn get(&self, _pos: BlockPos) -> BlockState { + // TODO: random axis + self.state.get_state().unwrap() + } +} + +#[derive(Deserialize)] +pub struct DualNoiseBlockStateProvider { + #[serde(flatten)] + base: NoiseBlockStateProvider, + variety: [u32; 2], + slow_noise: DoublePerlinNoiseParametersCodec, + slow_scale: f32, +} + +impl DualNoiseBlockStateProvider { + pub fn get(&self, pos: BlockPos) -> BlockState { + let noise = perlin_codec_to_static(self.slow_noise.clone()); + let sampler = DoublePerlinNoiseSampler::new( + &mut RandomGenerator::Legacy(LegacyRand::from_seed(self.base.base.seed as u64)), + &noise, + false, + ); + let slow_noise = self.get_slow_noise(&pos, &sampler); + let mapped = clamped_map( + slow_noise, + -1.0, + 1.0, + self.variety[0] as f64, + self.variety[1] as f64 + 1.0, + ) as i32; + let mut list = Vec::with_capacity(mapped as usize); + for i in 0..mapped { + let value = self.get_slow_noise( + &BlockPos(pos.0.add(&Vector3::new(i * 54545, 0, i * 34234))), + &sampler, + ); + list.push(self.base.get_state_by_value(&self.base.states, value)); + } + let value = self.base.base.get_noise(pos); + self.base + .get_state_by_value(&list, value) + .get_state() + .unwrap() + } + + fn get_slow_noise(&self, pos: &BlockPos, sampler: &DoublePerlinNoiseSampler) -> f64 { + sampler.sample( + pos.0.x as f64 * self.slow_scale as f64, + pos.0.y as f64 * self.slow_scale as f64, + pos.0.z as f64 * self.slow_scale as f64, + ) + } +} + +#[derive(Deserialize)] +pub struct WeightedBlockStateProvider { + entries: Vec>, +} + +impl WeightedBlockStateProvider { + pub fn get(&self, random: &mut RandomGenerator) -> BlockState { + Pool.get(&self.entries, random) + .unwrap() + .get_state() + .unwrap() + } +} + +#[derive(Deserialize)] +pub struct SimpleStateProvider { + state: BlockStateCodec, +} + +impl SimpleStateProvider { + pub fn get(&self, _pos: BlockPos) -> BlockState { + self.state.get_state().unwrap() + } +} + +#[derive(Deserialize)] +pub struct NoiseBlockStateProviderBase { + seed: i64, + noise: DoublePerlinNoiseParametersCodec, + scale: f32, +} + +fn perlin_codec_to_static(noise: DoublePerlinNoiseParametersCodec) -> DoublePerlinNoiseParameters { + let amplitudes_static: &'static [f64] = noise.amplitudes.leak(); + DoublePerlinNoiseParameters::new(noise.first_octave, amplitudes_static, "none") +} + +impl NoiseBlockStateProviderBase { + pub fn get_noise(&self, pos: BlockPos) -> f64 { + let noise = perlin_codec_to_static(self.noise.clone()); + let sampler = DoublePerlinNoiseSampler::new( + &mut RandomGenerator::Legacy(LegacyRand::from_seed(self.seed as u64)), + &noise, + false, + ); + sampler.sample( + pos.0.x as f64 * self.scale as f64, + pos.0.y as f64 * self.scale as f64, + pos.0.z as f64 * self.scale as f64, + ) + } +} + +#[derive(Deserialize)] +pub struct NoiseBlockStateProvider { + #[serde(flatten)] + base: NoiseBlockStateProviderBase, + states: Vec, +} + +impl NoiseBlockStateProvider { + pub fn get(&self, pos: BlockPos) -> BlockState { + let value = self.base.get_noise(pos); + self.get_state_by_value(&self.states, value) + .get_state() + .unwrap() + } + + fn get_state_by_value(&self, states: &[BlockStateCodec], value: f64) -> BlockStateCodec { + let val = ((1.0 + value) / 2.0).clamp(0.0, 0.9999); + states[(val * states.len() as f64) as usize].clone() + } +} + +#[derive(Deserialize)] +pub struct NoiseThresholdBlockStateProvider { + #[serde(flatten)] + base: NoiseBlockStateProviderBase, + threshold: f32, + high_chance: f32, + default_state: BlockStateCodec, + low_states: Vec, + high_states: Vec, +} + +impl NoiseThresholdBlockStateProvider { + pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState { + let value = self.base.get_noise(pos); + if value < self.threshold as f64 { + return self.low_states[random.next_bounded_i32(self.low_states.len() as i32) as usize] + .get_state() + .unwrap(); + } + if random.next_f32() < self.high_chance { + return self.high_states + [random.next_bounded_i32(self.high_states.len() as i32) as usize] + .get_state() + .unwrap(); + } + self.default_state.get_state().unwrap() + } +} diff --git a/pumpkin-world/src/generation/feature/configured_features.rs b/pumpkin-world/src/generation/feature/configured_features.rs new file mode 100644 index 000000000..3f712e229 --- /dev/null +++ b/pumpkin-world/src/generation/feature/configured_features.rs @@ -0,0 +1,385 @@ +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; + +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, level::Level, world::BlockRegistryExt}; + +use super::features::{ + bamboo::BambooFeature, + basalt_columns::BasaltColumnsFeature, + basalt_pillar::BasaltPillarFeature, + block_column::BlockColumnFeature, + block_pile::BlockPileFeature, + blue_ice::BlueIceFeature, + bonus_chest::BonusChestFeature, + chorus_plant::ChorusPlantFeature, + coral::{ + coral_claw::CoralClawFeature, coral_mushroom::CoralMushroomFeature, + coral_tree::CoralTreeFeature, + }, + delta_feature::DeltaFeatureFeature, + desert_well::DesertWellFeature, + disk::DiskFeature, + drip_stone::{ + cluster::DripstoneClusterFeature, large::LargeDripstoneFeature, + small::SmallDripstoneFeature, + }, + end_gateway::EndGatewayFeature, + end_island::EndIslandFeature, + end_platform::EndPlatformFeature, + end_spike::EndSpikeFeature, + fallen_tree::FallenTreeFeature, + fill_layer::FillLayerFeature, + forest_rock::ForestRockFeature, + fossil::FossilFeature, + freeze_top_layer::FreezeTopLayerFeature, + geode::GeodeFeature, + glowstone_blob::GlowstoneBlobFeature, + huge_brown_mushroom::HugeBrownMushroomFeature, + huge_fungus::HugeFungusFeature, + huge_red_mushroom::HugeRedMushroomFeature, + ice_spike::IceSpikeFeature, + iceberg::IcebergFeature, + kelp::KelpFeature, + lake::LakeFeature, + monster_room::DungeonFeature, + multiface_growth::MultifaceGrowthFeature, + nether_forest_vegetation::NetherForestVegetationFeature, + netherrack_replace_blobs::ReplaceBlobsFeature, + ore::OreFeature, + random_boolean_selector::RandomBooleanFeature, + random_patch::RandomPatchFeature, + random_selector::RandomFeature, + replace_single_block::ReplaceSingleBlockFeature, + root_system::RootSystemFeature, + scattered_ore::ScatteredOreFeature, + sculk_patch::SculkPatchFeature, + sea_pickle::SeaPickleFeature, + seagrass::SeagrassFeature, + simple_block::SimpleBlockFeature, + simple_random_selector::SimpleRandomFeature, + spring_feature::SpringFeatureFeature, + tree::TreeFeature, + twisting_vines::TwistingVinesFeature, + underwater_magma::UnderwaterMagmaFeature, + vegetation_patch::VegetationPatchFeature, + vines::VinesFeature, + void_start_platform::VoidStartPlatformFeature, + waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature, + weeping_vines::WeepingVinesFeature, +}; + +pub static CONFIGURED_FEATURES: LazyLock> = + LazyLock::new(|| { + serde_json::from_str(include_str!("../../../../assets/configured_features.json")) + .expect("Could not parse configured_features.json registry.") + }); + +// Yes this may look ugly and you wonder why this is hard coded, but its makes sense to hardcode since we have to add logic for these in code +#[derive(Deserialize)] +#[serde(tag = "type", content = "config")] +pub enum ConfiguredFeature { + #[serde(rename = "minecraft:no_op")] + NoOp, + #[serde(rename = "minecraft:tree")] + Tree(Box), + #[serde(rename = "minecraft:fallen_tree")] + FallenTree(FallenTreeFeature), + #[serde(rename = "minecraft:flower")] + Flower(RandomPatchFeature), + #[serde(rename = "minecraft:no_bonemeal_flower")] + NoBonemealFlower(RandomPatchFeature), + #[serde(rename = "minecraft:random_patch")] + RandomPatch(RandomPatchFeature), + #[serde(rename = "minecraft:block_pile")] + BlockPile(BlockPileFeature), + #[serde(rename = "minecraft:spring_feature")] + SpringFeature(SpringFeatureFeature), + #[serde(rename = "minecraft:chorus_plant")] + ChorusPlant(ChorusPlantFeature), + #[serde(rename = "minecraft:replace_single_block")] + ReplaceSingleBlock(ReplaceSingleBlockFeature), + #[serde(rename = "minecraft:void_start_platform")] + VoidStartPlatform(VoidStartPlatformFeature), + #[serde(rename = "minecraft:desert_well")] + DesertWell(DesertWellFeature), + #[serde(rename = "minecraft:fossil")] + Fossil(FossilFeature), + #[serde(rename = "minecraft:huge_red_mushroom")] + HugeRedMushroom(HugeRedMushroomFeature), + #[serde(rename = "minecraft:huge_brown_mushroom")] + HugeBrownMushroom(HugeBrownMushroomFeature), + #[serde(rename = "minecraft:ice_spike")] + IceSpike(IceSpikeFeature), + #[serde(rename = "minecraft:glowstone_blob")] + GlowstoneBlob(GlowstoneBlobFeature), + #[serde(rename = "minecraft:freeze_top_layer")] + FreezeTopLayer(FreezeTopLayerFeature), + #[serde(rename = "minecraft:vines")] + Vines(VinesFeature), + #[serde(rename = "minecraft:block_column")] + BlockColumn(BlockColumnFeature), + #[serde(rename = "minecraft:vegetation_patch")] + VegetationPatch(VegetationPatchFeature), + #[serde(rename = "minecraft:waterlogged_vegetation_patch")] + WaterloggedVegetationPatch(WaterloggedVegetationPatchFeature), + #[serde(rename = "minecraft:root_system")] + RootSystem(RootSystemFeature), + #[serde(rename = "minecraft:multiface_growth")] + MultifaceGrowth(MultifaceGrowthFeature), + #[serde(rename = "minecraft:underwater_magma")] + UnderwaterMagma(UnderwaterMagmaFeature), + #[serde(rename = "minecraft:monster_room")] + MonsterRoom(DungeonFeature), + #[serde(rename = "minecraft:blue_ice")] + BlueIce(BlueIceFeature), + #[serde(rename = "minecraft:iceberg")] + Iceberg(IcebergFeature), + #[serde(rename = "minecraft:forest_rock")] + ForestRock(ForestRockFeature), + #[serde(rename = "minecraft:disk")] + Disk(DiskFeature), + #[serde(rename = "minecraft:lake")] + Lake(LakeFeature), + #[serde(rename = "minecraft:ore")] + Ore(OreFeature), + #[serde(rename = "minecraft:end_platform")] + EndPlatform(EndPlatformFeature), + #[serde(rename = "minecraft:end_spike")] + EndSpike(EndSpikeFeature), + #[serde(rename = "minecraft:end_island")] + EndIsland(EndIslandFeature), + #[serde(rename = "minecraft:end_gateway")] + EndGateway(EndGatewayFeature), + #[serde(rename = "minecraft:seagrass")] + Seagrass(SeagrassFeature), + #[serde(rename = "minecraft:kelp")] + Kelp(KelpFeature), + #[serde(rename = "minecraft:coral_tree")] + CoralTree(CoralTreeFeature), + #[serde(rename = "minecraft:coral_mushroom")] + CoralMushroom(CoralMushroomFeature), + #[serde(rename = "minecraft:coral_claw")] + CoralClaw(CoralClawFeature), + #[serde(rename = "minecraft:sea_pickle")] + SeaPickle(SeaPickleFeature), + #[serde(rename = "minecraft:simple_block")] + SimpleBlock(SimpleBlockFeature), + #[serde(rename = "minecraft:bamboo")] + Bamboo(BambooFeature), + #[serde(rename = "minecraft:huge_fungus")] + HugeFungus(HugeFungusFeature), + #[serde(rename = "minecraft:nether_forest_vegetation")] + NetherForestVegetation(NetherForestVegetationFeature), + #[serde(rename = "minecraft:weeping_vines")] + WeepingVines(WeepingVinesFeature), + #[serde(rename = "minecraft:twisting_vines")] + TwistingVines(TwistingVinesFeature), + #[serde(rename = "minecraft:basalt_columns")] + BasaltColumns(BasaltColumnsFeature), + #[serde(rename = "minecraft:delta_feature")] + DeltaFeature(DeltaFeatureFeature), + #[serde(rename = "minecraft:netherrack_replace_blobs")] + NetherrackReplaceBlobs(ReplaceBlobsFeature), + #[serde(rename = "minecraft:fill_layer")] + FillLayer(FillLayerFeature), + #[serde(rename = "minecraft:bonus_chest")] + BonusChest(BonusChestFeature), + #[serde(rename = "minecraft:basalt_pillar")] + BasaltPillar(BasaltPillarFeature), + #[serde(rename = "minecraft:scattered_ore")] + ScatteredOre(ScatteredOreFeature), + #[serde(rename = "minecraft:random_selector")] + RandomSelector(RandomFeature), + #[serde(rename = "minecraft:simple_random_selector")] + SimpleRandomSelector(SimpleRandomFeature), + #[serde(rename = "minecraft:random_boolean_selector")] + RandomBooleanSelector(RandomBooleanFeature), + #[serde(rename = "minecraft:geode")] + Geode(GeodeFeature), + #[serde(rename = "minecraft:dripstone_cluster")] + DripstoneCluster(DripstoneClusterFeature), + #[serde(rename = "minecraft:large_dripstone")] + LargeDripstone(LargeDripstoneFeature), + #[serde(rename = "minecraft:pointed_dripstone")] + PointedDripstone(SmallDripstoneFeature), + #[serde(rename = "minecraft:sculk_patch")] + SculkPatch(SculkPatchFeature), +} + +impl ConfiguredFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + match self { + Self::PointedDripstone(feature) => feature.generate(chunk, random, pos), + Self::CoralMushroom(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::CoralTree(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::CoralClaw(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::SpringFeature(feature) => feature.generate(block_registry, chunk, random, pos), + Self::SimpleBlock(feature) => feature.generate(block_registry, chunk, random, pos), + Self::Flower(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::NoBonemealFlower(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::DesertWell(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::Bamboo(feature) => { + feature + .generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::BlockColumn(feature) => { + feature + .generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::RandomPatch(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::RandomBooleanSelector(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::Tree(feature) => { + feature + .generate(chunk, level, min_y, height, feature_name, random, pos) + .await + } + Self::RandomSelector(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::SimpleRandomSelector(feature) => { + feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + } + Self::Vines(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), + Self::Seagrass(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::SeaPickle(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) + } + Self::Ore(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), + _ => false, // TODO + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/bamboo.rs b/pumpkin-world/src/generation/feature/features/bamboo.rs new file mode 100644 index 000000000..3025bf0c8 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/bamboo.rs @@ -0,0 +1,98 @@ +use pumpkin_data::{ + Block, BlockDirection, + block_properties::{ + BambooLeaves, BambooLikeProperties, BlockProperties, Integer0To1, get_state_by_state_id, + }, + tag::Tagable, +}; +use pumpkin_util::{ + math::{position::BlockPos, vector2::Vector2}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, world::BlockRegistryExt}; + +#[derive(Deserialize)] +pub struct BambooFeature { + probability: f32, +} + +impl BambooFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut i = 0; + if chunk.is_air(&pos.0) { + if block_registry + .can_place_at(&Block::BAMBOO, chunk, &pos, BlockDirection::Up) + .await + { + let height = random.next_bounded_i32(12) + 5; + if random.next_f32() < self.probability { + let rnd = random.next_bounded_i32(4) + 1; + for x in pos.0.x - rnd..pos.0.x + rnd { + for z in pos.0.z - rnd..pos.0.z + rnd { + let block_below = BlockPos::new( + x, + chunk.top_block_height_exclusive(&Vector2::new(x, z)) as i32 - 1, + z, + ); + let block = chunk.get_block_state(&block_below.0); + if !block.to_block().is_tagged_with("minecraft:dirt").unwrap() { + continue; + } + chunk.set_block_state( + &block_below.0, + &get_state_by_state_id(Block::PODZOL.id).unwrap(), + ); + } + } + } + let mut bpos = pos; + let bamboo = get_state_by_state_id(Block::BAMBOO.default_state_id).unwrap(); + for _ in 0..height { + if chunk.is_air(&bpos.0) { + chunk.set_block_state(&bpos.0, &bamboo); + bpos = bpos.up(); + } else { + break; + } + } + // Top block + if bpos.0.y - pos.0.y >= 3 { + let mut props = BambooLikeProperties::default(&Block::BAMBOO); + props.leaves = BambooLeaves::Large; + props.stage = Integer0To1::L1; + + chunk.set_block_state( + &bpos.0, + &get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(), + ); + props.stage = Integer0To1::L0; + + chunk.set_block_state( + &bpos.down().0, + &get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(), + ); + props.leaves = BambooLeaves::Small; + + chunk.set_block_state( + &bpos.down().down().0, + &get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(), + ); + } + } + i += 1; + } + i > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/basalt_columns.rs b/pumpkin-world/src/generation/feature/features/basalt_columns.rs new file mode 100644 index 000000000..723e52a0f --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/basalt_columns.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BasaltColumnsFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/basalt_pillar.rs b/pumpkin-world/src/generation/feature/features/basalt_pillar.rs new file mode 100644 index 000000000..7c58fa659 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/basalt_pillar.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BasaltPillarFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/block_column.rs b/pumpkin-world/src/generation/feature/features/block_column.rs new file mode 100644 index 000000000..fd6f6745a --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/block_column.rs @@ -0,0 +1,114 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::RandomGenerator, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::{block_predicate::BlockPredicate, block_state_provider::BlockStateProvider}, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct BlockColumnFeature { + layers: Vec, + direction: BlockDirection, + allowed_placement: BlockPredicate, + prioritize_tip: bool, +} + +#[derive(Deserialize)] +struct Layer { + height: IntProvider, + provider: BlockStateProvider, +} + +impl BlockColumnFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let i = self.layers.len(); + let mut is = vec![0; i]; + let mut j = 0; + + for (k, item) in is.iter_mut().enumerate().take(i) { + *item = (self.layers[k].height).get(random); + j += *item; + } + + if j == 0 { + return false; + } + + let mut mutable = pos; + let mut mutable2 = mutable.offset(self.direction.to_offset()); + + let mut l = 0; + while l < j { + if !Box::pin( + self.allowed_placement + .test(block_registry, chunk, &mutable2), + ) + .await + { + Self::adjust_layer_heights(&mut is, j, l, self.prioritize_tip); + break; + } + mutable2 = mutable2.offset(self.direction.to_offset()); + l += 1; + } + + for (l, m) in is.iter().enumerate().take(i) { + if *m == 0 { + continue; + } + let layer = &self.layers[l]; + for _n in 0..*m { + let state = layer.provider.get(random, mutable); + chunk.set_block_state(&mutable.0, &state); + mutable = mutable.offset(self.direction.to_offset()); + } + } + + true + } + + fn adjust_layer_heights( + layer_heights: &mut [i32], + expected_height: i32, + actual_height: i32, + prioritize_tip: bool, + ) { + let mut i = expected_height - actual_height; + let j = if prioritize_tip { 1 } else { -1 }; + let k = if prioritize_tip { + 0 + } else { + layer_heights.len() as i32 - 1 + }; + let l = if prioritize_tip { + layer_heights.len() as i32 + } else { + -1 + }; + + let mut m = k; + while m != l && i > 0 { + let n = layer_heights[m as usize]; + let o = std::cmp::min(n, i); + layer_heights[m as usize] -= o; + i -= o; + m += j; + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/block_pile.rs b/pumpkin-world/src/generation/feature/features/block_pile.rs new file mode 100644 index 000000000..7601aad99 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/block_pile.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BlockPileFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/blue_ice.rs b/pumpkin-world/src/generation/feature/features/blue_ice.rs new file mode 100644 index 000000000..03649a38b --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/blue_ice.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BlueIceFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/bonus_chest.rs b/pumpkin-world/src/generation/feature/features/bonus_chest.rs new file mode 100644 index 000000000..8b44fa6a7 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/bonus_chest.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BonusChestFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/chorus_plant.rs b/pumpkin-world/src/generation/feature/features/chorus_plant.rs new file mode 100644 index 000000000..ba070864d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/chorus_plant.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ChorusPlantFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs b/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs new file mode 100644 index 000000000..ed53a137b --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs @@ -0,0 +1,78 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +use super::CoralFeature; + +#[derive(Deserialize)] +pub struct CoralClawFeature; + +impl CoralClawFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // First lets get a random coral + let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random); + if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) { + return false; + } + let i = random.next_bounded_i32(2) + 2; + let direction = BlockDirection::horizontal() + [random.next_bounded_i32(BlockDirection::horizontal().len() as i32 - 1) as usize]; + // TODO: Shuffle + let directions: Vec<_> = BlockDirection::horizontal() + .into_iter() + .take(i as usize) + .collect(); + 'block0: for direction2 in directions { + let mut pos = pos; + let j = random.next_bounded_i32(2) + 1; + pos = pos.offset(direction2.to_offset()); + + let direction3; + let k; + + if direction2 == direction { + direction3 = direction; + k = random.next_bounded_i32(3) + 2; + } else { + pos = pos.up(); + let _directions = [direction2, BlockDirection::Up]; + direction3 = direction2; // TODO: make this random + k = random.next_bounded_i32(3) + 5; + } + + for _ in 0..j { + if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) { + break; + } + pos = pos.offset(direction3.to_offset()); + } + + pos = pos.offset(direction3.to_offset()); + pos = pos.up(); + + for _l in 0..k { + pos = pos.offset(direction.opposite().to_offset()); + if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) { + continue 'block0; + } + if random.next_f32() < 0.25 { + pos = pos.up(); + } + } + } + true + } +} diff --git a/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs b/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs new file mode 100644 index 000000000..f16f40d3c --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs @@ -0,0 +1,56 @@ +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +use super::CoralFeature; + +#[derive(Deserialize)] +pub struct CoralMushroomFeature; + +impl CoralMushroomFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // First lets get a random coral + let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random); + + let i = random.next_bounded_i32(3) + 3; + let j = random.next_bounded_i32(3) + 3; + let k = random.next_bounded_i32(3) + 3; + let l = random.next_bounded_i32(3) + 1; + + for m in 0..=j { + for n in 0..=i { + for o in 0..=k { + let mut pos = pos; + pos = pos.offset(Vector3::new(pos.0.x + m, pos.0.y + n, pos.0.z + o)); + pos = pos.down_height(l); + + let condition_a = (m != 0 && m != j) || (n != 0 && n != i); + let condition_b = (o != 0 && o != k) || (n != 0 && n != i); + let condition_c = (m != 0 && m != j) || (o != 0 && o != k); + let condition_d = m == 0 || m == j || n == 0 || n == i || o == 0 || o == k; + let random_check = random.next_f32() < 0.1f32; + + if !((condition_a && condition_b && condition_c && condition_d) + && !random_check + && CoralFeature::generate_coral_piece(chunk, random, &block, pos)) + { + continue; + } + } + } + } + true + } +} diff --git a/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs b/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs new file mode 100644 index 000000000..e5e91e05b --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs @@ -0,0 +1,61 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +use super::CoralFeature; + +#[derive(Deserialize)] +pub struct CoralTreeFeature; + +impl CoralTreeFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // First lets get a random coral + let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random); + let mut pos = pos; + let i = random.next_bounded_i32(3) + 1; + for _ in 0..i { + if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) { + return true; + } + pos = pos.up(); + } + let i = random.next_bounded_i32(3) + 2; + + // TODO: Shuffle + let directions: Vec<_> = BlockDirection::horizontal() + .into_iter() + .take(i as usize) + .collect(); + for dir in directions { + pos = pos.offset(dir.to_offset()); + let times = random.next_bounded_i32(5) + 2; + let mut m = 0; + for n in 0..times { + if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) { + break; + } + pos = pos.up(); + m += 1; + if n != 0 && (m < 2 || random.next_f32() >= 0.25) { + continue; + } + pos = pos.offset(dir.to_offset()); + m = 0; + } + } + true + } +} diff --git a/pumpkin-world/src/generation/feature/features/coral/mod.rs b/pumpkin-world/src/generation/feature/features/coral/mod.rs new file mode 100644 index 000000000..b09a5c2eb --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/coral/mod.rs @@ -0,0 +1,100 @@ +use pumpkin_data::{ + Block, BlockDirection, BlockState, + block_properties::{ + BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties, get_block, + get_state_by_state_id, + }, + tag::{RegistryKey, Tagable, get_tag_values}, +}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::ProtoChunk; + +pub mod coral_claw; +pub mod coral_mushroom; +pub mod coral_tree; + +pub struct CoralFeature; + +impl CoralFeature { + pub fn generate_coral_piece( + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + state: &BlockState, + pos: BlockPos, + ) -> bool { + let block = chunk.get_block_state(&pos.0).to_block(); + let above_block = chunk.get_block_state(&pos.up().0).to_block(); + + if block != Block::WATER && !block.is_tagged_with("minecraft:corals").unwrap() + || above_block != Block::WATER + { + return false; + } + chunk.set_block_state(&pos.0, state); + if random.next_f32() < 0.25 { + chunk.set_block_state( + &pos.0, + &Self::get_random_tag_entry("minecraft:corals", random), + ); + } else if random.next_f32() < 0.05 { + let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); + props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(props.to_state_id(&Block::SEA_PICKLE)).unwrap(), + ); + } + for dir in BlockDirection::horizontal() { + let dir_pos = pos.offset(dir.to_offset()); + if random.next_f32() >= 0.2 + || chunk.get_block_state(&dir_pos.0).to_block() != Block::WATER + { + continue; + } + let wall_coral = Self::get_random_tag_entry_block("minecraft:wall_corals", random); + let original_props = &wall_coral + .properties(wall_coral.default_state_id) + .unwrap() + .to_props(); + let facing = dir.to_facing(); + // Set the right Axis + let props_vec: Vec<(&str, &str)> = original_props + .iter() + .map(|(key, value)| { + if key == "facing" { + (key.as_str(), facing.to_value()) + } else { + (key.as_str(), value.as_str()) + } + }) + .collect(); + chunk.set_block_state( + &dir_pos.0, + &get_state_by_state_id( + wall_coral + .from_properties(props_vec) + .unwrap() + .to_state_id(&wall_coral), + ) + .unwrap(), + ); + } + + true + } + + pub fn get_random_tag_entry(tag: &str, random: &mut RandomGenerator) -> BlockState { + let block = Self::get_random_tag_entry_block(tag, random); + get_state_by_state_id(block.default_state_id).unwrap() + } + + pub fn get_random_tag_entry_block(tag: &str, random: &mut RandomGenerator) -> Block { + let values = get_tag_values(RegistryKey::Block, tag).unwrap(); + let value = values[random.next_bounded_i32(values.len() as i32) as usize]; + get_block(value).unwrap() + } +} diff --git a/pumpkin-world/src/generation/feature/features/delta_feature.rs b/pumpkin-world/src/generation/feature/features/delta_feature.rs new file mode 100644 index 000000000..bfd889161 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/delta_feature.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct DeltaFeatureFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/desert_well.rs b/pumpkin-world/src/generation/feature/features/desert_well.rs new file mode 100644 index 000000000..16c23f783 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/desert_well.rs @@ -0,0 +1,152 @@ +use pumpkin_data::BlockDirection; +use pumpkin_macros::default_block_state; +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::RandomGenerator, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + block::RawBlockState, + generation::{chunk_noise::WATER_BLOCK, height_limit::HeightLimitView}, +}; + +// TODO: remove .to_state() + +#[derive(Deserialize)] +pub struct DesertWellFeature; + +impl DesertWellFeature { + const CAN_GENERATE: RawBlockState = default_block_state!("sand"); + const SAND: RawBlockState = default_block_state!("sand"); + const SLAB: RawBlockState = default_block_state!("sandstone_slab"); + const WALL: RawBlockState = default_block_state!("sandstone"); + + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut block_pos = pos.up(); + while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 { + block_pos = block_pos.down(); + } + let block = chunk.get_block_state(&pos.0).to_block(); + const CAN_GENERATE: RawBlockState = default_block_state!("sand"); + if CAN_GENERATE.to_block().id != block.id { + return false; + } + + for i in -2..=2 { + for j2 in -2..=2 { + if !chunk.is_air(&block_pos.0.add(&Vector3::new(i, -1, j2))) + || !chunk.is_air(&block_pos.0.add(&Vector3::new(i, -2, j2))) + { + continue; + } + return false; + } + } + + for i in -2..=0 { + for j2 in -2..=2 { + for k in -2..=2 { + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(j2, i, k)), + &Self::WALL.to_state(), + ); + } + } + } + + chunk.set_block_state(&block_pos.0, &WATER_BLOCK.to_state()); + + for direction in BlockDirection::horizontal().iter() { + chunk.set_block_state( + &block_pos.0.add(&direction.to_offset()), + &WATER_BLOCK.to_state(), + ); + } + + let block_pos2 = &block_pos.0.add(&Vector3::new(0, -1, 0)); + chunk.set_block_state(block_pos2, &Self::SAND.to_state()); + + for direction2 in BlockDirection::horizontal().iter() { + chunk.set_block_state( + &block_pos2.add(&direction2.to_offset()), + &Self::SAND.to_state(), + ); + } + + for j in -2..=2 { + for k in -2..=2 { + if j != -2 && j != 2 && k != -2 && k != 2 { + continue; + } + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(j, 1, k)), + &Self::WALL.to_state(), + ); + } + } + + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(2, 1, 0)), + &Self::SLAB.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(-2, 1, 0)), + &Self::SLAB.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(0, 1, 2)), + &Self::SLAB.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(0, 1, -2)), + &Self::SLAB.to_state(), + ); + + for j in -1..=1 { + for k in -1..=1 { + if j == 0 && k == 0 { + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(j, 4, k)), + &Self::WALL.to_state(), + ); + continue; + } + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(j, 4, k)), + &Self::SLAB.to_state(), + ); + } + } + + for j in 1..=3 { + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(-1, j, -1)), + &Self::WALL.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(-1, j, 1)), + &Self::WALL.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(1, j, -1)), + &Self::WALL.to_state(), + ); + chunk.set_block_state( + &block_pos.0.add(&Vector3::new(1, j, 1)), + &Self::WALL.to_state(), + ); + } + + true + } +} diff --git a/pumpkin-world/src/generation/feature/features/disk.rs b/pumpkin-world/src/generation/feature/features/disk.rs new file mode 100644 index 000000000..da6c084ad --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/disk.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct DiskFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/drip_stone/cluster.rs b/pumpkin-world/src/generation/feature/features/drip_stone/cluster.rs new file mode 100644 index 000000000..f53487542 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/drip_stone/cluster.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct DripstoneClusterFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/drip_stone/large.rs b/pumpkin-world/src/generation/feature/features/drip_stone/large.rs new file mode 100644 index 000000000..39398a197 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/drip_stone/large.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct LargeDripstoneFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs b/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs new file mode 100644 index 000000000..4709a245e --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs @@ -0,0 +1,30 @@ +use pumpkin_data::{Block, block_properties::get_state_by_state_id, tag::Tagable}; +use pumpkin_util::math::position::BlockPos; + +use crate::ProtoChunk; + +pub mod cluster; +pub mod large; +pub mod small; + +pub(super) fn can_replace(block: &Block) -> bool { + block == &Block::DRIPSTONE_BLOCK + || block + .is_tagged_with("minecraft:dripstone_replaceable_blocks") + .unwrap() +} + +pub(super) fn gen_dripstone(chunk: &mut ProtoChunk, pos: BlockPos) -> bool { + let block = chunk.get_block_state(&pos.0).to_block(); + if block + .is_tagged_with("minecraft:dripstone_replaceable_blocks") + .unwrap() + { + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(Block::DRIPSTONE_BLOCK.default_state_id).unwrap(), + ); + return true; + } + false +} diff --git a/pumpkin-world/src/generation/feature/features/drip_stone/small.rs b/pumpkin-world/src/generation/feature/features/drip_stone/small.rs new file mode 100644 index 000000000..ec1875726 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/drip_stone/small.rs @@ -0,0 +1,82 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +#[derive(Deserialize)] +pub struct SmallDripstoneFeature { + chance_of_taller_dripstone: f32, + chance_of_directional_spread: f32, + chance_of_spread_radius2: f32, + chance_of_spread_radius3: f32, +} + +impl SmallDripstoneFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + if let Some(dir) = Self::get_direction(chunk, pos, random) { + let pos = pos.offset(dir.opposite().to_offset()); + self.gen_dripstone_blocks(chunk, pos, random); + // TODO + return true; + } + false + } + + fn get_direction( + chunk: &mut ProtoChunk, + pos: BlockPos, + random: &mut RandomGenerator, + ) -> Option { + let up = super::can_replace(&chunk.get_block_state(&pos.up().0).to_block()); + let down: bool = super::can_replace(&chunk.get_block_state(&pos.down().0).to_block()); + if up && down { + return if random.next_bool() { + Some(BlockDirection::Down) + } else { + Some(BlockDirection::Up) + }; + } + if up { + return Some(BlockDirection::Down); + } + if down { + return Some(BlockDirection::Up); + } + None + } + + fn gen_dripstone_blocks( + &self, + chunk: &mut ProtoChunk, + pos: BlockPos, + random: &mut RandomGenerator, + ) { + super::gen_dripstone(chunk, pos); + for dir in BlockDirection::horizontal() { + if random.next_f32() > self.chance_of_directional_spread { + continue; + } + let pos = pos.offset(dir.to_offset()); + super::gen_dripstone(chunk, pos); + if random.next_f32() > self.chance_of_spread_radius2 { + continue; + } + let pos = pos.offset(BlockDirection::random(random).to_offset()); + super::gen_dripstone(chunk, pos); + if random.next_f32() > self.chance_of_spread_radius3 { + continue; + } + let pos = pos.offset(BlockDirection::random(random).to_offset()); + super::gen_dripstone(chunk, pos); + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/end_gateway.rs b/pumpkin-world/src/generation/feature/features/end_gateway.rs new file mode 100644 index 000000000..f6646b7f6 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/end_gateway.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct EndGatewayFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/end_island.rs b/pumpkin-world/src/generation/feature/features/end_island.rs new file mode 100644 index 000000000..c71a3531d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/end_island.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct EndIslandFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/end_platform.rs b/pumpkin-world/src/generation/feature/features/end_platform.rs new file mode 100644 index 000000000..e41e14808 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/end_platform.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct EndPlatformFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/end_spike.rs b/pumpkin-world/src/generation/feature/features/end_spike.rs new file mode 100644 index 000000000..f1f01c9d7 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/end_spike.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct EndSpikeFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/fallen_tree.rs b/pumpkin-world/src/generation/feature/features/fallen_tree.rs new file mode 100644 index 000000000..b983ff926 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/fallen_tree.rs @@ -0,0 +1,27 @@ +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider}; + +#[derive(Deserialize)] +pub struct FallenTreeFeature { + trunk_provider: BlockStateProvider, +} + +impl FallenTreeFeature { + pub fn generate( + &self, + _chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + _random: &mut RandomGenerator, + _pos: BlockPos, + ) -> bool { + false + } + + fn gen_stump(&self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, pos: BlockPos) { + chunk.set_block_state(&pos.0, &self.trunk_provider.get(random, pos)); + } +} diff --git a/pumpkin-world/src/generation/feature/features/fill_layer.rs b/pumpkin-world/src/generation/feature/features/fill_layer.rs new file mode 100644 index 000000000..4d69ed610 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/fill_layer.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct FillLayerFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/forest_rock.rs b/pumpkin-world/src/generation/feature/features/forest_rock.rs new file mode 100644 index 000000000..8976c499f --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/forest_rock.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ForestRockFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/fossil.rs b/pumpkin-world/src/generation/feature/features/fossil.rs new file mode 100644 index 000000000..2efb9fdb4 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/fossil.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct FossilFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs b/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs new file mode 100644 index 000000000..495516955 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct FreezeTopLayerFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/geode.rs b/pumpkin-world/src/generation/feature/features/geode.rs new file mode 100644 index 000000000..f1767dc32 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/geode.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct GeodeFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/glowstone_blob.rs b/pumpkin-world/src/generation/feature/features/glowstone_blob.rs new file mode 100644 index 000000000..31f708031 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/glowstone_blob.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct GlowstoneBlobFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/huge_brown_mushroom.rs b/pumpkin-world/src/generation/feature/features/huge_brown_mushroom.rs new file mode 100644 index 000000000..859b59c84 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/huge_brown_mushroom.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct HugeBrownMushroomFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/huge_fungus.rs b/pumpkin-world/src/generation/feature/features/huge_fungus.rs new file mode 100644 index 000000000..d9df5ec1b --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/huge_fungus.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct HugeFungusFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/huge_red_mushroom.rs b/pumpkin-world/src/generation/feature/features/huge_red_mushroom.rs new file mode 100644 index 000000000..ea8e5aa3d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/huge_red_mushroom.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct HugeRedMushroomFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/ice_spike.rs b/pumpkin-world/src/generation/feature/features/ice_spike.rs new file mode 100644 index 000000000..d9953e122 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/ice_spike.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct IceSpikeFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/iceberg.rs b/pumpkin-world/src/generation/feature/features/iceberg.rs new file mode 100644 index 000000000..8fc482aec --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/iceberg.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct IcebergFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/kelp.rs b/pumpkin-world/src/generation/feature/features/kelp.rs new file mode 100644 index 000000000..430756c52 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/kelp.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct KelpFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/lake.rs b/pumpkin-world/src/generation/feature/features/lake.rs new file mode 100644 index 000000000..9d8442bba --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/lake.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct LakeFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/mod.rs b/pumpkin-world/src/generation/feature/features/mod.rs new file mode 100644 index 000000000..750309009 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/mod.rs @@ -0,0 +1,56 @@ +pub mod bamboo; +pub mod basalt_columns; +pub mod basalt_pillar; +pub mod block_column; +pub mod block_pile; +pub mod blue_ice; +pub mod bonus_chest; +pub mod chorus_plant; +pub mod coral; +pub mod delta_feature; +pub mod desert_well; +pub mod disk; +pub mod drip_stone; +pub mod end_gateway; +pub mod end_island; +pub mod end_platform; +pub mod end_spike; +pub mod fallen_tree; +pub mod fill_layer; +pub mod forest_rock; +pub mod fossil; +pub mod freeze_top_layer; +pub mod geode; +pub mod glowstone_blob; +pub mod huge_brown_mushroom; +pub mod huge_fungus; +pub mod huge_red_mushroom; +pub mod ice_spike; +pub mod iceberg; +pub mod kelp; +pub mod lake; +pub mod monster_room; +pub mod multiface_growth; +pub mod nether_forest_vegetation; +pub mod netherrack_replace_blobs; +pub mod ore; +pub mod random_boolean_selector; +pub mod random_patch; +pub mod random_selector; +pub mod replace_single_block; +pub mod root_system; +pub mod scattered_ore; +pub mod sculk_patch; +pub mod sea_pickle; +pub mod seagrass; +pub mod simple_block; +pub mod simple_random_selector; +pub mod spring_feature; +pub mod tree; +pub mod twisting_vines; +pub mod underwater_magma; +pub mod vegetation_patch; +pub mod vines; +pub mod void_start_platform; +pub mod waterlogged_vegetation_patch; +pub mod weeping_vines; diff --git a/pumpkin-world/src/generation/feature/features/monster_room.rs b/pumpkin-world/src/generation/feature/features/monster_room.rs new file mode 100644 index 000000000..6128172d6 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/monster_room.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct DungeonFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/multiface_growth.rs b/pumpkin-world/src/generation/feature/features/multiface_growth.rs new file mode 100644 index 000000000..f474166cd --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/multiface_growth.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct MultifaceGrowthFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/nether_forest_vegetation.rs b/pumpkin-world/src/generation/feature/features/nether_forest_vegetation.rs new file mode 100644 index 000000000..ba23ba5d3 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/nether_forest_vegetation.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct NetherForestVegetationFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/netherrack_replace_blobs.rs b/pumpkin-world/src/generation/feature/features/netherrack_replace_blobs.rs new file mode 100644 index 000000000..b92b22d3e --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/netherrack_replace_blobs.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ReplaceBlobsFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/ore.rs b/pumpkin-world/src/generation/feature/features/ore.rs new file mode 100644 index 000000000..a7169ab66 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/ore.rs @@ -0,0 +1,252 @@ +use core::f32; +use std::collections::HashSet; + +use pumpkin_data::{BlockDirection, BlockState}; +use pumpkin_util::{ + math::{lerp, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + block::BlockStateCodec, + generation::{height_limit::HeightLimitView, rule::RuleTest}, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct OreFeature { + size: i32, + discard_chance_on_air_exposure: f32, + targets: Vec, +} + +#[derive(Deserialize)] +struct OreTarget { + pub target: RuleTest, + pub state: BlockStateCodec, +} + +impl OreFeature { + #[expect(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let f = random.next_f32() * f32::consts::PI; + let g = self.size as f32 / 8.0f32; + let i = ((self.size as f32 / 16.0f32 * 2.0 + 1.0) / 2.0).ceil() as i32; + + let d = pos.0.x as f64 + f.sin() as f64 * g as f64; + let e = pos.0.x as f64 - f.sin() as f64 * g as f64; + let h = pos.0.z as f64 + f.cos() as f64 * g as f64; // Use f.cos() for Math.cos(f) + let j = pos.0.z as f64 - f.cos() as f64 * g as f64; + + let _k = 2; // This variable 'k' from Java seems unused + let l = pos.0.y as f64 + random.next_bounded_i32(3) as f64 - 2.0; + let m = pos.0.y as f64 + random.next_bounded_i32(3) as f64 - 2.0; + + let n = pos.0.x - g.ceil() as i32 - i; + let o = pos.0.y - 2 - i; + let p = pos.0.z - g.ceil() as i32 - i; + let q = 2 * (g.ceil() as i32 + i); + let r = 2 * (2 + i); + + for _ in n..=(n + q) { + for _ in p..=(p + q) { + if o > chunk.ocean_floor_height_exclusive(&pos.0.to_vec2_i32()) as i32 { + continue; + } + return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r); + } + } + false + } + + #[expect(clippy::too_many_arguments)] + fn generate_vein_part( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + start_x: f64, + end_x: f64, + start_z: f64, + end_z: f64, + start_y: f64, + end_y: f64, + x_bound: i32, + y_bound: i32, + z_bound: i32, + horizontal_size: i32, + vertical_size: i32, + ) -> bool { + let mut placed_blocks_count = 0; + let mut bit_set = HashSet::new(); + let mut mutable_pos = BlockPos::new(0, 0, 0); + let j = self.size; + let mut ds = vec![0.0; (j * 4) as usize]; + for k in 0..j { + let f = k as f32 / j as f32; + let d = lerp(f as f64, start_x, end_x); + let e = lerp(f as f64, start_y, end_y); + let g = lerp(f as f64, start_z, end_z); + let h = random.next_f64() * j as f64 / 16.0; + let l = (((f32::consts::PI * f).sin() + 1.0) * h as f32 + 1.0) / 2.0; + + ds[k as usize * 4] = d; + ds[k as usize * 4 + 1] = e; + ds[k as usize * 4 + 2] = g; + ds[k as usize * 4 + 3] = l as f64; + } + + for k in 0..(j - 1) { + if ds[k as usize * 4 + 3] <= 0.0 { + continue; + } + for m in (k + 1)..j { + if ds[m as usize * 4 + 3] <= 0.0 { + continue; + } + let h_val = ds[k as usize * 4 + 3] - ds[m as usize * 4 + 3]; + let d_val = ds[k as usize * 4] - ds[m as usize * 4]; + let e_val = ds[k as usize * 4 + 1] - ds[m as usize * 4 + 1]; + let g_val = ds[k as usize * 4 + 2] - ds[m as usize * 4 + 2]; + + if h_val * h_val > d_val * d_val + e_val * e_val + g_val * g_val { + if h_val > 0.0 { + ds[m as usize * 4 + 3] = -1.0; + continue; + } + ds[k as usize * 4 + 3] = -1.0; + } + } + } + + for m_idx in 0..j { + let d_val = ds[m_idx as usize * 4 + 3]; + if d_val < 0.0 { + continue; + } + let e_val = ds[m_idx as usize * 4]; + let g_val = ds[m_idx as usize * 4 + 1]; + let h_val = ds[m_idx as usize * 4 + 2]; + + let n_bound = ((e_val - d_val).floor() as i32).max(x_bound); + let o_bound = ((g_val - d_val).floor() as i32).max(y_bound); + let p_bound = ((h_val - d_val).floor() as i32).max(z_bound); + let q_bound = ((e_val + d_val).floor() as i32).max(n_bound); + let r_bound = ((g_val + d_val).floor() as i32).max(o_bound); + let s_bound = ((h_val + d_val).floor() as i32).max(p_bound); + + for t_val in n_bound..=q_bound { + let u_val = (t_val as f64 + 0.5 - e_val) / d_val; + if u_val * u_val >= 1.0 { + continue; + } + for v_val in o_bound..=r_bound { + let w_val = (v_val as f64 + 0.5 - g_val) / d_val; + if u_val * u_val + w_val * w_val >= 1.0 { + continue; + } + 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 { + continue; + } + if chunk.out_of_height(v_val as i16) { + continue; + } + + let ac = (t_val - x_bound) + + (v_val - y_bound) * horizontal_size + + (aa_val - z_bound) * horizontal_size * vertical_size; + + if bit_set.contains(&ac) { + continue; + } + bit_set.insert(ac); + + mutable_pos.0.x = t_val; + mutable_pos.0.y = v_val; + mutable_pos.0.z = aa_val; + + // if !world.is_valid_for_set_block(&mutable_pos) { + // continue; + // } + + let ad = t_val; + let ae = v_val; + let af = aa_val; + + let block_state = chunk.get_block_state(&Vector3::new(ad, ae, af)); + + for target in &self.targets { + if self.should_place( + chunk, + block_state.to_state(), + random, + target, + &mut mutable_pos, + ) { + chunk.set_block_state( + &Vector3::new(ad, ae, af), + &target.state.get_state().unwrap(), + ); + placed_blocks_count += 1; + break; // Equivalent to 'continue block11;' + } + } + } + } + } + } + placed_blocks_count > 0 + } + + fn should_place( + &self, + chunk: &mut ProtoChunk, + state: BlockState, + random: &mut RandomGenerator, + target: &OreTarget, + pos: &mut BlockPos, + ) -> bool { + if !target.target.test(&state, random) { + return false; + } + if Self::should_not_discard(random, self.discard_chance_on_air_exposure) { + return true; + } + !Self::is_exposed_to_air(chunk, pos) + } + + fn should_not_discard(random: &mut RandomGenerator, chance: f32) -> bool { + if chance <= 0.0f32 { + return true; + } + if chance >= 1.0f32 { + return false; + } + random.next_f32() >= chance + } + + fn is_exposed_to_air(chunk: &mut ProtoChunk, pos: &BlockPos) -> bool { + for dir in BlockDirection::all() { + if chunk + .get_block_state(&pos.offset(dir.to_offset()).0) + .to_state() + .is_air() + { + return true; + } + } + false + } +} diff --git a/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs b/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs new file mode 100644 index 000000000..f6eda45b8 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs @@ -0,0 +1,51 @@ +use std::sync::Arc; + +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, generation::feature::placed_features::PlacedFeatureWrapper, level::Level, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct RandomBooleanFeature { + feature_true: Box, + feature_false: Box, +} + +impl RandomBooleanFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let val = random.next_bool(); + let feature = if val { + &self.feature_true + } else { + &self.feature_false + }; + Box::pin(feature.get().generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + )) + .await + } +} diff --git a/pumpkin-world/src/generation/feature/features/random_patch.rs b/pumpkin-world/src/generation/feature/features/random_patch.rs new file mode 100644 index 000000000..5e96320ab --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/random_patch.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, generation::feature::placed_features::PlacedFeature, level::Level, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct RandomPatchFeature { + tries: u8, + xz_spread: u8, + y_spread: u8, + feature: Box, +} + +impl RandomPatchFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature: &str, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut i = 0; + let xz = self.xz_spread as i32 + 1; + let y = self.y_spread as i32 + 1; + for _ in 0..self.tries { + let pos = Vector3::new( + pos.0.x + random.next_bounded_i32(xz) - random.next_bounded_i32(xz), + pos.0.y + random.next_bounded_i32(y) - random.next_bounded_i32(y), + pos.0.z + random.next_bounded_i32(xz) - random.next_bounded_i32(xz), + ); + if !Box::pin(self.feature.generate( + chunk, + level, + block_registry, + min_y, + height, + feature, + random, + BlockPos(pos), + )) + .await + { + continue; + } + i += 1; + } + i > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/random_selector.rs b/pumpkin-world/src/generation/feature/features/random_selector.rs new file mode 100644 index 000000000..ef1108c3d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/random_selector.rs @@ -0,0 +1,67 @@ +use std::sync::Arc; + +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, generation::feature::placed_features::PlacedFeatureWrapper, level::Level, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct RandomFeature { + features: Vec, + default: Box, +} + +#[derive(Deserialize)] +struct RandomFeatureEntry { + feature: PlacedFeatureWrapper, + chance: f32, +} + +impl RandomFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + for feature in &self.features { + if random.next_f32() >= feature.chance { + continue; + } + return Box::pin(feature.feature.get().generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + )) + .await; + } + Box::pin(self.default.get().generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + )) + .await + } +} diff --git a/pumpkin-world/src/generation/feature/features/replace_single_block.rs b/pumpkin-world/src/generation/feature/features/replace_single_block.rs new file mode 100644 index 000000000..f5eb00628 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/replace_single_block.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ReplaceSingleBlockFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/root_system.rs b/pumpkin-world/src/generation/feature/features/root_system.rs new file mode 100644 index 000000000..cd68cb59e --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/root_system.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct RootSystemFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/scattered_ore.rs b/pumpkin-world/src/generation/feature/features/scattered_ore.rs new file mode 100644 index 000000000..5a7ffaabe --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/scattered_ore.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ScatteredOreFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/sculk_patch.rs b/pumpkin-world/src/generation/feature/features/sculk_patch.rs new file mode 100644 index 000000000..eacf6658c --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/sculk_patch.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct SculkPatchFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/sea_pickle.rs b/pumpkin-world/src/generation/feature/features/sea_pickle.rs new file mode 100644 index 000000000..d93983401 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/sea_pickle.rs @@ -0,0 +1,51 @@ +use pumpkin_data::{ + Block, + block_properties::{ + BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties, get_state_by_state_id, + }, +}; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos, vector2::Vector2}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +#[derive(Deserialize)] +pub struct SeaPickleFeature { + count: IntProvider, +} + +impl SeaPickleFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut times = 0; + let count = self.count.get(random); + for _ in 0..count { + let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); + let z = random.next_bounded_i32(8) - random.next_bounded_i32(8); + let y = + chunk.ocean_floor_height_exclusive(&Vector2::new(pos.0.x + x, pos.0.z + z)) as i32; + if chunk.get_block_state(&pos.0).to_block() != Block::WATER { + continue; + } + let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); + props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash + let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(props.to_state_id(&Block::SEA_PICKLE)).unwrap(), + ); + times += 1; + } + times > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/seagrass.rs b/pumpkin-world/src/generation/feature/features/seagrass.rs new file mode 100644 index 000000000..6e90ec2ea --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/seagrass.rs @@ -0,0 +1,60 @@ +use pumpkin_data::{ + Block, + block_properties::{ + BlockProperties, DoubleBlockHalf, TallSeagrassLikeProperties, get_state_by_state_id, + }, +}; +use pumpkin_util::{ + math::{position::BlockPos, vector2::Vector2}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +#[derive(Deserialize)] +pub struct SeagrassFeature { + probability: f32, +} + +impl SeagrassFeature { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); + let z = random.next_bounded_i32(8) - random.next_bounded_i32(8); + let y = chunk.ocean_floor_height_exclusive(&Vector2::new(pos.0.x + x, pos.0.z + z)) as i32; + let top_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); + if chunk.get_block_state(&top_pos.0).to_block() == Block::WATER { + let tall = random.next_f64() < self.probability as f64; + if tall { + let tall_pos = top_pos.up(); + if chunk.get_block_state(&tall_pos.0).to_block() == Block::WATER { + let mut props = TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS); + props.half = DoubleBlockHalf::Upper; + chunk.set_block_state( + &top_pos.0, + &get_state_by_state_id(Block::TALL_SEAGRASS.default_state_id).unwrap(), + ); + chunk.set_block_state( + &tall_pos.0, + &get_state_by_state_id(props.to_state_id(&Block::TALL_SEAGRASS)).unwrap(), + ); + } + } else { + chunk.set_block_state( + &top_pos.0, + &get_state_by_state_id(Block::SEAGRASS.default_state_id).unwrap(), + ); + } + return true; + } + false + } +} diff --git a/pumpkin-world/src/generation/feature/features/simple_block.rs b/pumpkin-world/src/generation/feature/features/simple_block.rs new file mode 100644 index 000000000..1ad8d7ba3 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/simple_block.rs @@ -0,0 +1,41 @@ +use pumpkin_data::{BlockDirection, block_properties::get_block_by_state_id}; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::block_state_provider::BlockStateProvider, + world::{BlockAccessor, BlockRegistryExt}, +}; + +#[derive(Deserialize)] +pub struct SimpleBlockFeature { + to_place: BlockStateProvider, + schedule_tick: Option, +} + +impl SimpleBlockFeature { + pub fn generate( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let state = self.to_place.get(random, pos); + let block = get_block_by_state_id(state.id).unwrap(); + let block_accessor: &dyn BlockAccessor = chunk; + if !futures::executor::block_on(async move { + block_registry + .can_place_at(&block, block_accessor, &pos, BlockDirection::Up) + .await + }) { + return false; + } + + // TODO: check things.. + chunk.set_block_state(&pos.0, &state); + // TODO: schedule tick when needed + true + } +} diff --git a/pumpkin-world/src/generation/feature/features/simple_random_selector.rs b/pumpkin-world/src/generation/feature/features/simple_random_selector.rs new file mode 100644 index 000000000..a494c2e57 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/simple_random_selector.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, generation::feature::placed_features::PlacedFeature, level::Level, + world::BlockRegistryExt, +}; + +#[derive(Deserialize)] +pub struct SimpleRandomFeature { + features: Vec, +} + +impl SimpleRandomFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let i = random.next_bounded_i32(self.features.len() as i32); + let feature = &self.features[i as usize]; + Box::pin(feature.generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + )) + .await + } +} diff --git a/pumpkin-world/src/generation/feature/features/spring_feature.rs b/pumpkin-world/src/generation/feature/features/spring_feature.rs new file mode 100644 index 000000000..99b4e5497 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/spring_feature.rs @@ -0,0 +1,130 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, block::BlockStateCodec, world::BlockRegistryExt}; + +#[derive(Deserialize)] +pub struct SpringFeatureFeature { + state: BlockStateCodec, + requires_block_below: bool, + rock_count: i32, + hole_count: i32, + valid_blocks: BlockWrapper, +} + +#[derive(Deserialize, Clone)] +#[serde(untagged)] +enum BlockWrapper { + Single(String), + Multi(Vec), +} + +impl SpringFeatureFeature { + pub fn generate( + &self, + _block_registry: &dyn BlockRegistryExt, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // i don't think this is the most efficient way, but it works + let valid_blocks = match self.valid_blocks.clone() { + BlockWrapper::Single(item) => vec![item], + BlockWrapper::Multi(items) => items, + }; + if !valid_blocks.contains( + &chunk + .get_block_state(&pos.up().0) + .to_block() + .name + .to_string(), + ) { + return false; + } + if self.requires_block_below + && !valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::Down.to_offset()).0) + .to_block() + .name + .to_string(), + ) + { + return false; + } + let state = chunk.get_block_state(&pos.0); + if !state.to_state().is_air() && !valid_blocks.contains(&state.to_block().name.to_string()) + { + return false; + } + + let mut valid = 0; + if valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::West.to_offset()).0) + .to_block() + .name + .to_string(), + ) { + valid += 1; + } + if valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::East.to_offset()).0) + .to_block() + .name + .to_string(), + ) { + valid += 1; + } + if valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::North.to_offset()).0) + .to_block() + .name + .to_string(), + ) { + valid += 1; + } + if valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::South.to_offset()).0) + .to_block() + .name + .to_string(), + ) { + valid += 1; + } + if valid_blocks.contains( + &chunk + .get_block_state(&pos.offset(BlockDirection::Down.to_offset()).0) + .to_block() + .name + .to_string(), + ) { + valid += 1; + } + let mut air = 0; + if chunk.is_air(&pos.offset(BlockDirection::West.to_offset()).0) { + air += 1; + } + if chunk.is_air(&pos.offset(BlockDirection::East.to_offset()).0) { + air += 1; + } + if chunk.is_air(&pos.offset(BlockDirection::North.to_offset()).0) { + air += 1; + } + if chunk.is_air(&pos.offset(BlockDirection::South.to_offset()).0) { + air += 1; + } + if chunk.is_air(&pos.offset(BlockDirection::Down.to_offset()).0) { + air += 1; + } + if valid == self.rock_count && air == self.hole_count { + chunk.set_block_state(&pos.0, &self.state.get_state().unwrap()); + return true; + } + false + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs new file mode 100644 index 000000000..9fa89f4ce --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct AlterGroundTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs new file mode 100644 index 000000000..0e57c2104 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct AttachedToLeavesTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs new file mode 100644 index 000000000..52c4ead15 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs @@ -0,0 +1,37 @@ +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider}; + +#[derive(Deserialize)] +pub struct AttachedToLogsTreeDecorator { + probability: f32, + block_provider: BlockStateProvider, + directions: Vec, +} + +impl AttachedToLogsTreeDecorator { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _root_positions: Vec, + log_positions: Vec, + ) { + // TODO: shuffle + for pos in log_positions { + // TODO: random + let pos = pos.offset(self.directions[0].to_offset()); + if random.next_f32() > self.probability + || !chunk.get_block_state(&pos.0).to_state().is_air() + { + continue; + } + chunk.set_block_state(&pos.0, &self.block_provider.get(random, pos)); + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs new file mode 100644 index 000000000..568d9928c --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BeehiveTreeDecorator { + probability: f32, +} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs new file mode 100644 index 000000000..97da60154 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct CocoaTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs new file mode 100644 index 000000000..a2ed7b506 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct CreakingHeartTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/leave_vine.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/leave_vine.rs new file mode 100644 index 000000000..395a28150 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/leave_vine.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct LeavesVineTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs new file mode 100644 index 000000000..b63b26bdb --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs @@ -0,0 +1,96 @@ +use alter_ground::AlterGroundTreeDecorator; +use attached_to_leaves::AttachedToLeavesTreeDecorator; +use attached_to_logs::AttachedToLogsTreeDecorator; +use beehive::BeehiveTreeDecorator; +use cocoa::CocoaTreeDecorator; +use creaking_heart::CreakingHeartTreeDecorator; +use leave_vine::LeavesVineTreeDecorator; +use pale_moss::PaleMossTreeDecorator; +use place_on_ground::PlaceOnGroundTreeDecorator; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; +use trunk_vine::TrunkVineTreeDecorator; + +use crate::ProtoChunk; + +mod alter_ground; +mod attached_to_leaves; +mod attached_to_logs; +mod beehive; +mod cocoa; +mod creaking_heart; +mod leave_vine; +mod pale_moss; +mod place_on_ground; +mod trunk_vine; + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum TreeDecorator { + #[serde(rename = "minecraft:trunk_vine")] + TrunkVine(TrunkVineTreeDecorator), + #[serde(rename = "minecraft:leave_vine")] + LeaveVine(LeavesVineTreeDecorator), + #[serde(rename = "minecraft:pale_moss")] + PaleMoss(PaleMossTreeDecorator), + #[serde(rename = "minecraft:creaking_heart")] + CreakingHeart(CreakingHeartTreeDecorator), + #[serde(rename = "minecraft:cocoa")] + Cocoa(CocoaTreeDecorator), + #[serde(rename = "minecraft:beehive")] + Beehive(BeehiveTreeDecorator), + #[serde(rename = "minecraft:alter_ground")] + AlterGround(AlterGroundTreeDecorator), + #[serde(rename = "minecraft:attached_to_leaves")] + AttachedToLeaves(AttachedToLeavesTreeDecorator), + #[serde(rename = "minecraft:place_on_ground")] + PlaceOnGround(PlaceOnGroundTreeDecorator), + #[serde(rename = "minecraft:attached_to_logs")] + AttachedToLogs(AttachedToLogsTreeDecorator), +} + +impl TreeDecorator { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + root_positions: Vec, + log_positions: Vec, + ) { + match self { + TreeDecorator::TrunkVine(decorator) => decorator.generate(chunk, random, log_positions), + TreeDecorator::LeaveVine(_decorator) => {} + TreeDecorator::PaleMoss(_decorator) => {} + TreeDecorator::CreakingHeart(_decorator) => {} + TreeDecorator::Cocoa(_decorator) => {} + TreeDecorator::Beehive(_decorator) => {} + TreeDecorator::AlterGround(_decorator) => {} + TreeDecorator::PlaceOnGround(decorator) => { + decorator.generate(chunk, random, root_positions, log_positions) + } + TreeDecorator::AttachedToLeaves(_decorator) => {} + TreeDecorator::AttachedToLogs(decorator) => { + decorator.generate(chunk, random, root_positions, log_positions) + } + } + } + + pub(super) fn get_leaf_litter_positions( + root_positions: Vec, + log_positions: Vec, + ) -> Vec { + let mut list = Vec::new(); + if root_positions.is_empty() { + list.extend_from_slice(&log_positions); + } else if !log_positions.is_empty() + && root_positions.first().unwrap().0.y == log_positions.first().unwrap().0.y + { + list.extend_from_slice(&log_positions); + list.extend_from_slice(&root_positions); + } else { + list.extend_from_slice(&root_positions); + } + + list + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs new file mode 100644 index 000000000..7e271adf8 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct PaleMossTreeDecorator {} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs new file mode 100644 index 000000000..85a6f6eba --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs @@ -0,0 +1,83 @@ +use pumpkin_data::Block; +use pumpkin_util::{ + math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider}; + +use super::TreeDecorator; + +#[derive(Deserialize)] +pub struct PlaceOnGroundTreeDecorator { + tries: i32, + radius: i32, + height: i32, + block_state_provider: BlockStateProvider, +} + +impl PlaceOnGroundTreeDecorator { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + root_positions: Vec, + log_positions: Vec, + ) { + let list = TreeDecorator::get_leaf_litter_positions(root_positions, log_positions); + + if list.is_empty() { + return; + } + let pos = list.first().unwrap(); + let i = pos.0.y; + let mut j = pos.0.x; + let mut k = pos.0.x; + let mut l = pos.0.z; + let mut m = pos.0.z; + + for block_pos_2 in list { + if block_pos_2.0.y != i { + continue; + } + j = j.min(block_pos_2.0.x); + k = k.max(block_pos_2.0.x); + l = l.min(block_pos_2.0.z); + m = m.max(block_pos_2.0.z); + } + + let block_box = BoundingBox::new( + Vector3::new(j as f64, i as f64, l as f64), + Vector3::new(k as f64, i as f64, m as f64), + ) + .expand(self.radius as f64, self.height as f64, self.radius as f64); + + for _n in 0..self.tries { + let pos = BlockPos::new( + random.next_inbetween_i32(block_box.min.x as i32, block_box.max.x as i32), + random.next_inbetween_i32(block_box.min.y as i32, block_box.max.y as i32), + random.next_inbetween_i32(block_box.min.z as i32, block_box.max.z as i32), + ); + self.generate_decoration(chunk, pos, random); + } + } + + fn generate_decoration( + &self, + chunk: &mut ProtoChunk, + pos: BlockPos, + random: &mut RandomGenerator, + ) { + let state = chunk.get_block_state(&pos.0); + let pos = pos.up(); + let up_state = chunk.get_block_state(&pos.0); + + // TODO + if (up_state.to_state().is_air() || up_state.to_block() == Block::VINE) + && state.to_state().is_full_cube() + { + chunk.set_block_state(&pos.0, &self.block_state_provider.get(random, pos)); + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/decorator/trunk_vine.rs b/pumpkin-world/src/generation/feature/features/tree/decorator/trunk_vine.rs new file mode 100644 index 000000000..562ca9901 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/decorator/trunk_vine.rs @@ -0,0 +1,69 @@ +use pumpkin_data::{ + Block, BlockDirection, + block_properties::{BlockProperties, VineLikeProperties, get_state_by_state_id}, +}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::ProtoChunk; + +#[derive(Deserialize)] +pub struct TrunkVineTreeDecorator; + +impl TrunkVineTreeDecorator { + pub fn generate( + &self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + log_positions: Vec, + ) { + for pos in log_positions { + if random.next_bounded_i32(3) > 0 + && chunk.is_air(&pos.offset(BlockDirection::West.to_offset()).0) + { + let mut vine = VineLikeProperties::default(&Block::VINE); + vine.east = true; + chunk.set_block_state( + &pos.offset(BlockDirection::West.to_offset()).0, + &get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(), + ); + } + + if random.next_bounded_i32(3) > 0 + && chunk.is_air(&pos.offset(BlockDirection::East.to_offset()).0) + { + let mut vine = VineLikeProperties::default(&Block::VINE); + vine.west = true; + chunk.set_block_state( + &pos.offset(BlockDirection::West.to_offset()).0, + &get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(), + ); + } + + if random.next_bounded_i32(3) > 0 + && chunk.is_air(&pos.offset(BlockDirection::North.to_offset()).0) + { + let mut vine = VineLikeProperties::default(&Block::VINE); + vine.south = true; + chunk.set_block_state( + &pos.offset(BlockDirection::West.to_offset()).0, + &get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(), + ); + } + + if random.next_bounded_i32(3) > 0 + && chunk.is_air(&pos.offset(BlockDirection::South.to_offset()).0) + { + let mut vine = VineLikeProperties::default(&Block::VINE); + vine.north = true; + chunk.set_block_state( + &pos.offset(BlockDirection::West.to_offset()).0, + &get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(), + ); + } + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/acacia.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/acacia.rs new file mode 100644 index 000000000..d24d16baf --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/acacia.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::random::RandomGenerator; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct AcaciaFoliagePlacer; + +impl AcaciaFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + _offset: i32, + foliage_provider: &BlockState, + ) { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius + node.foliage_radius, + -1, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius - 1, + -foliage_height, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius + node.foliage_radius - 1, + 0, + node.giant_trunk, + foliage_provider, + ) + .await; + } + + pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 { + 0 + } +} + +impl LeaveValidator for AcaciaFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + if y == 0 { + return (dx > 1 || dz > 1) && dx != 0 && dz != 0; + } + dx == radius && dz == radius && radius > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/blob.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/blob.rs new file mode 100644 index 000000000..d63fe760a --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/blob.rs @@ -0,0 +1,63 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct BlobFoliagePlacer { + height: i32, +} + +impl BlobFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + for y in (offset - foliage_height..=offset).rev() { + let radius = (radius + node.foliage_radius - 1 - y / 2).max(0); + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } + + pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 { + self.height + } +} + +impl LeaveValidator for BlobFoliagePlacer { + fn is_invalid_for_leaves( + &self, + random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + dx == radius && dz == radius && (random.next_bounded_i32(2) == 0 || y == 0) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/bush.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/bush.rs new file mode 100644 index 000000000..82fae93c7 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/bush.rs @@ -0,0 +1,63 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct BushFoliagePlacer { + height: i32, +} + +impl BushFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + for y in (offset - foliage_height..=offset).rev() { + let radius = radius + node.foliage_radius - 1 - y; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } + + pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 { + self.height + } +} + +impl LeaveValidator for BushFoliagePlacer { + fn is_invalid_for_leaves( + &self, + random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + dx == radius && dz == radius && random.next_bounded_i32(2) == 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/cherry.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/cherry.rs new file mode 100644 index 000000000..d6a2344c6 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/cherry.rs @@ -0,0 +1,131 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::int_provider::IntProvider, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct CherryFoliagePlacer { + height: IntProvider, + wide_bottom_layer_hole_chance: f32, + corner_hole_chance: f32, + hanging_leaves_chance: f32, + hanging_leaves_extension_chance: f32, +} + +impl CherryFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let pos = node.center.up_height(offset); + let radius = radius + node.foliage_radius - 1; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius - 2, + foliage_height - 3, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius - 1, + foliage_height - 4, + node.giant_trunk, + foliage_provider, + ) + .await; + for y in foliage_height - 5..0 { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + } + // TODO: generateSquareWithHangingLeaves + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius, + -1, + node.giant_trunk, + foliage_provider, + ) + .await; + // TODO: generateSquareWithHangingLeaves + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius - 1, + -2, + node.giant_trunk, + foliage_provider, + ) + .await; + } + pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 { + self.height.get(random) + } +} + +impl LeaveValidator for CherryFoliagePlacer { + fn is_invalid_for_leaves( + &self, + random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + if y == -1 + && (dx == radius || dz == radius) + && random.next_f32() < self.wide_bottom_layer_hole_chance + { + return true; + } + let in_radius = dx == radius && dz == radius; + if radius > 2 { + return in_radius + || dx + dz > radius * 2 - 2 && random.next_f32() < self.corner_hole_chance; + } + in_radius && random.next_f32() < self.corner_hole_chance + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/dark_oak.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/dark_oak.rs new file mode 100644 index 000000000..5bf5cf843 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/dark_oak.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct DarkOakFoliagePlacer; + +impl DarkOakFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + _foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let pos = node.center.up_height(offset); + let is_giant = node.giant_trunk; + if is_giant { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius + 2, + -1, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius + 3, + 0, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius + 2, + 1, + node.giant_trunk, + foliage_provider, + ) + .await; + if random.next_bool() { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius, + 2, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } else { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius + 2, + -1, + node.giant_trunk, + foliage_provider, + ) + .await; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + pos, + radius + 1, + 0, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } + + pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 { + 4 + } +} + +impl LeaveValidator for DarkOakFoliagePlacer { + fn is_position_invalid( + &self, + random: &mut RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + giant_trunk: bool, + ) -> bool { + dbg!("aa"); + if !(y != 0 || !giant_trunk || dx != -radius && dx < radius || dz != -radius && dz < radius) + { + return true; + } + // This is default + let x = if giant_trunk { + dx.abs().min((dx - 1).abs()) + } else { + dx.abs() + }; + let z = if giant_trunk { + dz.abs().min((dz - 1).abs()) + } else { + dz.abs() + }; + self.is_invalid_for_leaves(random, x, y, z, radius, giant_trunk) + } + + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + giant_trunk: bool, + ) -> bool { + if y == -1 && !giant_trunk { + return dx == radius && dz == radius; + } + if y == 1 { + return dx + dz > radius * 2 - 2; + } + false + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/fancy.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/fancy.rs new file mode 100644 index 000000000..136c1381d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/fancy.rs @@ -0,0 +1,68 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{math::square, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct LargeOakFoliagePlacer { + height: i32, +} + +impl LargeOakFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + for y in (offset - foliage_height..=offset).rev() { + let radius = radius + + if y == offset || y == offset - foliage_height { + 0 + } else { + 1 + }; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } + + pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 { + self.height + } +} + +impl LeaveValidator for LargeOakFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + square(dx as f32 + 0.5) + square(dz as f32 + 0.5) > (radius * radius) as f32 + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/jungle.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/jungle.rs new file mode 100644 index 000000000..e2a67148d --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/jungle.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct JungleFoliagePlacer { + height: i32, +} + +impl JungleFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let height = if node.giant_trunk { + foliage_height + } else { + 1 + random.next_bounded_i32(2) + }; + for y in (offset - height..=offset).rev() { + let radius = radius + node.foliage_radius + 1 - y; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + } + } + pub fn get_random_height(&self, _random: &mut RandomGenerator, _trunk_height: i32) -> i32 { + self.height + } +} + +impl LeaveValidator for JungleFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + if dx + dz >= 7 { + return true; + } + dx * dx + dz * dz > radius * radius + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs new file mode 100644 index 000000000..5d1fce9fc --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/mega_pine.rs @@ -0,0 +1,79 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::RandomGenerator, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct MegaPineFoliagePlacer { + crown_height: IntProvider, +} + +impl MegaPineFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let pos = node.center; + let mut current = 0; + for y in pos.0.y - foliage_height + offset..pos.0.y + offset { + let delta = pos.0.y - y; + let rad = radius + + node.foliage_radius + + (delta as f32 / foliage_height as f32 * 3.5).floor() as i32; + let radius = if delta > 0 && rad == current && (y & 1) == 0 { + radius + 1 + } else { + radius + }; + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + BlockPos::new(pos.0.x, y, pos.0.z), + radius, + 0, + node.giant_trunk, + foliage_provider, + ) + .await; + current = rad; + } + } + pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 { + self.crown_height.get(random) + } +} + +impl LeaveValidator for MegaPineFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + if dx + dz >= 7 { + return true; + } + dx * dx + dz * dz > radius * radius + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs new file mode 100644 index 000000000..1ab0f26e4 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs @@ -0,0 +1,365 @@ +use std::sync::Arc; + +use acacia::AcaciaFoliagePlacer; +use blob::BlobFoliagePlacer; +use bush::BushFoliagePlacer; +use cherry::CherryFoliagePlacer; +use dark_oak::DarkOakFoliagePlacer; +use fancy::LargeOakFoliagePlacer; +use jungle::JungleFoliagePlacer; +use mega_pine::MegaPineFoliagePlacer; +use pine::PineFoliagePlacer; +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos, vector3::Vector3}, + random::RandomGenerator, +}; +use random_spread::RandomSpreadFoliagePlacer; +use serde::Deserialize; +use spruce::SpruceFoliagePlacer; + +use crate::{ProtoChunk, level::Level}; + +use super::{TreeFeature, TreeNode}; + +mod acacia; +mod blob; +mod bush; +mod cherry; +mod dark_oak; +mod fancy; +mod jungle; +mod mega_pine; +mod pine; +mod random_spread; +mod spruce; + +#[derive(Deserialize)] +pub struct FoliagePlacer { + radius: IntProvider, + offset: IntProvider, + #[serde(flatten)] + pub r#type: FoliageType, +} + +pub trait LeaveValidator { + fn is_position_invalid( + &self, + random: &mut RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + giant_trunk: bool, + ) -> bool { + let x = if giant_trunk { + dx.abs().min((dx - 1).abs()) + } else { + dx.abs() + }; + let z = if giant_trunk { + dz.abs().min((dz - 1).abs()) + } else { + dz.abs() + }; + self.is_invalid_for_leaves(random, x, y, z, radius, giant_trunk) + } + + fn is_invalid_for_leaves( + &self, + random: &mut RandomGenerator, + dx: i32, + y: i32, + dz: i32, + radius: i32, + giant_trunk: bool, + ) -> bool; +} + +impl FoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate_square( + validator: &T, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + center_pos: BlockPos, + radius: i32, + y: i32, + giant_trunk: bool, + foliage_provider: &BlockState, + ) { + let i = if giant_trunk { 1 } else { 0 }; + + for x in -radius..=(radius + i) { + for z in -radius..=(radius + i) { + if validator.is_position_invalid(random, x, y, z, radius, giant_trunk) { + continue; + } + let pos = BlockPos(center_pos.0.add(&Vector3::new(x, y, z))); + Self::place_foliage_block(chunk, level, pos, foliage_provider).await; + } + } + } + + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + foliage_provider: &BlockState, + ) { + let offset = self.offset.get(random); + self.r#type + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await; + } + + pub fn get_random_radius(&self, random: &mut RandomGenerator) -> i32 { + self.radius.get(random) + } + + pub async fn place_foliage_block( + chunk: &mut ProtoChunk<'_>, + _level: &Arc, + pos: BlockPos, + block_state: &BlockState, + ) { + let block = chunk.get_block_state(&pos.0); + if !TreeFeature::can_replace(&block.to_state(), &block.to_block()) { + return; + } + if chunk.chunk_pos == pos.chunk_and_chunk_relative_position().0 { + chunk.set_block_state(&pos.0, block_state); + } else { + // level.set_block_state(&pos, block_state.id).await; + } + } +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum FoliageType { + #[serde(rename = "minecraft:blob_foliage_placer")] + Blob(BlobFoliagePlacer), + #[serde(rename = "minecraft:spruce_foliage_placer")] + Spruce(SpruceFoliagePlacer), + #[serde(rename = "minecraft:pine_foliage_placer")] + Pine(PineFoliagePlacer), + #[serde(rename = "minecraft:acacia_foliage_placer")] + Acacia(AcaciaFoliagePlacer), + #[serde(rename = "minecraft:bush_foliage_placer")] + Bush(BushFoliagePlacer), + #[serde(rename = "minecraft:fancy_foliage_placer")] + Fancy(LargeOakFoliagePlacer), + #[serde(rename = "minecraft:jungle_foliage_placer")] + Jungle(JungleFoliagePlacer), + #[serde(rename = "minecraft:mega_pine_foliage_placer")] + MegaPine(MegaPineFoliagePlacer), + #[serde(rename = "minecraft:dark_oak_foliage_placer")] + DarkOak(DarkOakFoliagePlacer), + #[serde(rename = "minecraft:random_spread_foliage_placer")] + RandomSpread(RandomSpreadFoliagePlacer), + #[serde(rename = "minecraft:cherry_foliage_placer")] + Cherry(CherryFoliagePlacer), +} + +impl FoliageType { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + radius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + match self { + FoliageType::Blob(blob) => { + blob.generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Spruce(spruce) => { + spruce + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Pine(pine) => { + pine.generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Acacia(acacia) => { + acacia + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Bush(bush) => { + bush.generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Fancy(fancy) => { + fancy + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Jungle(jungle) => { + jungle + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::MegaPine(mega_pine) => { + mega_pine + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::DarkOak(dark_oak) => { + dark_oak + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::RandomSpread(random_spread) => { + random_spread + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + FoliageType::Cherry(cherry) => { + cherry + .generate( + chunk, + level, + random, + node, + foliage_height, + radius, + offset, + foliage_provider, + ) + .await + } + } + } + + pub fn get_random_height(&self, random: &mut RandomGenerator, trunk_height: i32) -> i32 { + match self { + FoliageType::Blob(blob) => blob.get_random_height(random), + FoliageType::Spruce(spruce) => spruce.get_random_height(random, trunk_height), + FoliageType::Pine(pine) => pine.get_random_height(random, trunk_height), + FoliageType::Acacia(acacia) => acacia.get_random_height(random), + FoliageType::Bush(bush) => bush.get_random_height(random), + FoliageType::Fancy(fancy) => fancy.get_random_height(random), + FoliageType::Jungle(jungle) => jungle.get_random_height(random, trunk_height), + FoliageType::MegaPine(mega_pine) => mega_pine.get_random_height(random, trunk_height), + FoliageType::DarkOak(dark_oak) => dark_oak.get_random_height(random), + FoliageType::RandomSpread(random_spread) => { + random_spread.get_random_height(random, trunk_height) + } + FoliageType::Cherry(cherry) => cherry.get_random_height(random), + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs new file mode 100644 index 000000000..a7a9f74ab --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/pine.rs @@ -0,0 +1,71 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{math::int_provider::IntProvider, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct PineFoliagePlacer { + height: IntProvider, +} + +impl PineFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + iradius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let mut radius = 0; + for y in offset..offset - foliage_height { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + if radius >= 1 && y == offset - foliage_height + 1 { + radius -= 1; + continue; + } + if radius >= iradius + node.foliage_radius { + continue; + } + radius += 1; + } + } + // TODO: getRandomRadius + pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 { + self.height.get(random) + } +} + +impl LeaveValidator for PineFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + dx == radius && dz == radius && radius > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/random_spread.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/random_spread.rs new file mode 100644 index 000000000..1deb0f104 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/random_spread.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::FoliagePlacer; + +#[derive(Deserialize)] +pub struct RandomSpreadFoliagePlacer { + foliage_height: IntProvider, + leaf_placement_attempts: i32, +} + +impl RandomSpreadFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + _node: &TreeNode, + foliage_height: i32, + radius: i32, + _offset: i32, + foliage_provider: &BlockState, + ) { + for _ in 0..self.leaf_placement_attempts { + let pos = BlockPos::new( + random.next_bounded_i32(radius) - random.next_bounded_i32(radius), + random.next_bounded_i32(foliage_height) - random.next_bounded_i32(foliage_height), + random.next_bounded_i32(radius) - random.next_bounded_i32(radius), + ); + FoliagePlacer::place_foliage_block(chunk, level, pos, foliage_provider).await; + } + } + // TODO: getRandomRadius + pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 { + self.foliage_height.get(random) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/spruce.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/spruce.rs new file mode 100644 index 000000000..a6e839515 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/spruce.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::int_provider::IntProvider, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level}; + +use super::{FoliagePlacer, LeaveValidator}; + +#[derive(Deserialize)] +pub struct SpruceFoliagePlacer { + trunk_height: IntProvider, +} + +impl SpruceFoliagePlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + node: &TreeNode, + foliage_height: i32, + iradius: i32, + offset: i32, + foliage_provider: &BlockState, + ) { + let mut radius = random.next_bounded_i32(2); + let mut max = 1; + let mut next = 0; + for y in ((-foliage_height)..=offset).rev() { + FoliagePlacer::generate_square( + self, + chunk, + level, + random, + node.center, + radius, + y, + node.giant_trunk, + foliage_provider, + ) + .await; + if radius >= max { + radius = next; + next = 1; + max = (iradius + node.foliage_radius).min(max + 1); + continue; + } + radius += 1; + } + } + pub fn get_random_height(&self, random: &mut RandomGenerator, trunk_height: i32) -> i32 { + (trunk_height - self.trunk_height.get(random)).max(4) + } +} + +impl LeaveValidator for SpruceFoliagePlacer { + fn is_invalid_for_leaves( + &self, + _random: &mut pumpkin_util::random::RandomGenerator, + dx: i32, + _y: i32, + dz: i32, + radius: i32, + _giant_trunk: bool, + ) -> bool { + dx == radius && dz == radius && radius > 0 + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/mod.rs b/pumpkin-world/src/generation/feature/features/tree/mod.rs new file mode 100644 index 000000000..bcd7d3528 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/mod.rs @@ -0,0 +1,153 @@ +use std::sync::Arc; + +use decorator::TreeDecorator; +use foliage::FoliagePlacer; +use pumpkin_data::{Block, BlockState, tag::Tagable}; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; +use trunk::TrunkPlacer; + +use crate::{ + ProtoChunk, + generation::{block_state_provider::BlockStateProvider, feature::size::FeatureSize}, + level::Level, +}; + +mod decorator; +mod foliage; +mod trunk; + +#[derive(Deserialize)] +pub struct TreeFeature { + dirt_provider: BlockStateProvider, + trunk_provider: BlockStateProvider, + trunk_placer: TrunkPlacer, + foliage_provider: BlockStateProvider, + foliage_placer: FoliagePlacer, + minimum_size: FeatureSize, + ignore_vines: bool, + force_dirt: bool, + decorators: Vec, +} + +pub struct TreeNode { + center: BlockPos, + foliage_radius: i32, + giant_trunk: bool, +} + +impl TreeFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // TODO + let log_positions = self + .generate_main(chunk, level, min_y, height, feature_name, random, pos) + .await; + + for decorator in &self.decorators { + decorator.generate(chunk, random, Vec::new(), log_positions.clone()); + } + true + } + + pub fn can_replace_or_log(state: &BlockState, block: &Block) -> bool { + Self::can_replace(state, block) || block.is_tagged_with("minecraft:logs").unwrap() + } + + pub fn is_air_or_leaves(state: &BlockState, block: &Block) -> bool { + state.is_air() || block.is_tagged_with("minecraft:leaves").unwrap() + } + + pub fn can_replace(state: &BlockState, block: &Block) -> bool { + state.is_air() + || block + .is_tagged_with("minecraft:replaceable_by_trees") + .unwrap() + } + + #[expect(clippy::too_many_arguments)] + async fn generate_main( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + _min_y: i8, + _height: u16, + _feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Vec { + let height = self.trunk_placer.get_height(random); + + let clipped_height = self.minimum_size.min_clipped_height; + let top = self.get_top(height, chunk, pos); // TODO: roots + if top < height && (clipped_height.is_none() || top < clipped_height.unwrap() as u32) { + return vec![]; + } + let trunk_state = self.trunk_provider.get(random, pos); + let dirt_state = self.dirt_provider.get(random, pos); + + let (nodes, logs) = self + .trunk_placer + .generate( + top, + pos, + chunk, + level, + random, + self.force_dirt, + &dirt_state, + &trunk_state, + ) + .await; + + let foliage_height = self + .foliage_placer + .r#type + .get_random_height(random, height as i32); + let foliage_radius = self.foliage_placer.get_random_radius(random); + let foliage_state = self.foliage_provider.get(random, pos); + for node in nodes { + self.foliage_placer + .generate( + chunk, + level, + random, + &node, + foliage_height, + foliage_radius, + &foliage_state, + ) + .await; + } + logs + } + + fn get_top(&self, height: u32, chunk: &ProtoChunk, init_pos: BlockPos) -> u32 { + for y in 0..=height + 1 { + let j = self.minimum_size.r#type.get_radius(height, y as i32); + for x in -j..=j { + for z in -j..=j { + let pos = BlockPos(init_pos.0.add_raw(x, y as i32, z)); + let rstate = chunk.get_block_state(&pos.0); + let block = rstate.to_block(); + if Self::can_replace_or_log(&rstate.to_state(), &block) + && (self.ignore_vines || block != Block::VINE) + { + continue; + } + return y.saturating_sub(2); + } + } + } + height + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs new file mode 100644 index 000000000..1811a9828 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use pumpkin_data::{BlockDirection, BlockState}; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::{TreeNode, trunk::TrunkPlacer}, + level::Level, +}; + +#[derive(Deserialize)] +pub struct BendingTrunkPlacer { + min_height_for_leaves: u32, + bend_length: IntProvider, +} + +impl BendingTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + _level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_block: &BlockState, + ) -> (Vec, Vec) { + placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); + + // TODO: make this random + let random_direction = BlockDirection::North; + let height = height - 1; + let mut pos = start_pos; + let mut trunk_poses = Vec::new(); + let mut nodes = Vec::new(); + for y in 0..height { + if y + 1 >= height + random.next_bounded_i32(2) as u32 { + pos = pos.offset(random_direction.to_offset()); + } + if placer.place(chunk, &pos, trunk_block) { + trunk_poses.push(pos); + } + if y >= 1 { + nodes.push(TreeNode { + center: pos, + foliage_radius: 0, + giant_trunk: false, + }); + } + pos = pos.up(); + } + let bend = self.bend_length.get(random); + for _ in 0..bend { + if placer.place(chunk, &pos, trunk_block) { + trunk_poses.push(pos); + } + nodes.push(TreeNode { + center: pos, + foliage_radius: 0, + giant_trunk: false, + }); + pos = pos.offset(random_direction.to_offset()); + } + (nodes, trunk_poses) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs new file mode 100644 index 000000000..b1e0565b8 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct CherryTrunkPlacer {} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs new file mode 100644 index 000000000..4e0f92b19 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs @@ -0,0 +1,145 @@ +use std::sync::Arc; + +use pumpkin_data::{BlockDirection, BlockState}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::{TreeFeature, TreeNode, trunk::TrunkPlacer}, + level::Level, +}; + +#[derive(Deserialize)] +pub struct DarkOakTrunkPlacer; + +impl DarkOakTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + _level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_block: &BlockState, + ) -> (Vec, Vec) { + let pos = start_pos.down(); + placer.set_dirt(chunk, &pos, force_dirt, dirt_state); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::East.to_offset()), + force_dirt, + dirt_state, + ); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::South.to_offset()), + force_dirt, + dirt_state, + ); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::South.to_offset()) + .offset(BlockDirection::South.to_offset()), + force_dirt, + dirt_state, + ); + let y_height = start_pos.0.y + height as i32 - 1; + let max_height = height - random.next_bounded_i32(4) as u32; + let mut trunk_poses = Vec::new(); + let mut nodes = Vec::new(); + let mut rand = random.next_bounded_i32(3); + + let mut x = pos.0.x; + let mut z = pos.0.x; + + // TODO: make this random + let random_direction = BlockDirection::North; + + for y in 0..height { + if y >= max_height && rand > 0 { + x += random_direction.to_offset().x; + z += random_direction.to_offset().z; + rand -= 1; + } + let pos = BlockPos::new(x, y_height, z); + // TODO: support multiple chunks + let state = chunk.get_block_state(&pos.0); + if !TreeFeature::is_air_or_leaves(&state.to_state(), &state.to_block()) { + continue; + } + if placer.try_place(chunk, &pos, trunk_block) { + trunk_poses.push(pos); + } + if placer.try_place( + chunk, + &pos.offset(BlockDirection::East.to_offset()), + trunk_block, + ) { + trunk_poses.push(pos.offset(BlockDirection::East.to_offset())); + } + if placer.try_place( + chunk, + &pos.offset(BlockDirection::South.to_offset()), + trunk_block, + ) { + trunk_poses.push(pos.offset(BlockDirection::South.to_offset())); + } + if placer.try_place( + chunk, + &pos.offset(BlockDirection::East.to_offset()) + .offset(BlockDirection::South.to_offset()), + trunk_block, + ) { + trunk_poses.push( + pos.offset(BlockDirection::East.to_offset()) + .offset(BlockDirection::South.to_offset()), + ); + } + } + nodes.push(TreeNode { + center: BlockPos::new(x, y_height, z), + foliage_radius: 0, + giant_trunk: true, + }); + for xd in -1..2 { + for zd in -1..2 { + if (0..=1).contains(&xd) && (0..=1).contains(&zd) || random.next_bounded_i32(3) > 0 + { + continue; + } + let h = random.next_bounded_i32(3) + 2; + for height in 0..h { + if placer.try_place( + chunk, + &BlockPos::new( + start_pos.0.x + xd, + y_height - height - 1, + start_pos.0.z + zd, + ), + trunk_block, + ) { + trunk_poses.push(BlockPos::new( + start_pos.0.x + xd, + y_height - height - 1, + start_pos.0.z + zd, + )); + } + } + nodes.push(TreeNode { + center: BlockPos::new(start_pos.0.x + xd, y_height, start_pos.0.z + zd), + foliage_radius: 0, + giant_trunk: true, + }); + } + } + + (nodes, trunk_poses) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/fancy.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/fancy.rs new file mode 100644 index 000000000..34d573dfc --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/fancy.rs @@ -0,0 +1,288 @@ +use core::f32; +use std::sync::Arc; + +use pumpkin_data::{ + BlockState, + block_properties::{Axis, EnumVariants, get_block_by_state_id, get_state_by_state_id}, +}; +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::{TreeFeature, TreeNode}, + level::Level, +}; + +use super::TrunkPlacer; + +#[derive(Deserialize)] +pub struct FancyTrunkPlacer; + +impl FancyTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_block: &BlockState, + ) -> (Vec, Vec) { + let j = height as i32 + 2; + let k = ((j as f64) * 0.618).floor() as i32; + + placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); + + let l = ((1.382 + (1.0 * (j as f64) / 13.0).powf(2.0)).floor() as i32).min(1); + let m = start_pos.0.y + k; + let mut list: Vec = Vec::new(); + let mut logs = Vec::new(); + + list.push(BranchPosition::new(start_pos, m)); + + for n in (0..=(j - 5)).rev() { + let f = Self::should_generate_branch(j, n); + if f < 0.0f32 { + continue; + } + + for _o in 0..l { + let g = (1.0f32 * f * (random.next_f32() + 0.328f32)) as f64; + let h = (random.next_f32() * 2.0f32 * f32::consts::PI) as f64; + let p = g * h.sin() + 0.5f64; + let q = g * h.cos() + 0.5f64; + + let block_pos = start_pos.add(p.floor() as i32, n - 1, q.floor() as i32); + let block_pos_2 = block_pos.up_height(5); + + let (i, new_logs) = Self::make_or_check_branch( + chunk, + level, + block_pos.0, + block_pos_2.0, + trunk_block, + false, + ) + .await; + logs.extend_from_slice(&new_logs); + + if !i { + continue; + } + + let r = start_pos.0.x - block_pos.0.x; + let s = start_pos.0.z - block_pos.0.z; + let t = (block_pos.0.y as f64) - ((r * r + s * s) as f64).sqrt() * 0.381f64; + let u = if t > (m as f64) { m } else { t as i32 }; + + let block_pos_3 = BlockPos::new(start_pos.0.x, u, start_pos.0.z); + + let (i, new_logs) = Self::make_or_check_branch( + chunk, + level, + block_pos_3.0, + block_pos.0, + trunk_block, + false, + ) + .await; + logs.extend_from_slice(&new_logs); + + if !i { + continue; + } + list.push(BranchPosition::new(block_pos, block_pos_3.0.y)); + } + } + + Self::make_or_check_branch( + chunk, + level, + start_pos.0, + start_pos.up_height(k).0, + trunk_block, + true, + ) + .await; + Self::make_branches(chunk, level, j, start_pos.0, trunk_block, &list).await; + + let mut list_2: Vec = Vec::new(); + for branch_position in list { + if Self::is_high_enough(j, branch_position.get_end_y() - start_pos.0.y) { + list_2.push(branch_position.node); + } + } + (list_2, logs) + } + + async fn make_or_check_branch( + chunk: &mut ProtoChunk<'_>, + _level: &Arc, + start_pos: Vector3, + branch_pos: Vector3, + trunk_provider: &BlockState, + make: bool, + ) -> (bool, Vec) { + if !make && start_pos == branch_pos { + return (true, vec![]); + } + + let block_pos_offset = Vector3::new( + branch_pos.x - start_pos.x, + branch_pos.y - start_pos.y, + branch_pos.z - start_pos.z, + ); + let i = Self::get_longest_side(block_pos_offset); + + let f = block_pos_offset.x as f32 / i as f32; + let g = block_pos_offset.y as f32 / i as f32; + let h = block_pos_offset.z as f32 / i as f32; + + let mut logs = Vec::new(); + for j in 0..=i { + let block_pos_2 = BlockPos(start_pos.add_raw( + (0.5f32 + j as f32 * f).floor() as i32, + (0.5f32 + j as f32 * g).floor() as i32, + (0.5f32 + j as f32 * h).floor() as i32, + )); + + let block = chunk.get_block_state(&block_pos_2.0); + + if make { + let axis = Self::get_log_axis(start_pos, block_pos_2.0); + + if TreeFeature::can_replace(&block.to_state(), &block.to_block()) { + let block = get_block_by_state_id(trunk_provider.id).unwrap(); + let original_props = &block.properties(trunk_provider.id).unwrap().to_props(); + let axis = axis.to_value(); + // Set the right Axis + let props_vec: Vec<(&str, &str)> = original_props + .iter() + .map(|(key, value)| { + if key == "axis" { + (key.as_str(), axis) + } else { + (key.as_str(), value.as_str()) + } + }) + .collect(); + let state = block + .from_properties(props_vec) + .unwrap() + .to_state_id(&block); + if chunk.chunk_pos == block_pos_2.chunk_and_chunk_relative_position().0 { + chunk.set_block_state( + &block_pos_2.0, + &get_state_by_state_id(state).unwrap(), + ); + } else { + // level.set_block_state(&block_pos_2, state).await; + } + logs.push(block_pos_2); + continue; + } + } + + if TreeFeature::can_replace_or_log(&block.to_state(), &block.to_block()) { + continue; + } + return (false, logs); + } + (true, logs) + } + + async fn make_branches( + chunk: &mut ProtoChunk<'_>, + level: &Arc, + tree_height: i32, + start_pos: Vector3, + trunk_provider: &BlockState, + branch_positions: &Vec, + ) { + for branch_position in branch_positions { + let i = branch_position.get_end_y(); + let block_pos = BlockPos::new(start_pos.x, i, start_pos.z); + if block_pos == branch_position.node.center + || !Self::is_high_enough(tree_height, i - start_pos.y) + { + continue; + } + Self::make_or_check_branch( + chunk, + level, + block_pos.0, + branch_position.node.center.0, + trunk_provider, + true, + ) + .await; + } + } + + fn should_generate_branch(tree_height: i32, height: i32) -> f32 { + if (height as f32) < (tree_height as f32) * 0.3f32 { + return -1.0f32; + } + let f = (tree_height as f32) / 2.0f32; + let g = f - (height as f32); + let h = (f * f - g * g).sqrt(); + if g == 0.0f32 { + h + } else if g.abs() >= f { + 0.0f32 + } else { + h * 0.5f32 + } + } + + fn get_longest_side(offset: Vector3) -> i32 { + let x = offset.x.abs(); + let y = offset.y.abs(); + let z = offset.z.abs(); + x.max(y.max(z)) + } + + fn get_log_axis(branch_start: Vector3, branch_end: Vector3) -> Axis { + let x = (branch_end.x - branch_start.x).abs(); + let z = (branch_end.z - branch_start.z).abs(); + let max = x.max(z); + if max > 0 { + if x == max { Axis::X } else { Axis::Z } + } else { + Axis::Y + } + } + + fn is_high_enough(tree_height: i32, height: i32) -> bool { + (height as f64) >= (tree_height as f64) * 0.2 + } +} + +pub struct BranchPosition { + pub node: TreeNode, + end_y: i32, +} + +impl BranchPosition { + pub fn new(pos: BlockPos, end_y: i32) -> Self { + BranchPosition { + node: TreeNode { + center: pos, + foliage_radius: 0, + giant_trunk: false, + }, + end_y, + } + } + + pub fn get_end_y(&self) -> i32 { + self.end_y + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs new file mode 100644 index 000000000..263cd50fe --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ForkingTrunkPlacer; diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs new file mode 100644 index 000000000..c8aaac109 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/giant.rs @@ -0,0 +1,97 @@ +use std::sync::Arc; + +use pumpkin_data::{BlockDirection, BlockState}; +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::RandomGenerator, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::{TreeNode, trunk::TrunkPlacer}, + level::Level, +}; + +#[derive(Deserialize)] +pub struct GiantTrunkPlacer; + +impl GiantTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + _level: &Arc, + _random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_block: &BlockState, + ) -> (Vec, Vec) { + let pos = start_pos.down(); + placer.set_dirt(chunk, &pos, force_dirt, dirt_state); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::East.to_offset()), + force_dirt, + dirt_state, + ); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::South.to_offset()), + force_dirt, + dirt_state, + ); + placer.set_dirt( + chunk, + &pos.offset(BlockDirection::South.to_offset()) + .offset(BlockDirection::South.to_offset()), + force_dirt, + dirt_state, + ); + + let mut trunk_poses = Vec::new(); + for y in 0..height { + if placer.try_place( + chunk, + &pos.offset(Vector3::new(0, y as i32, 0)), + trunk_block, + ) { + trunk_poses.push(pos.offset(Vector3::new(0, y as i32, 0))); + } + if y >= height - 1 { + continue; + } + if placer.try_place( + chunk, + &pos.offset(Vector3::new(1, y as i32, 0)), + trunk_block, + ) { + trunk_poses.push(pos.offset(Vector3::new(1, y as i32, 0))); + } + if placer.try_place( + chunk, + &pos.offset(Vector3::new(1, y as i32, 1)), + trunk_block, + ) { + trunk_poses.push(pos.offset(Vector3::new(1, y as i32, 1))); + } + if placer.try_place( + chunk, + &pos.offset(Vector3::new(0, y as i32, 1)), + trunk_block, + ) { + trunk_poses.push(pos.offset(Vector3::new(0, y as i32, 1))); + } + } + ( + vec![TreeNode { + center: start_pos.up_height(height as i32), + foliage_radius: 0, + giant_trunk: true, + }], + trunk_poses, + ) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/mega_jungle.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/mega_jungle.rs new file mode 100644 index 000000000..a7b0b0153 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/mega_jungle.rs @@ -0,0 +1,77 @@ +use core::f32; +use std::sync::Arc; + +use pumpkin_data::BlockState; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::{ + TreeNode, + trunk::{TrunkPlacer, giant::GiantTrunkPlacer}, + }, + level::Level, +}; + +#[derive(Deserialize)] +pub struct MegaJungleTrunkPlacer; + +impl MegaJungleTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_block: &BlockState, + ) -> (Vec, Vec) { + let (mut nodes, mut trunk_poses) = GiantTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_block, + ) + .await; + let mut i = height as i32 - 2 - random.next_bounded_i32(4); + + let mut j_val = 0; + let mut k_val = 0; + + while i > height as i32 / 2 { + let f = random.next_f32() * (f32::consts::PI * 2.0); + + for l in 0..5 { + j_val = (1.5f32 + f.cos() * l as f32) as i32; + k_val = (1.5f32 + f.sin() * l as f32) as i32; + + let block_pos = start_pos.add(j_val, i - 3 + l / 2, k_val); + + if placer.try_place(chunk, &block_pos, trunk_block) { + trunk_poses.push(block_pos); + } + } + + nodes.push(TreeNode { + center: start_pos.add(j_val, i, k_val), + foliage_radius: -2, + giant_trunk: false, + }); + + i -= 2 + random.next_bounded_i32(4); + } + (nodes, trunk_poses) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs new file mode 100644 index 000000000..048d1de67 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs @@ -0,0 +1,245 @@ +use std::sync::Arc; + +use fancy::FancyTrunkPlacer; +use pumpkin_data::{Block, BlockState, tag::Tagable}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; +use serde::Deserialize; +use straight::StraightTrunkPlacer; + +use crate::{ + ProtoChunk, + generation::feature::features::tree::trunk::{ + bending::BendingTrunkPlacer, cherry::CherryTrunkPlacer, dark_oak::DarkOakTrunkPlacer, + forking::ForkingTrunkPlacer, giant::GiantTrunkPlacer, mega_jungle::MegaJungleTrunkPlacer, + upwards_branching::UpwardsBranchingTrunkPlacer, + }, + level::Level, +}; + +use super::{TreeFeature, TreeNode}; + +mod bending; +mod cherry; +mod dark_oak; +mod fancy; +mod forking; +mod giant; +mod mega_jungle; +mod straight; +mod upwards_branching; + +#[derive(Deserialize)] +pub struct TrunkPlacer { + base_height: u8, + height_rand_a: u8, + height_rand_b: u8, + #[serde(flatten)] + r#type: TrunkType, +} + +impl TrunkPlacer { + pub fn get_height(&self, random: &mut RandomGenerator) -> u32 { + self.base_height as u32 + + random.next_bounded_i32(self.height_rand_a as i32 + 1) as u32 + + random.next_bounded_i32(self.height_rand_b as i32 + 1) as u32 + } + + pub fn set_dirt( + &self, + chunk: &mut ProtoChunk<'_>, + pos: &BlockPos, + force_dirt: bool, + dirt_state: &BlockState, + ) { + let block = chunk.get_block_state(&pos.0).to_block(); + if force_dirt + || !(block.is_tagged_with("minecraft:dirt").unwrap() + && block != Block::GRASS_BLOCK + && block != Block::MYCELIUM) + { + chunk.set_block_state(&pos.0, dirt_state); + } + } + + pub fn place( + &self, + chunk: &mut ProtoChunk<'_>, + pos: &BlockPos, + trunk_block: &BlockState, + ) -> bool { + let block = chunk.get_block_state(&pos.0); + if TreeFeature::can_replace(&block.to_state(), &block.to_block()) { + chunk.set_block_state(&pos.0, trunk_block); + return true; + } + false + } + + pub fn try_place( + &self, + chunk: &mut ProtoChunk<'_>, + pos: &BlockPos, + trunk_block: &BlockState, + ) -> bool { + let block = chunk.get_block_state(&pos.0); + if TreeFeature::can_replace_or_log(&block.to_state(), &block.to_block()) { + return self.place(chunk, pos, trunk_block); + } + false + } + + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_state: &BlockState, + ) -> (Vec, Vec) { + self.r#type + .generate( + self, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum TrunkType { + #[serde(rename = "minecraft:straight_trunk_placer")] + Straight(StraightTrunkPlacer), + #[serde(rename = "minecraft:forking_trunk_placer")] + Forking(ForkingTrunkPlacer), + #[serde(rename = "minecraft:giant_trunk_placer")] + Giant(GiantTrunkPlacer), + #[serde(rename = "minecraft:mega_jungle_trunk_placer")] + MegaJungle(MegaJungleTrunkPlacer), + #[serde(rename = "minecraft:dark_oak_trunk_placer")] + DarkOak(DarkOakTrunkPlacer), + #[serde(rename = "minecraft:fancy_trunk_placer")] + Fancy(FancyTrunkPlacer), + #[serde(rename = "minecraft:bending_trunk_placer")] + Bending(BendingTrunkPlacer), + #[serde(rename = "minecraft:upwards_branching_trunk_placer")] + UpwardsBranching(UpwardsBranchingTrunkPlacer), + #[serde(rename = "minecraft:cherry_trunk_placer")] + Cherry(CherryTrunkPlacer), +} + +impl TrunkType { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + random: &mut RandomGenerator, + force_dirt: bool, + dirt_state: &BlockState, + trunk_state: &BlockState, + ) -> (Vec, Vec) { + match self { + Self::Straight(_) => StraightTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + force_dirt, + dirt_state, + trunk_state, + ), + TrunkType::Forking(_) => (vec![], vec![]), // TODO + TrunkType::Giant(_) => { + GiantTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } + TrunkType::MegaJungle(_) => { + MegaJungleTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } + TrunkType::DarkOak(_) => { + DarkOakTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } + TrunkType::Fancy(_) => { + FancyTrunkPlacer::generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } + TrunkType::Bending(bending) => { + bending + .generate( + placer, + height, + start_pos, + chunk, + level, + random, + force_dirt, + dirt_state, + trunk_state, + ) + .await + } + TrunkType::UpwardsBranching(_) => (vec![], vec![]), // TODO + TrunkType::Cherry(_) => (vec![], vec![]), // TODO + } + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/straight.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/straight.rs new file mode 100644 index 000000000..51a1ba081 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/straight.rs @@ -0,0 +1,39 @@ +use pumpkin_data::BlockState; +use pumpkin_util::math::position::BlockPos; +use serde::Deserialize; + +use crate::{ProtoChunk, generation::feature::features::tree::TreeNode}; + +use super::TrunkPlacer; + +#[derive(Deserialize)] +pub struct StraightTrunkPlacer; + +impl StraightTrunkPlacer { + pub fn generate( + placer: &TrunkPlacer, + height: u32, + start_pos: BlockPos, + chunk: &mut ProtoChunk, + force_dirt: bool, + dirt_state: &BlockState, + trunk_state: &BlockState, + ) -> (Vec, Vec) { + placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); + let mut logs = Vec::new(); + for i in 0..height { + let pos = start_pos.up_height(i as i32); + if placer.place(chunk, &pos, trunk_state) { + logs.push(pos); + } + } + ( + vec![TreeNode { + center: start_pos.up_height(height as i32), + foliage_radius: 0, + giant_trunk: false, + }], + logs, + ) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs new file mode 100644 index 000000000..97db2d876 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs @@ -0,0 +1,4 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct UpwardsBranchingTrunkPlacer {} diff --git a/pumpkin-world/src/generation/feature/features/twisting_vines.rs b/pumpkin-world/src/generation/feature/features/twisting_vines.rs new file mode 100644 index 000000000..6f2d047ae --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/twisting_vines.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct TwistingVinesFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/underwater_magma.rs b/pumpkin-world/src/generation/feature/features/underwater_magma.rs new file mode 100644 index 000000000..548a4ccbc --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/underwater_magma.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct UnderwaterMagmaFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/vegetation_patch.rs b/pumpkin-world/src/generation/feature/features/vegetation_patch.rs new file mode 100644 index 000000000..78672b6e7 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/vegetation_patch.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct VegetationPatchFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/vines.rs b/pumpkin-world/src/generation/feature/features/vines.rs new file mode 100644 index 000000000..a5801b86a --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/vines.rs @@ -0,0 +1,53 @@ +use pumpkin_data::{ + Block, BlockDirection, + block_properties::{BlockProperties, get_state_by_state_id}, +}; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use serde::Deserialize; + +use crate::{ProtoChunk, world::BlockRegistryExt}; + +#[derive(Deserialize)] +pub struct VinesFeature; + +impl VinesFeature { + #[expect(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut ProtoChunk, + _block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + if !chunk.is_air(&pos.0) { + return false; + } + for dir in BlockDirection::all() { + // TODO + if dir == BlockDirection::Down + || !chunk + .get_block_state(&pos.offset(dir.to_offset()).0) + .to_state() + .is_full_cube() + { + continue; + } + let mut vine = + pumpkin_data::block_properties::VineLikeProperties::default(&Block::VINE); + vine.north = dir == BlockDirection::North; + vine.east = dir == BlockDirection::East; + vine.south = dir == BlockDirection::South; + vine.west = dir == BlockDirection::West; + vine.up = dir == BlockDirection::Up; + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(), + ); + return true; + } + false + } +} diff --git a/pumpkin-world/src/generation/feature/features/void_start_platform.rs b/pumpkin-world/src/generation/feature/features/void_start_platform.rs new file mode 100644 index 000000000..d8c90c903 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/void_start_platform.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct VoidStartPlatformFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/waterlogged_vegetation_patch.rs b/pumpkin-world/src/generation/feature/features/waterlogged_vegetation_patch.rs new file mode 100644 index 000000000..10d07fc18 --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/waterlogged_vegetation_patch.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct WaterloggedVegetationPatchFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/features/weeping_vines.rs b/pumpkin-world/src/generation/feature/features/weeping_vines.rs new file mode 100644 index 000000000..2d0f315ab --- /dev/null +++ b/pumpkin-world/src/generation/feature/features/weeping_vines.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct WeepingVinesFeature { + // TODO +} diff --git a/pumpkin-world/src/generation/feature/mod.rs b/pumpkin-world/src/generation/feature/mod.rs new file mode 100644 index 000000000..12a773f8a --- /dev/null +++ b/pumpkin-world/src/generation/feature/mod.rs @@ -0,0 +1,7 @@ +mod configured_features; +/// So first we go trough all the placed features and check if we should place a feature somewhere using `placed_features`. +/// then if we want to place a feature we place it using the `configured_features`, there is the logic for how we are going to place the feature +pub mod placed_features; + +mod features; +mod size; diff --git a/pumpkin-world/src/generation/feature/placed_features.rs b/pumpkin-world/src/generation/feature/placed_features.rs new file mode 100644 index 000000000..de13b6f33 --- /dev/null +++ b/pumpkin-world/src/generation/feature/placed_features.rs @@ -0,0 +1,556 @@ +use async_trait::async_trait; +use pumpkin_data::BlockDirection; +use serde::Deserialize; +use std::collections::HashMap; +use std::iter; +use std::ops::Deref; +use std::sync::{Arc, LazyLock}; + +use pumpkin_util::biome::FOLIAGE_NOISE; +use pumpkin_util::math::int_provider::IntProvider; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; + +use crate::generation::block_predicate::BlockPredicate; +use crate::generation::height_limit::HeightLimitView; +use crate::generation::height_provider::HeightProvider; +use crate::level::Level; +use crate::world::BlockRegistryExt; +use crate::{HeightMap, ProtoChunk}; + +use super::configured_features::{CONFIGURED_FEATURES, ConfiguredFeature}; + +pub static PLACED_FEATURES: LazyLock> = LazyLock::new(|| { + serde_json::from_str(include_str!("../../../../assets/placed_feature.json")) + .expect("Could not parse placed_feature.json registry.") +}); + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum PlacedFeatureWrapper { + Direct(Box), + Named(String), +} + +impl PlacedFeatureWrapper { + pub fn get(&self) -> &PlacedFeature { + match self { + Self::Named(name) => PLACED_FEATURES + .get(name.strip_prefix("minecraft:").unwrap_or(name)) + .unwrap(), + Self::Direct(feature) => feature, + } + } +} + +#[derive(Deserialize)] +pub struct PlacedFeature { + /// The name of the configuired feature + feature: Feature, + placement: Vec, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Feature { + Named(String), + Inlined(Box), +} + +impl PlacedFeature { + #[expect(clippy::too_many_arguments)] + pub async fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature_name: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut stream: Vec = vec![pos]; + for modifier in &self.placement { + let mut new_stream = Vec::with_capacity(stream.len()); + + for block_pos in stream { + let positions = modifier + .get_positions( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + block_pos, + ) + .await; + new_stream.extend(positions); + } + + stream = new_stream; + } + + let feature = match &self.feature { + Feature::Named(name) => CONFIGURED_FEATURES + .get(name.strip_prefix("minecraft:").unwrap_or(name)) + .unwrap(), + Feature::Inlined(feature) => feature, + }; + + let mut ret = false; + for pos in stream { + if feature + .generate( + chunk, + level, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ) + .await + { + ret = true; + } + } + ret + } +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum PlacementModifier { + #[serde(rename = "minecraft:block_predicate_filter")] + BlockPredicateFilter(BlockFilterPlacementModifier), + #[serde(rename = "minecraft:rarity_filter")] + RarityFilter(RarityFilterPlacementModifier), + #[serde(rename = "minecraft:surface_relative_threshold_filter")] + SurfaceRelativeThresholdFilter(SurfaceThresholdFilterPlacementModifier), + #[serde(rename = "minecraft:surface_water_depth_filter")] + SurfaceWaterDepthFilter(SurfaceWaterDepthFilterPlacementModifier), + #[serde(rename = "minecraft:biome")] + Biome(BiomePlacementModifier), + #[serde(rename = "minecraft:count")] + Count(CountPlacementModifier), + #[serde(rename = "minecraft:noise_based_count")] + NoiseBasedCount(NoiseBasedCountPlacementModifier), + #[serde(rename = "minecraft:noise_threshold_count")] + NoiseThresholdCount(NoiseThresholdCountPlacementModifier), + #[serde(rename = "minecraft:count_on_every_layer")] + CountOnEveryLayer, + #[serde(rename = "minecraft:environment_scan")] + EnvironmentScan(EnvironmentScanPlacementModifier), + #[serde(rename = "minecraft:heightmap")] + Heightmap(HeightmapPlacementModifier), + #[serde(rename = "minecraft:height_range")] + HeightRange(HeightRangePlacementModifier), + #[serde(rename = "minecraft:in_square")] + InSquare(SquarePlacementModifier), + #[serde(rename = "minecraft:random_offset")] + RandomOffset(RandomOffsetPlacementModifier), + #[serde(rename = "minecraft:fixed_placement")] + FixedPlacement, +} + +impl PlacementModifier { + #[expect(clippy::too_many_arguments)] + pub async fn get_positions( + &self, + chunk: &ProtoChunk<'_>, + block_registry: &dyn BlockRegistryExt, + min_y: i8, + height: u16, + feature: &str, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + match self { + PlacementModifier::BlockPredicateFilter(modifier) => { + modifier + .get_positions(block_registry, chunk, feature, random, pos) + .await + } + PlacementModifier::RarityFilter(modifier) => { + modifier + .get_positions(block_registry, chunk, feature, random, pos) + .await + } + PlacementModifier::SurfaceRelativeThresholdFilter(modifier) => { + modifier + .get_positions(block_registry, chunk, feature, random, pos) + .await + } + PlacementModifier::SurfaceWaterDepthFilter(modifier) => { + modifier + .get_positions(block_registry, chunk, feature, random, pos) + .await + } + PlacementModifier::Biome(modifier) => { + modifier + .get_positions(block_registry, chunk, feature, random, pos) + .await + } + PlacementModifier::Count(modifier) => modifier.get_positions(random, pos), + PlacementModifier::NoiseBasedCount(modifier) => { + Box::new(modifier.get_positions(random, pos)) + } + PlacementModifier::NoiseThresholdCount(modifier) => modifier.get_positions(random, pos), + PlacementModifier::CountOnEveryLayer => Box::new(iter::empty()), + PlacementModifier::EnvironmentScan(modifier) => { + modifier.get_positions(chunk, block_registry, pos).await + } + PlacementModifier::Heightmap(modifier) => { + modifier.get_positions(chunk, min_y, height, random, pos) + } + PlacementModifier::HeightRange(modifier) => { + modifier.get_positions(min_y, height, random, pos) + } + PlacementModifier::InSquare(_) => SquarePlacementModifier::get_positions(random, pos), + PlacementModifier::RandomOffset(modifier) => modifier.get_positions(random, pos), + PlacementModifier::FixedPlacement => Box::new(iter::empty()), + } + } +} + +#[derive(Deserialize)] +pub struct NoiseBasedCountPlacementModifier { + noise_to_count_ratio: i32, + noise_factor: f64, + noise_offset: f64, +} + +impl CountPlacementModifierBase for NoiseBasedCountPlacementModifier { + fn get_count(&self, _random: &mut RandomGenerator, pos: BlockPos) -> i32 { + let noise = FOLIAGE_NOISE + .sample( + pos.0.x as f64 / self.noise_factor, + pos.0.z as f64 / self.noise_factor, + false, + ) + .max(0.0); // TODO: max is wrong + ((noise + self.noise_offset) * self.noise_to_count_ratio as f64).ceil() as i32 + } +} + +#[derive(Deserialize)] +pub struct NoiseThresholdCountPlacementModifier { + noise_level: f64, + below_noise: i32, + above_noise: i32, +} + +impl CountPlacementModifierBase for NoiseThresholdCountPlacementModifier { + fn get_count(&self, _random: &mut RandomGenerator, pos: BlockPos) -> i32 { + let noise = FOLIAGE_NOISE.sample(pos.0.x as f64 / 200.0, pos.0.z as f64 / 200.0, false); + if noise < self.noise_level { + self.below_noise + } else { + self.above_noise + } + } +} + +#[derive(Deserialize)] +pub struct EnvironmentScanPlacementModifier { + direction_of_search: BlockDirection, + target_condition: BlockPredicate, + allowed_search_condition: Option, + max_steps: i32, +} + +impl EnvironmentScanPlacementModifier { + pub async fn get_positions( + &self, + chunk: &ProtoChunk<'_>, + block_registry: &dyn BlockRegistryExt, + pos: BlockPos, + ) -> Box> { + let allowed_search_condition = self + .allowed_search_condition + .as_ref() + .unwrap_or(&BlockPredicate::AlwaysTrue); + + if !allowed_search_condition + .test(block_registry, chunk, &pos) + .await + { + return Box::new(iter::empty()); + } + let mut pos = pos; + for _ in 0..self.max_steps { + if self + .target_condition + .test(block_registry, chunk, &pos) + .await + { + return Box::new(iter::once(pos)); + } + pos = pos.offset(self.direction_of_search.to_offset()); + + if chunk.out_of_height(pos.0.y as i16) { + return Box::new(iter::empty()); + } + + if !allowed_search_condition + .test(block_registry, chunk, &pos) + .await + { + break; + } + } + if self + .target_condition + .test(block_registry, chunk, &pos) + .await + { + return Box::new(iter::once(pos)); + } + + Box::new(iter::empty()) + } +} + +#[derive(Deserialize)] +pub struct RandomOffsetPlacementModifier { + xz_spread: IntProvider, + y_spread: IntProvider, +} + +impl RandomOffsetPlacementModifier { + pub fn get_positions( + &self, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + let x = pos.0.x + self.xz_spread.get(random); + let y = pos.0.y + self.y_spread.get(random); + let z = pos.0.z + self.xz_spread.get(random); + Box::new(iter::once(BlockPos(Vector3::new(x, y, z)))) + } +} + +#[derive(Deserialize)] +pub struct BlockFilterPlacementModifier { + predicate: BlockPredicate, +} + +#[async_trait] +impl ConditionalPlacementModifier for BlockFilterPlacementModifier { + async fn should_place( + &self, + block_registry: &dyn BlockRegistryExt, + _feature: &str, + chunk: &ProtoChunk, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + self.predicate.test(block_registry, chunk, &pos).await + } +} + +#[derive(Deserialize)] +pub struct SurfaceThresholdFilterPlacementModifier { + heightmap: HeightMap, + min_inclusive: Option, + max_inclusive: Option, +} + +#[async_trait] +impl ConditionalPlacementModifier for SurfaceThresholdFilterPlacementModifier { + async fn should_place( + &self, + _block_registry: &dyn BlockRegistryExt, + _feature: &str, + chunk: &ProtoChunk, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let y = chunk.get_top_y(&self.heightmap, &pos.0.to_vec2_i32()); + let min = y.saturating_add(self.min_inclusive.unwrap_or(i32::MIN) as i64); + let max = y.saturating_add(self.max_inclusive.unwrap_or(i32::MAX) as i64); + min <= pos.0.y as i64 && pos.0.y as i64 <= max + } +} + +#[derive(Deserialize)] +pub struct RarityFilterPlacementModifier { + chance: u32, +} + +#[async_trait] +impl ConditionalPlacementModifier for RarityFilterPlacementModifier { + async fn should_place( + &self, + _block_registry: &dyn BlockRegistryExt, + _feature: &str, + _chunk: &ProtoChunk, + random: &mut RandomGenerator, + _pos: BlockPos, + ) -> bool { + random.next_f32() < 1.0 / self.chance as f32 + } +} + +#[derive(Deserialize)] +pub struct SquarePlacementModifier; + +impl SquarePlacementModifier { + pub fn get_positions( + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + let x = random.next_bounded_i32(16) + pos.0.x; + let z = random.next_bounded_i32(16) + pos.0.z; + Box::new(iter::once(BlockPos(Vector3::new(x, pos.0.y, z)))) + } +} + +#[derive(Deserialize)] +pub struct CountPlacementModifier { + count: IntProvider, +} + +impl CountPlacementModifierBase for CountPlacementModifier { + fn get_count(&self, random: &mut RandomGenerator, _pos: BlockPos) -> i32 { + self.count.get(random) + } +} + +#[derive(Deserialize)] +pub struct SurfaceWaterDepthFilterPlacementModifier { + max_water_depth: i32, +} + +#[async_trait] +impl ConditionalPlacementModifier for SurfaceWaterDepthFilterPlacementModifier { + async fn should_place( + &self, + _block_registry: &dyn BlockRegistryExt, + _feature: &str, + chunk: &ProtoChunk, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let world_top = chunk.top_block_height_exclusive(&Vector2::new(pos.0.x, pos.0.z)) as i32; + let ocean = chunk.ocean_floor_height_exclusive(&Vector2::new(pos.0.x, pos.0.z)) as i32; + world_top - ocean <= self.max_water_depth + } +} + +#[derive(Deserialize)] +pub struct BiomePlacementModifier; + +#[async_trait] +impl ConditionalPlacementModifier for BiomePlacementModifier { + async fn should_place( + &self, + _block_registry: &dyn BlockRegistryExt, + this_feature: &str, + chunk: &ProtoChunk, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + // we check if the current feature can be applied to the biome at the pos + let name = format!("minecraft:{this_feature}"); + let biome = chunk.get_biome_for_terrain_gen(&pos.0); + + for feature in biome.features { + if feature.contains(&name.deref()) { + return true; + } + } + false + } +} + +#[derive(Deserialize)] +pub struct HeightRangePlacementModifier { + height: HeightProvider, +} + +impl HeightRangePlacementModifier { + pub fn get_positions( + &self, + min_y: i8, + height: u16, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + let mut pos = pos; + pos.0.y = self.height.get(random, min_y, height); + Box::new(iter::once(pos)) + } +} + +#[derive(Deserialize)] +pub struct HeightmapPlacementModifier { + heightmap: HeightMap, +} + +impl HeightmapPlacementModifier { + pub fn get_positions( + &self, + chunk: &ProtoChunk, + min_y: i8, + _height: u16, + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + let x = pos.0.x; + let z = pos.0.z; + let top = chunk.get_top_y(&self.heightmap, &Vector2::new(x, z)) as i32; + if top > min_y as i32 { + return Box::new(iter::once(BlockPos(Vector3::new(x, top, z)))); + } + Box::new(iter::empty()) + } +} + +pub trait CountPlacementModifierBase { + fn get_positions( + &self, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + let count = self.get_count(random, pos); + Box::new(std::iter::repeat_n(pos, count as usize)) + } + + fn get_count(&self, random: &mut RandomGenerator, pos: BlockPos) -> i32; +} + +#[async_trait] +pub trait ConditionalPlacementModifier { + async fn get_positions( + &self, + block_registry: &dyn BlockRegistryExt, + chunk: &ProtoChunk, + feature: &str, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Box> { + if self + .should_place(block_registry, feature, chunk, random, pos) + .await + { + Box::new(iter::once(pos)) + } else { + Box::new(iter::empty()) + } + } + + async fn should_place( + &self, + block_registry: &dyn BlockRegistryExt, + feature: &str, + chunk: &ProtoChunk, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool; +} diff --git a/pumpkin-world/src/generation/feature/size.rs b/pumpkin-world/src/generation/feature/size.rs new file mode 100644 index 000000000..52d6d9dbc --- /dev/null +++ b/pumpkin-world/src/generation/feature/size.rs @@ -0,0 +1,64 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct FeatureSize { + pub min_clipped_height: Option, + #[serde(flatten)] + pub r#type: FeatureSizeType, +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +pub enum FeatureSizeType { + #[serde(rename = "minecraft:three_layers_feature_size")] + ThreeLayersFeatureSize(ThreeLayersFeatureSize), + #[serde(rename = "minecraft:two_layers_feature_size")] + TwoLayersFeatureSize(TwoLayersFeatureSize), +} + +impl FeatureSizeType { + pub fn get_radius(&self, height: u32, y: i32) -> i32 { + match self { + FeatureSizeType::ThreeLayersFeatureSize(three) => three.get_radius(height, y), + FeatureSizeType::TwoLayersFeatureSize(two) => two.get_radius(y), + } + } +} + +#[derive(Deserialize)] +pub struct TwoLayersFeatureSize { + limit: u8, + lower_size: u8, + upper_size: u8, +} + +impl TwoLayersFeatureSize { + pub fn get_radius(&self, y: i32) -> i32 { + if y < self.limit as i32 { + self.lower_size as i32 + } else { + self.upper_size as i32 + } + } +} + +#[derive(Deserialize)] +pub struct ThreeLayersFeatureSize { + limit: u8, + upper_limit: u8, + lower_size: u8, + middle_size: u8, + upper_size: u8, +} + +impl ThreeLayersFeatureSize { + pub fn get_radius(&self, height: u32, y: i32) -> i32 { + if y < self.limit as i32 { + self.lower_size as i32 + } else if y >= height as i32 - self.upper_limit as i32 { + self.upper_size as i32 + } else { + self.middle_size as i32 + } + } +} diff --git a/pumpkin-world/src/generation/generator.rs b/pumpkin-world/src/generation/generator.rs deleted file mode 100644 index 41b02d1b1..000000000 --- a/pumpkin-world/src/generation/generator.rs +++ /dev/null @@ -1,13 +0,0 @@ -use pumpkin_util::math::vector2::Vector2; - -use crate::chunk::ChunkData; -use crate::dimension::Dimension; -use crate::generation::Seed; - -pub trait GeneratorInit { - fn new(seed: Seed, dimension: Dimension) -> Self; -} - -pub trait WorldGenerator: Sync + Send { - fn generate_chunk(&self, at: &Vector2) -> ChunkData; -} diff --git a/pumpkin-world/src/generation/height_provider.rs b/pumpkin-world/src/generation/height_provider.rs index d5a2ac7bf..1e3c0b6c9 100644 --- a/pumpkin-world/src/generation/height_provider.rs +++ b/pumpkin-world/src/generation/height_provider.rs @@ -6,17 +6,44 @@ use super::y_offset::YOffset; #[derive(Deserialize)] #[serde(tag = "type")] pub enum HeightProvider { + #[serde(rename = "minecraft:uniform")] Uniform(UniformHeightProvider), + #[serde(rename = "minecraft:trapezoid")] + Trapezoid(TrapezoidHeightProvider), + #[serde(rename = "minecraft:very_biased_to_bottom")] + VeryBiasedToBottom(VeryBiasedToBottomHeightProvider), } impl HeightProvider { pub fn get(&self, random: &mut RandomGenerator, min_y: i8, height: u16) -> i32 { match self { - HeightProvider::Uniform(uniform) => uniform.get(random, min_y, height), + HeightProvider::Uniform(provider) => provider.get(random, min_y, height), + HeightProvider::Trapezoid(provider) => provider.get(random, min_y, height), + HeightProvider::VeryBiasedToBottom(provider) => provider.get(random, min_y, height), } } } +#[derive(Deserialize)] +pub struct VeryBiasedToBottomHeightProvider { + min_inclusive: YOffset, + max_inclusive: YOffset, + inner: Option, +} + +impl VeryBiasedToBottomHeightProvider { + pub fn get(&self, random: &mut RandomGenerator, min_y: i8, height: u16) -> i32 { + let min = self.min_inclusive.get_y(min_y, height) as i32; + let max = self.max_inclusive.get_y(min_y, height) as i32; + let inner = self.inner.unwrap_or(1) as i32; + + let min_rnd = random.next_inbetween_i32(min + inner, max); + let max_rnd = random.next_inbetween_i32(min, min_rnd - 1); + + random.next_inbetween_i32(min, max_rnd - 1 + inner) + } +} + #[derive(Deserialize)] pub struct UniformHeightProvider { min_inclusive: YOffset, @@ -28,6 +55,35 @@ impl UniformHeightProvider { let min = self.min_inclusive.get_y(min_y, height) as i32; let max = self.max_inclusive.get_y(min_y, height) as i32; - random.next_bounded_i32(max - min + 1) + min + random.next_inbetween_i32(min, max) + } +} + +#[derive(Deserialize)] +pub struct TrapezoidHeightProvider { + min_inclusive: YOffset, + max_inclusive: YOffset, + plateau: Option, +} + +impl TrapezoidHeightProvider { + pub fn get(&self, random: &mut RandomGenerator, min_y: i8, height: u16) -> i32 { + let plateau = self.plateau.unwrap_or(0); + let i = self.min_inclusive.get_y(min_y, height); + let j = self.max_inclusive.get_y(min_y, height); + + if i > j { + log::warn!("Empty height range"); + return i as i32; + } + + let k = j - i; + if plateau >= k as i32 { + return random.next_inbetween_i32(i as i32, j as i32); + } + + let l = (k as i32 - plateau) / 2; + let m = k as i32 - l; + i as i32 + random.next_inbetween_i32(0, m) + random.next_inbetween_i32(0, l) } } diff --git a/pumpkin-world/src/generation/implementation/mod.rs b/pumpkin-world/src/generation/implementation/mod.rs index e1968895c..153a25496 100644 --- a/pumpkin-world/src/generation/implementation/mod.rs +++ b/pumpkin-world/src/generation/implementation/mod.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; + +use async_trait::async_trait; use pumpkin_data::noise_router::{ END_BASE_NOISE_ROUTER, NETHER_BASE_NOISE_ROUTER, OVERWORLD_BASE_NOISE_ROUTER, }; @@ -8,17 +11,31 @@ use super::{ settings::gen_settings_from_dimension, }; use crate::chunk::format::LightContainer; +use crate::level::Level; +use crate::world::BlockRegistryExt; use crate::{chunk::ChunkLight, dimension::Dimension}; use crate::{ chunk::{ ChunkData, ChunkSections, SubChunk, palette::{BiomePalette, BlockPalette}, }, - generation::{ - GlobalRandomConfig, Seed, WorldGenerator, generator::GeneratorInit, proto_chunk::ProtoChunk, - }, + generation::{GlobalRandomConfig, Seed, proto_chunk::ProtoChunk}, }; +pub trait GeneratorInit { + fn new(seed: Seed, dimension: Dimension) -> Self; +} + +#[async_trait] +pub trait WorldGenerator: Sync + Send { + async fn generate_chunk( + &self, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + at: &Vector2, + ) -> ChunkData; +} + pub struct VanillaGenerator { random_config: GlobalRandomConfig, base_router: ProtoNoiseRouters, @@ -44,8 +61,14 @@ impl GeneratorInit for VanillaGenerator { } } +#[async_trait] impl WorldGenerator for VanillaGenerator { - fn generate_chunk(&self, at: &Vector2) -> ChunkData { + async fn generate_chunk( + &self, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + at: &Vector2, + ) -> ChunkData { let generation_settings = gen_settings_from_dimension(&self.dimension); let sub_chunks = generation_settings.shape.height as usize / BlockPalette::SIZE; @@ -61,6 +84,7 @@ impl WorldGenerator for VanillaGenerator { proto_chunk.populate_biomes(self.dimension); proto_chunk.populate_noise(); proto_chunk.build_surface(); + proto_chunk.generate_features(level, block_registry).await; for y in 0..biome_coords::from_block(generation_settings.shape.height) { for z in 0..BiomePalette::SIZE { diff --git a/pumpkin-world/src/generation/mod.rs b/pumpkin-world/src/generation/mod.rs index a8048a4e3..bf6b2a80f 100644 --- a/pumpkin-world/src/generation/mod.rs +++ b/pumpkin-world/src/generation/mod.rs @@ -3,32 +3,33 @@ pub mod aquifer_sampler; mod biome; mod blender; +mod block_predicate; +mod block_state_provider; pub mod carver; pub mod chunk_noise; -mod generator; +mod feature; pub mod height_limit; pub mod height_provider; -mod implementation; +pub mod implementation; pub mod noise; pub mod noise_router; pub mod ore_sampler; pub mod positions; pub mod proto_chunk; +pub mod rule; +mod rule_test; mod seed; pub mod settings; mod surface; pub mod y_offset; use derive_getters::Getters; -pub use generator::WorldGenerator; -use implementation::VanillaGenerator; +use implementation::{GeneratorInit, VanillaGenerator, WorldGenerator}; use pumpkin_util::random::{ RandomDeriver, RandomDeriverImpl, RandomImpl, legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, }; pub use seed::Seed; -use generator::GeneratorInit; - use crate::dimension::Dimension; pub fn get_world_gen(seed: Seed, dimension: Dimension) -> Box { diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index b59e744ce..f6a5319cb 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -1,18 +1,30 @@ +use std::sync::Arc; + +use async_trait::async_trait; use pumpkin_data::{ - BlockState, - block_properties::{blocks_movement, get_block_by_state_id}, + Block, BlockState, + block_properties::{ + blocks_movement, get_block_and_state_by_state_id, get_block_by_state_id, + get_state_by_state_id, + }, chunk::Biome, tag::Tagable, }; use pumpkin_macros::default_block_state; -use pumpkin_util::math::{vector2::Vector2, vector3::Vector3}; +use pumpkin_util::{ + math::{position::BlockPos, vector2::Vector2, vector3::Vector3}, + random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro}, +}; use crate::{ + HeightMap, biome::{BiomeSupplier, MultiNoiseBiomeSupplier, hash_seed}, block::RawBlockState, chunk::CHUNK_AREA, dimension::Dimension, generation::{biome, positions::chunk_pos}, + level::Level, + world::{BlockAccessor, BlockRegistryExt}, }; use super::{ @@ -20,6 +32,7 @@ use super::{ aquifer_sampler::{FluidLevel, FluidLevelSampler, FluidLevelSamplerImpl}, biome_coords, chunk_noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, + feature::placed_features::PLACED_FEATURES, height_limit::HeightLimitView, noise_router::{ multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, @@ -92,7 +105,7 @@ impl FluidLevelSamplerImpl for StandardChunkFluidLevelSampler { /// 12. full: Generation is done and a chunk can now be loaded. The proto-chunk is now converted to a level chunk and all block updates deferred in the above steps are executed. /// pub struct ProtoChunk<'a> { - chunk_pos: Vector2, + pub chunk_pos: Vector2, pub noise_sampler: ChunkNoiseGenerator<'a>, // TODO: These can technically go to an even higher level and we can reuse them across chunks pub multi_noise_sampler: MultiNoiseSampler<'a>, @@ -247,6 +260,19 @@ impl<'a> ProtoChunk<'a> { } } + pub fn get_top_y(&self, heightmap: &HeightMap, pos: &Vector2) -> i64 { + match heightmap { + HeightMap::WorldSurfaceWg => self.top_block_height_exclusive(pos), + HeightMap::WorldSurface => self.top_block_height_exclusive(pos), + HeightMap::OceanFloorWg => self.ocean_floor_height_exclusive(pos), + HeightMap::OceanFloor => self.ocean_floor_height_exclusive(pos), + HeightMap::MotionBlocking => self.top_motion_blocking_block_height_exclusive(pos), + HeightMap::MotionBlockingNoLeaves => { + self.top_motion_blocking_block_no_leaves_height_exclusive(pos) + } + } + } + pub fn top_block_height_exclusive(&self, pos: &Vector2) -> i64 { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; @@ -284,7 +310,8 @@ impl<'a> ProtoChunk<'a> { #[cfg(debug_assertions)] { assert!(local_pos.x >= 0 && local_pos.x <= 15); - assert!(local_pos.y < self.height() as i32 && local_pos.y >= 0); + assert!(local_pos.y < self.height() as i32); + assert!(local_pos.y >= 0); assert!(local_pos.z >= 0 && local_pos.z <= 15); } self.height() as usize * CHUNK_DIM as usize * local_pos.x as usize @@ -315,6 +342,12 @@ impl<'a> ProtoChunk<'a> { + local_biome_pos.z as usize } + #[inline] + pub fn is_air(&self, local_pos: &Vector3) -> bool { + let state = self.get_block_state(local_pos).to_state(); + state.is_air() + } + #[inline] pub fn get_block_state(&self, local_pos: &Vector3) -> RawBlockState { let local_pos = Vector3::new( @@ -322,33 +355,35 @@ impl<'a> ProtoChunk<'a> { local_pos.y - self.bottom_y() as i32, local_pos.z & 15, ); + if local_pos.y < 0 || local_pos.y >= self.height() as i32 { + return RawBlockState::AIR; + } let index = self.local_pos_to_block_index(&local_pos); self.flat_block_map[index] } - pub fn set_block_state(&mut self, local_pos: &Vector3, block_state: BlockState) { + pub fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState) { + let local_pos = Vector3::new(pos.x & 15, pos.y - self.bottom_y() as i32, pos.z & 15); + if local_pos.y < 0 || local_pos.y >= self.height() as i32 { + return; + } if !block_state.is_air() { - self.maybe_update_surface_height_map(local_pos); + self.maybe_update_surface_height_map(pos); } - if blocks_movement(&block_state) { - self.maybe_update_ocean_floor_height_map(local_pos); + if blocks_movement(block_state) { + self.maybe_update_ocean_floor_height_map(pos); } - if blocks_movement(&block_state) || block_state.is_liquid() { - self.maybe_update_motion_blocking_height_map(local_pos); + if blocks_movement(block_state) || block_state.is_liquid() { + self.maybe_update_motion_blocking_height_map(pos); if let Some(block) = get_block_by_state_id(block_state.id) { if !block.is_tagged_with("minecraft:leaves").unwrap() { - self.maybe_update_motion_blocking_no_leaves_height_map(local_pos); + self.maybe_update_motion_blocking_no_leaves_height_map(pos); } } } - let local_pos = Vector3::new( - local_pos.x & 15, - local_pos.y - self.bottom_y() as i32, - local_pos.z & 15, - ); let index = self.local_pos_to_block_index(&local_pos); self.flat_block_map[index] = RawBlockState { state_id: block_state.id, @@ -363,7 +398,6 @@ impl<'a> ProtoChunk<'a> { global_biome_pos.z & biome_coords::from_block(15), ); let index = self.local_biome_pos_to_biome_index(&local_pos); - self.flat_biome_map[index] } @@ -493,7 +527,7 @@ impl<'a> ProtoChunk<'a> { .unwrap_or(self.default_block); self.set_block_state( &Vector3::new(block_x, block_y, block_z), - block_state.to_state(), + &block_state.to_state(), ); } } @@ -505,7 +539,7 @@ impl<'a> ProtoChunk<'a> { } } - fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3) -> &'static Biome { + pub fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3) -> &'static Biome { let seed_biome_pos = biome::get_biome_blend( self.bottom_y(), self.height(), @@ -516,6 +550,10 @@ impl<'a> ProtoChunk<'a> { self.get_biome(&seed_biome_pos) } + /// Constructs the terrain surface, although "surface" is a misnomer as it also places underground blocks like bedrock and deepslate. + /// This stage also generates larger decorative structures, such as badlands pillars and icebergs. + /// + /// It is crucial that biome assignments are determined before this process begins. pub fn build_surface(&mut self) { let start_x = chunk_pos::start_block_x(&self.chunk_pos); let start_z = chunk_pos::start_block_z(&self.chunk_pos); @@ -615,7 +653,7 @@ impl<'a> ProtoChunk<'a> { let new_state = self.settings.surface_rule.try_apply(self, &mut context); if let Some(state) = new_state { - self.set_block_state(&pos, state.to_state()); + self.set_block_state(&pos, &state); } } } @@ -640,6 +678,55 @@ impl<'a> ProtoChunk<'a> { } } + /// This generates "Features," also known as decorations, which include things like trees, grass, ores, and more. + /// Essentially, it encompasses everything above the surface or underground. It's crucial that this step is executed after biomes are generated, + /// as the decoration directly depends on the biome. Similarly, running this after the surface is built is logical, as it often involves checking block types. + /// For example, flowers are typically placed only on grass blocks. + /// + /// Features are defined across two separate asset files, each serving a distinct purpose: + /// + /// 1. First, we determine **whether** to generate a feature and **at which block positions** to place it. + /// 2. Then, using the second file, we determine **how** to generate the feature. + pub async fn generate_features( + &mut self, + level: &Arc, + block_registry: &dyn BlockRegistryExt, + ) { + let chunk_pos = self.chunk_pos; + let min_y = self.noise_sampler.min_y(); + let height = self.noise_sampler.height(); + + let bottom_section = section_coords::block_to_section(min_y) as i32; + let block_pos = BlockPos(Vector3::new( + section_coords::section_to_block(chunk_pos.x), + bottom_section, + section_coords::section_to_block(chunk_pos.z), + )); + + let population_seed = + Xoroshiro::get_population_seed(self.random_config.seed, block_pos.0.x, block_pos.0.z); + + // TODO: This needs to be different depending on what biomes are in the chunk -> affects the + // random + for (name, feature) in PLACED_FEATURES.iter() { + // TODO: Properly set index and step + let decorator_seed = get_decorator_seed(population_seed, 0, 0); + let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed)); + feature + .generate( + self, + level, + block_registry, + min_y, + height, + name, + &mut random, + block_pos, + ) + .await; + } + } + fn start_cell_x(&self) -> i32 { self.start_block_x() / self.noise_sampler.horizontal_cell_block_count() as i32 } @@ -657,6 +744,28 @@ impl<'a> ProtoChunk<'a> { } } +#[async_trait] +impl BlockAccessor for ProtoChunk<'_> { + async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block { + self.get_block_state(&position.0).to_block() + } + + async fn get_block_state(&self, position: &BlockPos) -> pumpkin_data::BlockState { + self.get_block_state(&position.0).to_state() + } + + async fn get_block_and_block_state( + &self, + position: &BlockPos, + ) -> (pumpkin_data::Block, pumpkin_data::BlockState) { + let id = self.get_block_state(&position.0).state_id; + get_block_and_state_by_state_id(id).unwrap_or(( + Block::AIR, + get_state_by_state_id(Block::AIR.default_state_id).unwrap(), + )) + } +} + #[cfg(test)] mod test { use std::sync::LazyLock; diff --git a/pumpkin-world/src/generation/rule/block_match.rs b/pumpkin-world/src/generation/rule/block_match.rs new file mode 100644 index 000000000..7e2c5e639 --- /dev/null +++ b/pumpkin-world/src/generation/rule/block_match.rs @@ -0,0 +1,14 @@ +use pumpkin_data::{BlockState, block_properties::get_block_by_state_id}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct BlockMatchRuleTest { + // This should be a Block codec, so this is wrong + block: String, +} + +impl BlockMatchRuleTest { + pub fn test(&self, state: &BlockState) -> bool { + get_block_by_state_id(state.id).unwrap().name == self.block + } +} diff --git a/pumpkin-world/src/generation/rule/block_state_match.rs b/pumpkin-world/src/generation/rule/block_state_match.rs new file mode 100644 index 000000000..835efa809 --- /dev/null +++ b/pumpkin-world/src/generation/rule/block_state_match.rs @@ -0,0 +1,15 @@ +use pumpkin_data::BlockState; +use serde::Deserialize; + +use crate::block::BlockStateCodec; + +#[derive(Deserialize)] +pub struct BlockStateMatchRuleTest { + block_state: BlockStateCodec, +} + +impl BlockStateMatchRuleTest { + pub fn test(&self, state: &BlockState) -> bool { + state.id == self.block_state.get_state().unwrap().id + } +} diff --git a/pumpkin-world/src/generation/rule/mod.rs b/pumpkin-world/src/generation/rule/mod.rs new file mode 100644 index 000000000..7e7f78ba3 --- /dev/null +++ b/pumpkin-world/src/generation/rule/mod.rs @@ -0,0 +1,44 @@ +use block_match::BlockMatchRuleTest; +use block_state_match::BlockStateMatchRuleTest; +use pumpkin_data::BlockState; +use pumpkin_util::random::RandomGenerator; +use random_block_match::RandomBlockMatchRuleTest; +use random_block_state_match::RandomBlockStateMatchRuleTest; +use serde::Deserialize; +use tag_match::TagMatchRuleTest; + +mod block_match; +mod block_state_match; +mod random_block_match; +mod random_block_state_match; +mod tag_match; + +#[derive(Deserialize)] +#[serde(tag = "predicate_type")] +pub enum RuleTest { + #[serde(rename = "minecraft:always_true")] + AlwaysTrue, + #[serde(rename = "minecraft:block_match")] + BlockMatch(BlockMatchRuleTest), + #[serde(rename = "minecraft:blockstate_match")] + BlockStateMatch(BlockStateMatchRuleTest), + #[serde(rename = "minecraft:tag_match")] + TagMatch(TagMatchRuleTest), + #[serde(rename = "minecraft:random_block_match")] + RandomBlockMatch(RandomBlockMatchRuleTest), + #[serde(rename = "minecraft:random_blockstate_match")] + RandomBlockStateMatch(RandomBlockStateMatchRuleTest), +} + +impl RuleTest { + pub fn test(&self, state: &BlockState, random: &mut RandomGenerator) -> bool { + match self { + RuleTest::AlwaysTrue => true, + RuleTest::BlockMatch(rule) => rule.test(state), + RuleTest::BlockStateMatch(rule) => rule.test(state), + RuleTest::TagMatch(rule) => rule.test(state), + RuleTest::RandomBlockMatch(rule) => rule.test(state, random), + RuleTest::RandomBlockStateMatch(rule) => rule.test(state, random), + } + } +} diff --git a/pumpkin-world/src/generation/rule/random_block_match.rs b/pumpkin-world/src/generation/rule/random_block_match.rs new file mode 100644 index 000000000..31919e5c8 --- /dev/null +++ b/pumpkin-world/src/generation/rule/random_block_match.rs @@ -0,0 +1,17 @@ +use pumpkin_data::{BlockState, block_properties::get_block_by_state_id}; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct RandomBlockMatchRuleTest { + // This should be a Block codec, so this is wrong + block: String, + probability: f32, +} + +impl RandomBlockMatchRuleTest { + pub fn test(&self, state: &BlockState, random: &mut RandomGenerator) -> bool { + get_block_by_state_id(state.id).unwrap().name == self.block + && random.next_f32() < self.probability + } +} diff --git a/pumpkin-world/src/generation/rule/random_block_state_match.rs b/pumpkin-world/src/generation/rule/random_block_state_match.rs new file mode 100644 index 000000000..f7f6f16c2 --- /dev/null +++ b/pumpkin-world/src/generation/rule/random_block_state_match.rs @@ -0,0 +1,17 @@ +use pumpkin_data::BlockState; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use serde::Deserialize; + +use crate::block::BlockStateCodec; + +#[derive(Deserialize)] +pub struct RandomBlockStateMatchRuleTest { + block_state: BlockStateCodec, + probability: f32, +} + +impl RandomBlockStateMatchRuleTest { + pub fn test(&self, state: &BlockState, random: &mut RandomGenerator) -> bool { + state.id == self.block_state.get_state().unwrap().id && random.next_f32() < self.probability + } +} diff --git a/pumpkin-world/src/generation/rule/tag_match.rs b/pumpkin-world/src/generation/rule/tag_match.rs new file mode 100644 index 000000000..0070ddfd2 --- /dev/null +++ b/pumpkin-world/src/generation/rule/tag_match.rs @@ -0,0 +1,16 @@ +use pumpkin_data::{BlockState, block_properties::get_block_by_state_id, tag::Tagable}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct TagMatchRuleTest { + tag: String, +} + +impl TagMatchRuleTest { + pub fn test(&self, state: &BlockState) -> bool { + get_block_by_state_id(state.id) + .unwrap() + .is_tagged_with(&self.tag) + .unwrap() + } +} diff --git a/pumpkin-world/src/generation/rule_test.rs b/pumpkin-world/src/generation/rule_test.rs new file mode 100644 index 000000000..379650b80 --- /dev/null +++ b/pumpkin-world/src/generation/rule_test.rs @@ -0,0 +1,41 @@ +use pumpkin_data::{Block, tag::Tagable}; +use serde::Deserialize; + +/// Rule tests are used in structure or features generation to check if a block state matches some condition. +#[derive(Deserialize)] +pub enum RuleTest { + TagMatch(TagMatchTest), + BlockMatch(BlockMatchRuleTest), + // TODO: add more +} + +pub struct AlwaysTrueRuleTest; + +impl AlwaysTrueRuleTest { + pub fn test(&self, _block: Block) -> bool { + true + } +} + +#[derive(Deserialize)] +pub struct BlockMatchRuleTest { + block: String, +} + +impl BlockMatchRuleTest { + pub fn test(&self, block: Block) -> bool { + let test_block = Block::from_registry_key(&self.block).expect("Failed to find block"); + test_block == block + } +} + +#[derive(Deserialize)] +pub struct TagMatchTest { + tag: String, +} + +impl TagMatchTest { + pub fn test(&self, block: Block) -> bool { + block.is_tagged_with(&self.tag).unwrap() + } +} diff --git a/pumpkin-world/src/generation/surface/mod.rs b/pumpkin-world/src/generation/surface/mod.rs index e22f8f5a2..58fef5b85 100644 --- a/pumpkin-world/src/generation/surface/mod.rs +++ b/pumpkin-world/src/generation/surface/mod.rs @@ -300,7 +300,7 @@ pub fn estimate_surface_height( context.estimated_surface_heights[3] as f64, ) .floor() as i32; - context.surface_min_y = surface + context.run_depth - 8; + context.surface_min_y = surface.saturating_add(context.run_depth) - 8; } context.surface_min_y } diff --git a/pumpkin-world/src/generation/surface/rule.rs b/pumpkin-world/src/generation/surface/rule.rs index f563dc033..d5f3635f9 100644 --- a/pumpkin-world/src/generation/surface/rule.rs +++ b/pumpkin-world/src/generation/surface/rule.rs @@ -1,12 +1,8 @@ -use std::sync::OnceLock; - +use pumpkin_data::BlockState; use serde::Deserialize; use super::{MaterialCondition, MaterialRuleContext}; -use crate::{ - ProtoChunk, - block::{BlockStateCodec, RawBlockState}, -}; +use crate::{ProtoChunk, block::BlockStateCodec}; #[derive(Deserialize)] #[serde(tag = "type")] @@ -26,7 +22,7 @@ impl MaterialRule { &self, chunk: &mut ProtoChunk, context: &mut MaterialRuleContext, - ) -> Option { + ) -> Option { match self { MaterialRule::Badlands(badlands) => badlands.try_apply(context), MaterialRule::Block(block) => block.try_apply(), @@ -40,7 +36,7 @@ impl MaterialRule { pub struct BadLandsMaterialRule; impl BadLandsMaterialRule { - pub fn try_apply(&self, context: &mut MaterialRuleContext) -> Option { + pub fn try_apply(&self, context: &mut MaterialRuleContext) -> Option { Some( context .terrain_builder @@ -52,15 +48,11 @@ impl BadLandsMaterialRule { #[derive(Deserialize)] pub struct BlockMaterialRule { result_state: BlockStateCodec, - #[serde(skip)] - block_state: OnceLock>, } impl BlockMaterialRule { - pub fn try_apply(&self) -> Option { - *self - .block_state - .get_or_init(|| RawBlockState::new(&self.result_state.name)) + pub fn try_apply(&self) -> Option { + self.result_state.get_state() } } @@ -74,7 +66,7 @@ impl SequenceMaterialRule { &self, chunk: &mut ProtoChunk, context: &mut MaterialRuleContext, - ) -> Option { + ) -> Option { for seq in &self.sequence { if let Some(state) = seq.try_apply(chunk, context) { return Some(state); @@ -95,7 +87,7 @@ impl ConditionMaterialRule { &self, chunk: &mut ProtoChunk, context: &mut MaterialRuleContext, - ) -> Option { + ) -> Option { if self.if_true.test(chunk, context) { return self.then_run.try_apply(chunk, context); } diff --git a/pumpkin-world/src/generation/surface/terrain.rs b/pumpkin-world/src/generation/surface/terrain.rs index cff58c4e9..227bf8f50 100644 --- a/pumpkin-world/src/generation/surface/terrain.rs +++ b/pumpkin-world/src/generation/surface/terrain.rs @@ -1,4 +1,4 @@ -use pumpkin_data::chunk::Biome; +use pumpkin_data::{BlockState, chunk::Biome}; use pumpkin_macros::default_block_state; use pumpkin_util::{ math::vector3::Vector3, @@ -172,7 +172,7 @@ impl SurfaceTerrainBuilder { break; } - chunk.set_block_state(&pos, default_state.to_state()); + chunk.set_block_state(&pos, &default_state.to_state()); } } } @@ -247,17 +247,17 @@ impl SurfaceTerrainBuilder { && rand.next_f64() > 0.15) { if snow_blocks <= snow_block_count && y > snow_bottom { - chunk.set_block_state(&pos, Self::SNOW_BLOCK.to_state()); + chunk.set_block_state(&pos, &Self::SNOW_BLOCK.to_state()); snow_blocks += 1; } else { - chunk.set_block_state(&pos, Self::PACKED_ICE.to_state()); + chunk.set_block_state(&pos, &Self::PACKED_ICE.to_state()); } } } } } - pub fn get_terracotta_block(&self, pos: &Vector3) -> RawBlockState { + pub fn get_terracotta_block(&self, pos: &Vector3) -> BlockState { let offset = (self .terracotta_bands_offset_noise .sample(pos.x as f64, 0.0, pos.z as f64) @@ -266,5 +266,6 @@ impl SurfaceTerrainBuilder { let offset = pos.y + offset; self.terracotta_bands [(offset as usize + self.terracotta_bands.len()) % self.terracotta_bands.len()] + .to_state() } } diff --git a/pumpkin-world/src/generation/y_offset.rs b/pumpkin-world/src/generation/y_offset.rs index 01ef080db..351f45217 100644 --- a/pumpkin-world/src/generation/y_offset.rs +++ b/pumpkin-world/src/generation/y_offset.rs @@ -15,14 +15,14 @@ impl YOffset { YOffset::BelowTop(below_top) => { height as i16 - 1 + min_y as i16 - below_top.below_top as i16 } - YOffset::Absolute(absolute) => absolute.absolute as i16, + YOffset::Absolute(absolute) => absolute.absolute, } } } #[derive(Deserialize)] pub struct Absolute { - absolute: u16, + absolute: i16, } #[derive(Deserialize)] diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 841843cd6..08e82114f 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -10,22 +10,29 @@ use dashmap::{DashMap, Entry}; use log::trace; use num_traits::Zero; use pumpkin_config::{advanced_config, chunk::ChunkFormat}; +use pumpkin_data::Block; use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use tokio::{ - sync::{Mutex, Notify, RwLock, mpsc}, + select, + sync::{ + Mutex, Notify, RwLock, + mpsc::{self, UnboundedReceiver}, + }, task::JoinHandle, }; use tokio_util::task::TaskTracker; use crate::{ + BlockStateId, + block::RawBlockState, chunk::{ ChunkData, ChunkParsingError, ChunkReadingError, ScheduledTick, TickPriority, format::{anvil::AnvilChunkFile, linear::LinearFile}, io::{ChunkIO, LoadedData, chunk_file_manager::ChunkFileManager}, }, dimension::Dimension, - generation::{Seed, WorldGenerator, get_world_gen}, - world::SimpleWorld, + generation::{Seed, get_world_gen, implementation::WorldGenerator}, + world::{BlockRegistryExt, SimpleWorld}, }; pub type SyncChunk = Arc>; @@ -41,6 +48,7 @@ pub type SyncChunk = Arc>; /// For more details on world generation, refer to the `WorldGenerator` module. pub struct Level { pub seed: Seed, + block_registry: Arc, level_folder: LevelFolder, // Holds this level's spawn chunks, which are always loaded @@ -69,7 +77,12 @@ pub struct LevelFolder { } impl Level { - pub fn from_root_folder(root_folder: PathBuf, seed: i64, dimension: Dimension) -> Self { + pub fn from_root_folder( + root_folder: PathBuf, + block_registry: Arc, + seed: i64, + dimension: Dimension, + ) -> Self { // If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one let region_folder = root_folder.join("region"); if !region_folder.exists() { @@ -93,6 +106,7 @@ impl Level { Self { seed, + block_registry, world_gen, level_folder, chunk_saver, @@ -306,6 +320,100 @@ impl Level { } } + // Stream the chunks (don't collect them and then do stuff with them) + /// Spawns a tokio task to stream chunks. + /// Important: must be called from an async function (or changed to accept a tokio runtime + /// handle) + pub fn receive_chunks( + self: &Arc, + chunks: Vec>, + ) -> UnboundedReceiver<(SyncChunk, bool)> { + let (sender, receiver) = mpsc::unbounded_channel(); + // Put this in another thread so we aren't blocking on it + let level = self.clone(); + self.spawn_task(async move { + let cancel_notifier = level.shutdown_notifier.notified(); + let fetch_task = level.fetch_chunks(&chunks, sender); + + // Don't continue to handle chunks if we are shutting down + select! { + () = cancel_notifier => {}, + () = fetch_task => {} + }; + }); + + receiver + } + + pub async fn get_chunk( + self: &Arc, + chunk_coordinate: Vector2, + ) -> Arc> { + match self.try_get_chunk(chunk_coordinate) { + Some(chunk) => chunk.clone(), + None => self.receive_chunk(chunk_coordinate).await.0, + } + } + + pub async fn receive_chunk( + self: &Arc, + chunk_pos: Vector2, + ) -> (Arc>, bool) { + let mut receiver = self.receive_chunks(vec![chunk_pos]); + + receiver + .recv() + .await + .expect("Channel closed for unknown reason") + } + + pub async fn get_block_state(self: &Arc, position: &BlockPos) -> RawBlockState { + let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); + let chunk = self.get_chunk(chunk_coordinate).await; + + let chunk = chunk.read().await; + let Some(id) = chunk.section.get_block_absolute_y( + relative.x as usize, + relative.y, + relative.z as usize, + ) else { + return RawBlockState { + state_id: Block::AIR.default_state_id, + }; + }; + + RawBlockState { state_id: id } + } + + pub async fn set_block_state( + self: &Arc, + position: &BlockPos, + block_state_id: BlockStateId, + ) -> BlockStateId { + let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); + let chunk = self.get_chunk(chunk_coordinate).await; + let mut chunk = chunk.write().await; + + let replaced_block_state_id = chunk + .section + .get_block_absolute_y(relative.x as usize, relative.y, relative.z as usize) + .unwrap(); + + if replaced_block_state_id == block_state_id { + return block_state_id; + } + + chunk.dirty = true; + + chunk.section.set_block_absolute_y( + relative.x as usize, + relative.y, + relative.z as usize, + block_state_id, + ); + replaced_block_state_id + } + pub async fn write_chunks(&self, chunks_to_write: Vec<(Vector2, SyncChunk)>) { if chunks_to_write.is_empty() { return; @@ -478,6 +586,8 @@ impl Level { let loaded_chunks = self.loaded_chunks.clone(); let world_gen = self.world_gen.clone(); + let block_registry = self.block_registry.clone(); + let self_clone = self.clone(); let handle_generate = async move { let continue_to_generate = Arc::new(AtomicBool::new(true)); while let Some(pos) = generate_bridge_recv.recv().await { @@ -489,21 +599,32 @@ impl Level { let world_gen = world_gen.clone(); let channel = channel.clone(); let cloned_continue_to_generate = continue_to_generate.clone(); - rayon::spawn(move || { + let block_registry = block_registry.clone(); + let self_clone = self_clone.clone(); + + tokio::spawn(async move { // Rayon tasks are queued, so also check it here if !cloned_continue_to_generate.load(Ordering::Relaxed) { return; } - let result = loaded_chunks - .entry(pos) - .or_insert_with(|| { - // Avoid possible duplicating work by doing this within the dashmap lock - let generated_chunk = world_gen.generate_chunk(&pos); - Arc::new(RwLock::new(generated_chunk)) - }) + let result = { + let entry = loaded_chunks.entry(pos); // Get the entry for the position + + // Check if the entry already exists. + // If not, generate the chunk asynchronously and insert it. + match entry { + Entry::Occupied(entry) => entry.into_ref(), + Entry::Vacant(entry) => { + let generated_chunk = world_gen + .generate_chunk(&self_clone, block_registry.as_ref(), &pos) + .await; + entry.insert(Arc::new(RwLock::new(generated_chunk))) + } + } .value() - .clone(); + .clone() + }; if !send_chunk(true, result, &channel) { // Stop any additional queued generations diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index 71a31f8a9..493ac91fa 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -20,6 +20,17 @@ pub mod world_info; pub type BlockId = u16; pub type BlockStateId = u16; +#[derive(Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum HeightMap { + WorldSurfaceWg, + WorldSurface, + OceanFloorWg, + OceanFloor, + MotionBlocking, + MotionBlockingNoLeaves, +} + #[macro_export] macro_rules! global_path { ($path:expr) => {{ @@ -49,6 +60,7 @@ pub use generation::{ GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters, proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting, }; +use serde::Deserialize; pub fn bench_create_and_populate_noise( base_router: &ProtoNoiseRouters, diff --git a/pumpkin-world/src/world.rs b/pumpkin-world/src/world.rs index 13d587db8..f8dabe326 100644 --- a/pumpkin-world/src/world.rs +++ b/pumpkin-world/src/world.rs @@ -36,7 +36,7 @@ impl std::fmt::Display for GetBlockError { } #[async_trait] -pub trait SimpleWorld: Send + Sync { +pub trait SimpleWorld: BlockAccessor + Send + Sync { async fn set_block_state( self: Arc, position: &BlockPos, @@ -44,8 +44,6 @@ pub trait SimpleWorld: Send + Sync { flags: BlockFlags, ) -> BlockStateId; - async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block; - async fn update_neighbor( self: Arc, neighbor_block_pos: &BlockPos, @@ -60,3 +58,26 @@ pub trait SimpleWorld: Send + Sync { async fn remove_block_entity(&self, block_pos: &BlockPos); } + +#[async_trait] +pub trait BlockRegistryExt: Send + Sync { + async fn can_place_at( + &self, + block: &pumpkin_data::Block, + block_accessor: &dyn BlockAccessor, + block_pos: &BlockPos, + face: BlockDirection, + ) -> bool; +} + +#[async_trait] +pub trait BlockAccessor: Send + Sync { + async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block; + + async fn get_block_state(&self, position: &BlockPos) -> pumpkin_data::BlockState; + + async fn get_block_and_block_state( + &self, + position: &BlockPos, + ) -> (pumpkin_data::Block, pumpkin_data::BlockState); +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 0c01b7731..b3a1a794f 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -42,7 +42,7 @@ serde_json.workspace = true bytes.workspace = true -rand = "0.9" +rand = "0.8" num-bigint = "0.4" @@ -50,7 +50,7 @@ num-bigint = "0.4" rustyline-async = "0.4.6" # encryption -rsa = { version = "=0.10.0-rc.0", features = ["sha1"] } +rsa = { version = "0.9", features = ["sha1"] } rsa-der = "0.3" # authentication @@ -61,11 +61,11 @@ reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", ] } -sha1 = "=0.11.0-rc.0" -sha2 = "=0.11.0-rc.0" +sha1 = "0.10" # velocity en -hmac = "=0.13.0-rc.0" +hmac = "0.12" +sha2 = "0.10" base64 = "0.22" diff --git a/pumpkin/src/block/blocks/bamboo.rs b/pumpkin/src/block/blocks/bamboo.rs new file mode 100644 index 000000000..3c85ed7df --- /dev/null +++ b/pumpkin/src/block/blocks/bamboo.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +#[pumpkin_block("minecraft:bamboo")] +pub struct BambooBlock; + +#[async_trait] +impl PumpkinBlock for BambooBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below + .is_tagged_with("minecraft:bamboo_plantable_on") + .unwrap() + } +} diff --git a/pumpkin/src/block/blocks/bed.rs b/pumpkin/src/block/blocks/bed.rs index 27caab3f8..352841673 100644 --- a/pumpkin/src/block/blocks/bed.rs +++ b/pumpkin/src/block/blocks/bed.rs @@ -9,6 +9,7 @@ use pumpkin_data::tag::get_tag_values; use pumpkin_util::GameMode; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -37,21 +38,25 @@ impl BlockMetadata for BedBlock { impl PumpkinBlock for BedBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - let facing = player.living_entity.entity.get_horizontal_facing(); - - world.get_block_state(block_pos).await.replaceable() - && world - .get_block_state(&block_pos.offset(facing.to_offset())) - .await - .replaceable() + if let Some(player) = player { + let facing = player.living_entity.entity.get_horizontal_facing(); + let world = world.unwrap(); + return world.get_block_state(block_pos).await.replaceable() + && world + .get_block_state(&block_pos.offset(facing.to_offset())) + .await + .replaceable(); + } + false } async fn on_place( diff --git a/pumpkin/src/block/blocks/cactus.rs b/pumpkin/src/block/blocks/cactus.rs index 15a16253c..17312c964 100644 --- a/pumpkin/src/block/blocks/cactus.rs +++ b/pumpkin/src/block/blocks/cactus.rs @@ -12,7 +12,7 @@ use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; use pumpkin_world::chunk::TickPriority; -use pumpkin_world::world::BlockFlags; +use pumpkin_world::world::{BlockAccessor, BlockFlags}; use crate::block::pumpkin_block::PumpkinBlock; use crate::entity::EntityBase; @@ -26,7 +26,7 @@ pub struct CactusBlock; #[async_trait] impl PumpkinBlock for CactusBlock { async fn on_scheduled_tick(&self, world: &Arc, _block: &Block, pos: &BlockPos) { - if !can_place_at(world, pos).await { + if !can_place_at(world.as_ref(), pos).await { world.break_block(pos, None, BlockFlags::empty()).await; } } @@ -89,19 +89,20 @@ impl PumpkinBlock for CactusBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(block_accessor, block_pos).await } } -async fn can_place_at(world: &World, block_pos: &BlockPos) -> bool { +async fn can_place_at(world: &dyn BlockAccessor, block_pos: &BlockPos) -> bool { // TODO: use tags // Disallow to place any blocks nearby a cactus for direction in BlockDirection::horizontal() { diff --git a/pumpkin/src/block/blocks/dirt_path.rs b/pumpkin/src/block/blocks/dirt_path.rs index ea22c9eb0..5d0f25708 100644 --- a/pumpkin/src/block/blocks/dirt_path.rs +++ b/pumpkin/src/block/blocks/dirt_path.rs @@ -13,6 +13,7 @@ use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; use pumpkin_world::chunk::TickPriority; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; #[pumpkin_block("minecraft:dirt_path")] @@ -65,15 +66,16 @@ impl PumpkinBlock for DirtPathBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/doors.rs b/pumpkin/src/block/blocks/doors.rs index 22cb4db11..5bc9d0292 100644 --- a/pumpkin/src/block/blocks/doors.rs +++ b/pumpkin/src/block/blocks/doors.rs @@ -14,6 +14,7 @@ use pumpkin_data::tag::Tagable; use pumpkin_data::tag::get_tag_values; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -190,15 +191,16 @@ impl PumpkinBlock for DoorBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(world.unwrap(), block_pos).await } async fn placed( diff --git a/pumpkin/src/block/blocks/farmland.rs b/pumpkin/src/block/blocks/farmland.rs index f72153765..4a067671c 100644 --- a/pumpkin/src/block/blocks/farmland.rs +++ b/pumpkin/src/block/blocks/farmland.rs @@ -13,6 +13,7 @@ use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; use pumpkin_world::chunk::TickPriority; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; #[pumpkin_block("minecraft:farmland")] @@ -64,15 +65,16 @@ impl PumpkinBlock for FarmLandBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/fire/fire.rs b/pumpkin/src/block/blocks/fire/fire.rs index afa1792bb..b64350723 100644 --- a/pumpkin/src/block/blocks/fire/fire.rs +++ b/pumpkin/src/block/blocks/fire/fire.rs @@ -1,6 +1,7 @@ use pumpkin_data::block_properties::HorizontalAxis; use pumpkin_data::entity::EntityType; use pumpkin_registry::DimensionType; +use pumpkin_world::world::BlockAccessor; use rand::Rng; use std::sync::Arc; use std::sync::atomic::Ordering; @@ -27,7 +28,7 @@ pub struct FireBlock; impl FireBlock { pub fn get_fire_tick_delay() -> i32 { - 30 + rand::rng().random_range(0..10) + 30 + rand::thread_rng().gen_range(0..10) } } @@ -82,7 +83,7 @@ impl PumpkinBlock for FireBlock { if ticks < 0 { base_entity.fire_ticks.store(ticks + 1, Ordering::Relaxed); } else if base_entity.entity_type == EntityType::PLAYER { - let rnd_ticks = rand::rng().random_range(1..3); + let rnd_ticks = rand::thread_rng().gen_range(1..3); base_entity .fire_ticks .store(ticks + rnd_ticks, Ordering::Relaxed); @@ -112,15 +113,16 @@ impl PumpkinBlock for FireBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - FireBlockBase::can_place_at(world, block_pos).await + FireBlockBase::can_place_at(block_accessor, block_pos).await } async fn broken( diff --git a/pumpkin/src/block/blocks/fire/mod.rs b/pumpkin/src/block/blocks/fire/mod.rs index 7aac040d7..9a639f755 100644 --- a/pumpkin/src/block/blocks/fire/mod.rs +++ b/pumpkin/src/block/blocks/fire/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use pumpkin_data::Block; use pumpkin_data::world::WorldEvent; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; use soul_fire::SoulFireBlock; use crate::world::World; @@ -31,9 +32,10 @@ impl FireBlockBase { true } - pub async fn can_place_at(world: &World, block_pos: &BlockPos) -> bool { - let block_state = world.get_block_state(block_pos).await; - block_state.is_air() && Self::can_place_on(&world.get_block(&block_pos.down()).await) + pub async fn can_place_at(block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool { + let block_state = block_accessor.get_block_state(block_pos).await; + block_state.is_air() + && Self::can_place_on(&block_accessor.get_block(&block_pos.down()).await) } async fn broken(world: Arc, block_pos: BlockPos) { diff --git a/pumpkin/src/block/blocks/fire/soul_fire.rs b/pumpkin/src/block/blocks/fire/soul_fire.rs index 1a4f18358..ee6e6559e 100644 --- a/pumpkin/src/block/blocks/fire/soul_fire.rs +++ b/pumpkin/src/block/blocks/fire/soul_fire.rs @@ -7,6 +7,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use crate::block::pumpkin_block::PumpkinBlock; use crate::entity::player::Player; @@ -47,16 +48,17 @@ impl PumpkinBlock for SoulFireBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - FireBlockBase::can_place_at(world, block_pos).await - && Self::is_soul_base(&world.get_block(&block_pos.down()).await) + FireBlockBase::can_place_at(block_accessor, block_pos).await + && Self::is_soul_base(&block_accessor.get_block(&block_pos.down()).await) } async fn broken( diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index be92be1dd..bf9a4ee0d 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -1,3 +1,4 @@ +pub mod bamboo; pub mod barrel; pub mod bed; pub mod cactus; @@ -14,11 +15,11 @@ pub mod furnace; pub mod glass_panes; pub mod iron_bars; pub mod jukebox; -pub mod lily_pad; pub mod logs; pub mod nether_portal; pub mod note; pub mod piston; +pub mod plant; pub mod pumpkin; pub mod redstone; pub mod signs; @@ -28,4 +29,5 @@ pub mod sugar_cane; pub mod tnt; pub mod torches; pub mod trapdoor; +pub mod vine; pub mod walls; diff --git a/pumpkin/src/block/blocks/plant/bush.rs b/pumpkin/src/block/blocks/plant/bush.rs new file mode 100644 index 000000000..c04a6d0d5 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/bush.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct BushBlock; + +impl BlockMetadata for BushBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["bush"] + } +} + +#[async_trait] +impl PumpkinBlock for BushBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/plant/dry_vegetation.rs b/pumpkin/src/block/blocks/plant/dry_vegetation.rs new file mode 100644 index 000000000..3a768b030 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/dry_vegetation.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct DryVegetationBlock; + +impl BlockMetadata for DryVegetationBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["dead_bush", "tall_dry_grass", "short_dry_grass"] + } +} + +#[async_trait] +impl PumpkinBlock for DryVegetationBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below + .is_tagged_with("minecraft:dry_vegetation_may_place_on") + .unwrap() + } +} diff --git a/pumpkin/src/block/blocks/plant/flower.rs b/pumpkin/src/block/blocks/plant/flower.rs new file mode 100644 index 000000000..828208208 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/flower.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::tag::{RegistryKey, Tagable, get_tag_values}; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct FlowerBlock; + +impl BlockMetadata for FlowerBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + get_tag_values(RegistryKey::Block, "c:flowers/small").unwrap() + } +} + +#[async_trait] +impl PumpkinBlock for FlowerBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/plant/flowerbed.rs b/pumpkin/src/block/blocks/plant/flowerbed.rs new file mode 100644 index 000000000..2c4b32007 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/flowerbed.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct FlowerbedBlock; + +impl BlockMetadata for FlowerbedBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["pink_petals", "wildflowers"] + } +} + +#[async_trait] +impl PumpkinBlock for FlowerbedBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/plant/leaf_litter.rs b/pumpkin/src/block/blocks/plant/leaf_litter.rs new file mode 100644 index 000000000..fea1bd398 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/leaf_litter.rs @@ -0,0 +1,40 @@ +use async_trait::async_trait; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct LeafLitterBlock; + +impl BlockMetadata for LeafLitterBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["leaf_litter"] + } +} + +#[async_trait] +impl PumpkinBlock for LeafLitterBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block_state(&block_pos.down()).await; + block_below.is_side_solid(BlockDirection::Up) + } +} diff --git a/pumpkin/src/block/blocks/lily_pad.rs b/pumpkin/src/block/blocks/plant/lily_pad.rs similarity index 100% rename from pumpkin/src/block/blocks/lily_pad.rs rename to pumpkin/src/block/blocks/plant/lily_pad.rs diff --git a/pumpkin/src/block/blocks/plant/mod.rs b/pumpkin/src/block/blocks/plant/mod.rs new file mode 100644 index 000000000..ddb4c53ff --- /dev/null +++ b/pumpkin/src/block/blocks/plant/mod.rs @@ -0,0 +1,10 @@ +pub mod bush; +pub mod dry_vegetation; +pub mod flower; +pub mod flowerbed; +pub mod leaf_litter; +pub mod lily_pad; +pub mod mushroom_plant; +pub mod sapling; +pub mod short_plant; +pub mod tall_plant; diff --git a/pumpkin/src/block/blocks/plant/mushroom_plant.rs b/pumpkin/src/block/blocks/plant/mushroom_plant.rs new file mode 100644 index 000000000..ce23ca4a9 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/mushroom_plant.rs @@ -0,0 +1,50 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct MushroomPlantBlock; + +impl BlockMetadata for MushroomPlantBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["brown_mushroom", "red_mushroom"] + } +} + +#[async_trait] +impl PumpkinBlock for MushroomPlantBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let (block_below, state) = block_accessor + .get_block_and_block_state(&block_pos.down()) + .await; + if block_below + .is_tagged_with("minecraft:mushroom_grow_block") + .unwrap() + { + return true; + } + // TODO + state.is_full_cube() + } +} diff --git a/pumpkin/src/block/blocks/plant/sapling.rs b/pumpkin/src/block/blocks/plant/sapling.rs new file mode 100644 index 000000000..2debd583c --- /dev/null +++ b/pumpkin/src/block/blocks/plant/sapling.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::tag::{RegistryKey, Tagable, get_tag_values}; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct SaplingBlock; + +impl BlockMetadata for SaplingBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + get_tag_values(RegistryKey::Block, "minecraft:saplings").unwrap() + } +} + +#[async_trait] +impl PumpkinBlock for SaplingBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/plant/short_plant.rs b/pumpkin/src/block/blocks/plant/short_plant.rs new file mode 100644 index 000000000..c5e329c27 --- /dev/null +++ b/pumpkin/src/block/blocks/plant/short_plant.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct ShortPlantBlock; + +impl BlockMetadata for ShortPlantBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &["short_grass", "fern"] + } +} + +#[async_trait] +impl PumpkinBlock for ShortPlantBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/plant/tall_plant.rs b/pumpkin/src/block/blocks/plant/tall_plant.rs new file mode 100644 index 000000000..31818863d --- /dev/null +++ b/pumpkin/src/block/blocks/plant/tall_plant.rs @@ -0,0 +1,67 @@ +use async_trait::async_trait; +use pumpkin_data::tag::Tagable; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +pub struct TallPlantBlock; + +impl BlockMetadata for TallPlantBlock { + fn namespace(&self) -> &'static str { + "minecraft" + } + + fn ids(&self) -> &'static [&'static str] { + &[ + "tall_grass", + "large_fern", + "pitcher_plant", + // TallFlowerBlocks + "sunflower", + "lilac", + "peony", + "rose_bush", + ] + } +} + +#[async_trait] +impl PumpkinBlock for TallPlantBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + let (block, state) = block_accessor.get_block_and_block_state(block_pos).await; + if let Some(props) = block.properties(state.id).map(|s| s.to_props()) { + if props + .iter() + .any(|(key, value)| key == "half" && value == "upper") + { + let (block, below_state) = block_accessor + .get_block_and_block_state(&block_pos.down()) + .await; + if let Some(props) = block.properties(below_state.id).map(|s| s.to_props()) { + let is_lower = props + .iter() + .any(|(key, value)| key == "half" && value == "lower"); + return below_state.id == state.id && is_lower; + } + } + } + let block_below = block_accessor.get_block(&block_pos.down()).await; + block_below.is_tagged_with("minecraft:dirt").unwrap() || block_below == Block::FARMLAND + } +} diff --git a/pumpkin/src/block/blocks/redstone/rails/activator_rail.rs b/pumpkin/src/block/blocks/redstone/rails/activator_rail.rs index 5f4879a9d..ee490f5a3 100644 --- a/pumpkin/src/block/blocks/redstone/rails/activator_rail.rs +++ b/pumpkin/src/block/blocks/redstone/rails/activator_rail.rs @@ -5,6 +5,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -76,14 +77,15 @@ impl PumpkinBlock for ActivatorRailBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_rail_at(world, block_pos).await + can_place_rail_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/redstone/rails/detector_rail.rs b/pumpkin/src/block/blocks/redstone/rails/detector_rail.rs index 9f2077193..57353c2db 100644 --- a/pumpkin/src/block/blocks/redstone/rails/detector_rail.rs +++ b/pumpkin/src/block/blocks/redstone/rails/detector_rail.rs @@ -5,6 +5,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -76,14 +77,15 @@ impl PumpkinBlock for DetectorRailBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_rail_at(world, block_pos).await + can_place_rail_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/redstone/rails/powered_rail.rs b/pumpkin/src/block/blocks/redstone/rails/powered_rail.rs index bb539cc8d..c20267b98 100644 --- a/pumpkin/src/block/blocks/redstone/rails/powered_rail.rs +++ b/pumpkin/src/block/blocks/redstone/rails/powered_rail.rs @@ -5,6 +5,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -76,14 +77,15 @@ impl PumpkinBlock for PoweredRailBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_rail_at(world, block_pos).await + can_place_rail_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/redstone/rails/rail.rs b/pumpkin/src/block/blocks/redstone/rails/rail.rs index 814d9cc39..033afa4de 100644 --- a/pumpkin/src/block/blocks/redstone/rails/rail.rs +++ b/pumpkin/src/block/blocks/redstone/rails/rail.rs @@ -7,6 +7,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -148,14 +149,15 @@ impl PumpkinBlock for RailBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_rail_at(world, block_pos).await + can_place_rail_at(world.unwrap(), block_pos).await } } diff --git a/pumpkin/src/block/blocks/redstone/redstone_torch.rs b/pumpkin/src/block/blocks/redstone/redstone_torch.rs index 5520b8fc3..578e14a45 100644 --- a/pumpkin/src/block/blocks/redstone/redstone_torch.rs +++ b/pumpkin/src/block/blocks/redstone/redstone_torch.rs @@ -15,6 +15,7 @@ use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; use pumpkin_world::chunk::TickPriority; +use pumpkin_world::world::BlockAccessor; use pumpkin_world::world::BlockFlags; type RWallTorchProps = pumpkin_data::block_properties::FurnaceLikeProperties; @@ -102,14 +103,16 @@ impl PumpkinBlock for RedstoneTorchBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { + let world = world.unwrap(); let support_block = world.get_block_state(&block_pos.down()).await; if support_block.is_center_solid(BlockDirection::Up) { return true; diff --git a/pumpkin/src/block/blocks/redstone/redstone_wire.rs b/pumpkin/src/block/blocks/redstone/redstone_wire.rs index a0616c581..5a9cb90d2 100644 --- a/pumpkin/src/block/blocks/redstone/redstone_wire.rs +++ b/pumpkin/src/block/blocks/redstone/redstone_wire.rs @@ -12,7 +12,7 @@ use pumpkin_macros::pumpkin_block; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; -use pumpkin_world::world::BlockFlags; +use pumpkin_world::world::{BlockAccessor, BlockFlags}; use crate::block::BlockIsReplacing; use crate::block::registry::BlockActionResult; @@ -31,15 +31,16 @@ pub struct RedstoneWireBlock; impl PumpkinBlock for RedstoneWireBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(world.unwrap(), block_pos).await } async fn on_place( diff --git a/pumpkin/src/block/blocks/sugar_cane.rs b/pumpkin/src/block/blocks/sugar_cane.rs index 8d03ec1f4..7d0a46363 100644 --- a/pumpkin/src/block/blocks/sugar_cane.rs +++ b/pumpkin/src/block/blocks/sugar_cane.rs @@ -12,7 +12,7 @@ use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; use pumpkin_world::chunk::TickPriority; -use pumpkin_world::world::BlockFlags; +use pumpkin_world::world::{BlockAccessor, BlockFlags}; use crate::block::pumpkin_block::PumpkinBlock; use crate::entity::player::Player; @@ -25,7 +25,7 @@ pub struct SugarCaneBlock; #[async_trait] impl PumpkinBlock for SugarCaneBlock { async fn on_scheduled_tick(&self, world: &Arc, _block: &Block, pos: &BlockPos) { - if !can_place_at(world, pos).await { + if !can_place_at(world.as_ref(), pos).await { world.break_block(pos, None, BlockFlags::empty()).await; } } @@ -75,20 +75,21 @@ impl PumpkinBlock for SugarCaneBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - can_place_at(world, block_pos).await + can_place_at(block_accessor, block_pos).await } } -async fn can_place_at(world: &World, block_pos: &BlockPos) -> bool { - let block_below = world.get_block(&block_pos.down()).await; +async fn can_place_at(block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool { + let block_below = block_accessor.get_block(&block_pos.down()).await; if block_below == Block::SUGAR_CANE { return true; @@ -98,7 +99,7 @@ async fn can_place_at(world: &World, block_pos: &BlockPos) -> bool { || block_below.is_tagged_with("minecraft:sand").unwrap() { for direction in HorizontalFacing::all() { - let block = world + let block = block_accessor .get_block(&block_pos.down().offset(direction.to_offset())) .await; diff --git a/pumpkin/src/block/blocks/tnt.rs b/pumpkin/src/block/blocks/tnt.rs index fb3fa54ce..fb001c596 100644 --- a/pumpkin/src/block/blocks/tnt.rs +++ b/pumpkin/src/block/blocks/tnt.rs @@ -98,7 +98,7 @@ impl PumpkinBlock for TNTBlock { entity .set_velocity(Vector3::new(-angle.sin() * 0.02, 0.2, -angle.cos() * 0.02)) .await; - let fuse = rand::rng().random_range(0..DEFAULT_FUSE / 4) + DEFAULT_FUSE / 8; + let fuse = rand::thread_rng().gen_range(0..DEFAULT_FUSE / 4) + DEFAULT_FUSE / 8; let tnt = Arc::new(TNTEntity::new(entity, DEFAULT_POWER, fuse)); world.spawn_entity(tnt).await; } diff --git a/pumpkin/src/block/blocks/torches.rs b/pumpkin/src/block/blocks/torches.rs index d324d7e41..f476e444b 100644 --- a/pumpkin/src/block/blocks/torches.rs +++ b/pumpkin/src/block/blocks/torches.rs @@ -8,6 +8,7 @@ use pumpkin_data::{Block, FacingExt, HorizontalFacingExt}; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; +use pumpkin_world::world::BlockAccessor; type WallTorchProps = pumpkin_data::block_properties::WallTorchLikeProperties; // Normal tourches don't have properties @@ -102,20 +103,21 @@ impl PumpkinBlock for TorchBlock { async fn can_place_at( &self, - _server: &Server, - world: &World, - _player: &Player, + _server: Option<&Server>, + world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { - let support_block = world.get_block_state(&block_pos.down()).await; + let support_block = block_accessor.get_block_state(&block_pos.down()).await; if support_block.is_center_solid(BlockDirection::Up) { return true; } for dir in BlockDirection::horizontal() { - if can_place_at(world, block_pos, dir).await { + if can_place_at(world.unwrap(), block_pos, dir).await { return true; } } diff --git a/pumpkin/src/block/blocks/vine.rs b/pumpkin/src/block/blocks/vine.rs new file mode 100644 index 000000000..04caaa8ca --- /dev/null +++ b/pumpkin/src/block/blocks/vine.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_macros::pumpkin_block; +use pumpkin_protocol::server::play::SUseItemOn; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockAccessor; + +use crate::block::pumpkin_block::PumpkinBlock; +use crate::entity::player::Player; +use crate::server::Server; +use crate::world::World; + +#[pumpkin_block("minecraft:vine")] +pub struct VineBlock; + +#[async_trait] +impl PumpkinBlock for VineBlock { + async fn can_place_at( + &self, + _server: Option<&Server>, + _world: Option<&World>, + block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, + _block: &Block, + block_pos: &BlockPos, + _face: BlockDirection, + _use_item_on: Option<&SUseItemOn>, + ) -> bool { + // TODO: This is bad and not vanilla, just a "hotfix" + for dir in BlockDirection::all() { + if !block_accessor + .get_block_state(&block_pos.offset(dir.to_offset())) + .await + .is_air() + { + return true; + } + } + false + } +} diff --git a/pumpkin/src/block/fluid/flowing.rs b/pumpkin/src/block/fluid/flowing.rs index 58af63226..f4b086e07 100644 --- a/pumpkin/src/block/fluid/flowing.rs +++ b/pumpkin/src/block/fluid/flowing.rs @@ -79,23 +79,6 @@ pub trait FlowingFluid { async fn can_convert_to_source(&self, world: &Arc) -> bool; - async fn is_waterlogged(&self, world: &Arc, pos: &BlockPos) -> Option { - let block = world.get_block(pos).await; - - let state_id = world.get_block_state_id(pos).await; - // Check if the block has waterlogged property and if it's true - if let Some(properties) = block.properties(state_id) { - if properties - .to_props() - .iter() - .any(|(key, value)| key == "waterlogged" && value == "true") - { - return Some(state_id); - } - } - None - } - fn is_same_fluid(&self, fluid: &Fluid, other_state_id: BlockStateId) -> bool { if let Some(other_fluid) = Fluid::from_state_id(other_state_id) { return fluid.id == other_fluid.id; @@ -106,9 +89,7 @@ pub trait FlowingFluid { async fn spread_fluid(&self, world: &Arc, fluid: &Fluid, block_pos: &BlockPos) { let block_state_id = world.get_block_state_id(block_pos).await; if let Some(new_fluid_state) = self.get_new_liquid(world, fluid, block_pos).await { - if new_fluid_state.to_state_id(fluid) != block_state_id - && self.is_waterlogged(world, block_pos).await.is_none() - { + if new_fluid_state.to_state_id(fluid) != block_state_id { world .set_block_state( block_pos, @@ -118,14 +99,6 @@ pub trait FlowingFluid { .await; } self.spread(world, fluid, block_pos, &new_fluid_state).await; - } else if self.is_waterlogged(world, block_pos).await.is_some() { - self.spread( - world, - fluid, - block_pos, - &FlowingFluidProperties::default(fluid), - ) - .await; } else { world .break_block(block_pos, None, BlockFlags::NOTIFY_NEIGHBORS) @@ -186,14 +159,6 @@ pub trait FlowingFluid { let neighbor_pos = block_pos.offset(direction.to_offset()); let neighbor_state_id = world.get_block_state_id(&neighbor_pos).await; - if fluid.default_state_index == Fluid::WATER.default_state_index - && self.is_waterlogged(world, &neighbor_pos).await.is_some() - { - source_count += 1; - highest_level = highest_level.max(8); - continue; - } - if !self.is_same_fluid(fluid, neighbor_state_id) { continue; } @@ -222,9 +187,7 @@ pub trait FlowingFluid { let above_pos = block_pos.up(); let above_state_id = world.get_block_state_id(&above_pos).await; - if self.is_same_fluid(fluid, above_state_id) - || self.is_waterlogged(world, &above_pos).await.is_some() - { + if self.is_same_fluid(fluid, above_state_id) { return Some(self.get_flowing(fluid, Level::L8, true).await); } @@ -407,9 +370,7 @@ pub trait FlowingFluid { state_id: BlockStateId, ) { //TODO Implement lava water mix - if self.is_waterlogged(world, pos).await.is_some() { - return; - } + world .set_block_state(pos, state_id, BlockFlags::NOTIFY_ALL) .await; diff --git a/pumpkin/src/block/loot.rs b/pumpkin/src/block/loot.rs index 03e24c642..38d735d3a 100644 --- a/pumpkin/src/block/loot.rs +++ b/pumpkin/src/block/loot.rs @@ -160,9 +160,9 @@ impl LootFunctionNumberProviderExt for LootFunctionNumberProvider { fn generate(&self) -> f32 { match self { Self::Constant { value } => *value, - Self::Uniform { min, max } => rand::rng().random_range(*min..=*max), + Self::Uniform { min, max } => rand::thread_rng().gen_range(*min..=*max), Self::Binomial { n, p } => (0..n.floor() as u32).fold(0.0, |c, _| { - if rand::rng().random_bool(f64::from(*p)) { + if rand::thread_rng().gen_bool(f64::from(*p)) { c + 1.0 } else { c diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index b0959d322..e79777f83 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -1,3 +1,4 @@ +use blocks::bamboo::BambooBlock; use blocks::barrel::BarrelBlock; use blocks::bed::BedBlock; use blocks::cactus::CactusBlock; @@ -11,13 +12,22 @@ use blocks::fire::fire::FireBlock; use blocks::fire::soul_fire::SoulFireBlock; use blocks::glass_panes::GlassPaneBlock; use blocks::iron_bars::IronBarsBlock; -use blocks::lily_pad::LilyPadBlock; use blocks::logs::LogBlock; use blocks::nether_portal::NetherPortalBlock; use blocks::note::NoteBlock; use blocks::piston::piston::PistonBlock; use blocks::piston::piston_extension::PistonExtensionBlock; use blocks::piston::piston_head::PistonHeadBlock; +use blocks::plant::bush::BushBlock; +use blocks::plant::dry_vegetation::DryVegetationBlock; +use blocks::plant::flower::FlowerBlock; +use blocks::plant::flowerbed::FlowerbedBlock; +use blocks::plant::leaf_litter::LeafLitterBlock; +use blocks::plant::lily_pad::LilyPadBlock; +use blocks::plant::mushroom_plant::MushroomPlantBlock; +use blocks::plant::sapling::SaplingBlock; +use blocks::plant::short_plant::ShortPlantBlock; +use blocks::plant::tall_plant::TallPlantBlock; use blocks::pumpkin::PumpkinBlock; use blocks::redstone::buttons::ButtonBlock; use blocks::redstone::observer::ObserverBlock; @@ -39,6 +49,7 @@ use blocks::stairs::StairBlock; use blocks::sugar_cane::SugarCaneBlock; use blocks::torches::TorchBlock; use blocks::trapdoor::TrapDoorBlock; +use blocks::vine::VineBlock; use blocks::walls::WallBlock; use blocks::{ chest::ChestBlock, furnace::FurnaceBlock, redstone::lever::LeverBlock, tnt::TNTBlock, @@ -52,6 +63,8 @@ use pumpkin_data::{Block, BlockState}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::random::get_seed; +use pumpkin_util::random::xoroshiro128::Xoroshiro; use pumpkin_world::BlockStateId; use pumpkin_world::item::ItemStack; use rand::Rng; @@ -76,6 +89,7 @@ pub fn default_registry() -> Arc { // Blocks manager.register(BedBlock); + manager.register(SaplingBlock); manager.register(CactusBlock); manager.register(ChestBlock); manager.register(CraftingTableBlock); @@ -89,16 +103,26 @@ pub fn default_registry() -> Arc { manager.register(IronBarsBlock); manager.register(JukeboxBlock); manager.register(LogBlock); + manager.register(BambooBlock); manager.register(SignBlock); manager.register(SlabBlock); manager.register(StairBlock); + manager.register(ShortPlantBlock); + manager.register(DryVegetationBlock); manager.register(LilyPadBlock); manager.register(SugarCaneBlock); + manager.register(VineBlock); manager.register(TNTBlock); + manager.register(BushBlock); + manager.register(FlowerBlock); manager.register(TorchBlock); manager.register(TrapDoorBlock); + manager.register(MushroomPlantBlock); + manager.register(FlowerbedBlock); + manager.register(LeafLitterBlock); manager.register(WallBlock); manager.register(NetherPortalBlock); + manager.register(TallPlantBlock); manager.register(NoteBlock); manager.register(PumpkinBlock); manager.register(CommandBlock); @@ -164,7 +188,13 @@ pub async fn drop_loot( if experience { if let Some(experience) = &block.experience { - let amount = experience.experience.get(); + // TODO: this is bad, this is ugly, this is used :D + let amount = + experience + .experience + .get(&mut pumpkin_util::random::RandomGenerator::Xoroshiro( + Xoroshiro::from_seed(get_seed()), + )); // TODO: Silk touch gives no exp if amount > 0 { ExperienceOrbEntity::spawn(world, pos.to_f64(), amount as u32).await; @@ -176,9 +206,9 @@ pub async fn drop_loot( async fn drop_stack(world: &Arc, pos: &BlockPos, stack: ItemStack) { let height = EntityType::ITEM.dimension[1] / 2.0; let pos = Vector3::new( - f64::from(pos.0.x) + 0.5 + rand::rng().random_range(-0.25..0.25), - f64::from(pos.0.y) + 0.5 + rand::rng().random_range(-0.25..0.25) - f64::from(height), - f64::from(pos.0.z) + 0.5 + rand::rng().random_range(-0.25..0.25), + f64::from(pos.0.x) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25), + f64::from(pos.0.y) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25) - f64::from(height), + f64::from(pos.0.z) + 0.5 + rand::thread_rng().gen_range(-0.25..0.25), ); let entity = world.create_entity(pos, EntityType::ITEM); diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index bbe5bfd6e..f90be7210 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -9,7 +9,7 @@ use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; -use pumpkin_world::world::BlockFlags; +use pumpkin_world::world::{BlockAccessor, BlockFlags}; use std::sync::Arc; use super::BlockIsReplacing; @@ -101,13 +101,14 @@ pub trait PumpkinBlock: Send + Sync { #[allow(clippy::too_many_arguments)] async fn can_place_at( &self, - _server: &Server, - _world: &World, - _player: &Player, + _server: Option<&Server>, + _world: Option<&World>, + _block_accessor: &dyn BlockAccessor, + _player: Option<&Player>, _block: &Block, _block_pos: &BlockPos, _face: BlockDirection, - _use_item_on: &SUseItemOn, + _use_item_on: Option<&SUseItemOn>, ) -> bool { true } diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index a24b43933..d11d10038 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -3,13 +3,14 @@ use crate::entity::EntityBase; use crate::entity::player::Player; use crate::server::Server; use crate::world::World; +use async_trait::async_trait; use pumpkin_data::fluid::Fluid; use pumpkin_data::item::Item; use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_protocol::server::play::SUseItemOn; use pumpkin_util::math::position::BlockPos; use pumpkin_world::BlockStateId; -use pumpkin_world::world::BlockFlags; +use pumpkin_world::world::{BlockAccessor, BlockFlags, BlockRegistryExt}; use std::collections::HashMap; use std::sync::Arc; @@ -29,6 +30,29 @@ pub struct BlockRegistry { fluids: HashMap, Arc>, } +#[async_trait] +impl BlockRegistryExt for BlockRegistry { + async fn can_place_at( + &self, + block: &pumpkin_data::Block, + block_accessor: &dyn BlockAccessor, + block_pos: &BlockPos, + face: BlockDirection, + ) -> bool { + self.can_place_at( + None, + None, + block_accessor, + None, + block, + block_pos, + face, + None, + ) + .await + } +} + impl BlockRegistry { pub fn register(&mut self, block: T) { self.blocks.insert(block.names(), Arc::new(block)); @@ -141,18 +165,28 @@ impl BlockRegistry { #[allow(clippy::too_many_arguments)] pub async fn can_place_at( &self, - server: &Server, - world: &World, - player: &Player, + server: Option<&Server>, + world: Option<&World>, + block_accessor: &dyn BlockAccessor, + player: Option<&Player>, block: &Block, block_pos: &BlockPos, face: BlockDirection, - use_item_on: &SUseItemOn, + use_item_on: Option<&SUseItemOn>, ) -> bool { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { return pumpkin_block - .can_place_at(server, world, player, block, block_pos, face, use_item_on) + .can_place_at( + server, + world, + block_accessor, + player, + block, + block_pos, + face, + use_item_on, + ) .await; } true diff --git a/pumpkin/src/command/commands/playsound.rs b/pumpkin/src/command/commands/playsound.rs index 7dbee50d0..89f2ce4c8 100644 --- a/pumpkin/src/command/commands/playsound.rs +++ b/pumpkin/src/command/commands/playsound.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use pumpkin_data::sound::SoundCategory; use pumpkin_util::text::TextComponent; -use rand::Rng; +use rand::{Rng, thread_rng}; use crate::command::{ CommandError, CommandExecutor, CommandSender, @@ -109,7 +109,7 @@ impl CommandExecutor for Executor { }; // Use same random seed for all targets to ensure sound synchronization - let seed = rand::rng().random::(); + let seed = thread_rng().r#gen::(); // Track how many players actually received the sound let mut players_who_heard = 0; diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 7b87b7254..bfc5aa3fc 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -6,13 +6,9 @@ use crossbeam::atomic::AtomicCell; use living::LivingEntity; use player::Player; use pumpkin_data::{ - Block, - block_properties::{ - Facing, HorizontalFacing, get_block_and_state_by_state_id, get_state_by_state_id, - }, + block_properties::{Facing, HorizontalFacing}, damage::DamageType, entity::{EntityPose, EntityType}, - fluid::Fluid, sound::{Sound, SoundCategory}, }; use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; @@ -651,21 +647,16 @@ impl Entity { for y in blockpos.0.y..=blockpos1.0.y { for z in blockpos.0.z..=blockpos1.0.z { let pos = BlockPos::new(x, y, z); - let id = world.get_block_state_id(&pos).await; - if let Some(fluid) = Fluid::from_state_id(id) { + let (block, state) = world.get_block_and_block_state(&pos).await; + world + .block_registry + .on_entity_collision(block, &world, entity, pos, state, server) + .await; + if let Ok(fluid) = world.get_fluid(&pos).await { world .block_registry .on_entity_collision_fluid(&fluid, entity) .await; - } else { - let (block, state) = get_block_and_state_by_state_id(id).unwrap_or(( - Block::AIR, - get_state_by_state_id(Block::AIR.default_state_id).unwrap(), - )); - world - .block_registry - .on_entity_collision(block, &world, entity, pos, state, server) - .await; } } } diff --git a/pumpkin/src/item/items/bucket.rs b/pumpkin/src/item/items/bucket.rs index 4e49a8294..629ea0f2c 100644 --- a/pumpkin/src/item/items/bucket.rs +++ b/pumpkin/src/item/items/bucket.rs @@ -2,20 +2,19 @@ use std::sync::Arc; use crate::entity::player::Player; use async_trait::async_trait; -use pumpkin_data::{Block, BlockState, fluid::Fluid, item::Item}; -use pumpkin_registry::DimensionType; -use pumpkin_util::{ - GameMode, - math::{position::BlockPos, vector3::Vector3}, -}; -use pumpkin_world::{inventory::Inventory, item::ItemStack, world::BlockFlags}; +use pumpkin_data::Block; +use pumpkin_data::fluid::Fluid; +use pumpkin_data::item::Item; +use pumpkin_util::GameMode; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector3::Vector3; +use pumpkin_world::world::BlockFlags; use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem}; use crate::world::World; pub struct EmptyBucketItem; pub struct FilledBucketItem; -pub struct MilkBucketItem; impl ItemMetadata for EmptyBucketItem { fn ids() -> Box<[u16]> { @@ -28,24 +27,21 @@ impl ItemMetadata for FilledBucketItem { [ Item::WATER_BUCKET.id, Item::LAVA_BUCKET.id, - Item::POWDER_SNOW_BUCKET.id, - Item::AXOLOTL_BUCKET.id, - Item::COD_BUCKET.id, - Item::SALMON_BUCKET.id, - Item::TROPICAL_FISH_BUCKET.id, - Item::PUFFERFISH_BUCKET.id, - Item::TADPOLE_BUCKET.id, + // TODO drink milk + // Item::MILK_BUCKET.id, + // TODO implement these buckets, and getting the item from the world + // Item::POWDER_SNOW_BUCKET.id, + // Item::AXOLOTL_BUCKET.id, + // Item::COD_BUCKET.id, + // Item::SALMON_BUCKET.id, + // Item::TROPICAL_FISH_BUCKET.id, + // Item::PUFFERFISH_BUCKET.id, + // Item::TADPOLE_BUCKET.id, ] .into() } } -impl ItemMetadata for MilkBucketItem { - fn ids() -> Box<[u16]> { - [Item::MILK_BUCKET.id].into() - } -} - fn get_start_and_end_pos(player: &Player) -> (Vector3, Vector3) { let start_pos = player.eye_position(); let (yaw, pitch) = player.rotation(); @@ -62,30 +58,6 @@ fn get_start_and_end_pos(player: &Player) -> (Vector3, Vector3) { (start_pos, end_pos) } -fn waterlogged_check(block: &Block, state: &BlockState) -> Option { - block.properties(state.id).and_then(|properties| { - properties - .to_props() - .into_iter() - .find(|p| p.0 == "waterlogged") - .map(|(_, value)| value == true.to_string()) - }) -} - -fn set_waterlogged(block: &Block, state: &BlockState, waterlogged: bool) -> u16 { - let original_props = &block.properties(state.id).unwrap().to_props(); - let mut props_vec: Vec<(&str, &str)> = Vec::with_capacity(original_props.len()); - let waterlogged = waterlogged.to_string(); - for (key, value) in original_props { - if key == "waterlogged" { - props_vec.push((key.as_str(), &waterlogged)); - } else { - props_vec.push((key.as_str(), value.as_str())); - } - } - block.from_properties(props_vec).unwrap().to_state_id(block) -} - #[async_trait] impl PumpkinItem for EmptyBucketItem { async fn normal_use(&self, _item: &Item, player: &Player) { @@ -95,101 +67,16 @@ impl PumpkinItem for EmptyBucketItem { let checker = async |pos: &BlockPos, world_inner: &Arc| { let state_id = world_inner.get_block_state_id(pos).await; - let block = Block::from_state_id(state_id).unwrap(); - - if state_id == Block::AIR.default_state_id { - return false; - } - - (block.id != Block::WATER.id && block.id != Block::LAVA.id) - || ((block.id == Block::WATER.id && state_id == Block::WATER.default_state_id) - || (block.id == Block::LAVA.id && state_id == Block::LAVA.default_state_id)) + state_id == Block::WATER.default_state_id || state_id == Block::LAVA.default_state_id }; - let Some((block_pos, direction)) = world.raycast(start_pos, end_pos, checker).await else { - return; - }; + let (block_pos, _) = world.raytrace(start_pos, end_pos, checker).await; - // let state_id = world.get_block_state_id(&block_pos).await; - // - // let block = Block::from_state_id(state_id).unwrap(); - let (block, state) = world.get_block_and_block_state(&block_pos).await; - - if block - .properties(state.id) - .and_then(|properties| { - properties - .to_props() - .into_iter() - .find(|p| p.0 == "waterlogged") - .map(|(_, value)| value == true.to_string()) - }) - .unwrap_or(false) - { - let state_id = set_waterlogged(&block, &state, false); + if let Some(pos) = block_pos { world - .set_block_state(&block_pos, state_id, BlockFlags::NOTIFY_NEIGHBORS) - .await; - world.schedule_fluid_tick(block.id, block_pos, 5).await; - } else if state.id == Block::LAVA.default_state_id - || state.id == Block::WATER.default_state_id - { - world - .break_block(&block_pos, None, BlockFlags::NOTIFY_NEIGHBORS) - .await; - world - .set_block_state( - &block_pos, - Block::AIR.default_state_id, - BlockFlags::NOTIFY_NEIGHBORS, - ) - .await; - } else { - let (block, state) = world - .get_block_and_block_state(&block_pos.offset(direction.to_offset())) - .await; - if waterlogged_check(&block, &state).is_some() { - let state_id = set_waterlogged(&block, &state, false); - world - .set_block_state( - &block_pos.offset(direction.to_offset()), - state_id, - BlockFlags::NOTIFY_NEIGHBORS, - ) - .await; - world - .schedule_fluid_tick(block.id, block_pos.offset(direction.to_offset()), 5) - .await; - } else { - return; - } - } - - let item = if state.id == Block::LAVA.default_state_id { - &Item::LAVA_BUCKET - } else { - &Item::WATER_BUCKET - }; - - if player.gamemode.load() == GameMode::Creative { - //Check if player already has the item in their inventory - for i in 0..player.inventory.main_inventory.len() { - if player.inventory.main_inventory[i].lock().await.item.id == item.id { - return; - } - } - //If not, add it to the inventory - let mut item_stack = ItemStack::new(1, item); - player - .inventory - .insert_stack_anywhere(&mut item_stack) - .await; - } else { - let item_stack = ItemStack::new(1, item); - player - .inventory - .set_stack(player.inventory.get_selected_slot().into(), item_stack) + .set_block_state(&pos, Block::AIR.id, BlockFlags::NOTIFY_NEIGHBORS) .await; + //TODO: Pickup in inv } } } @@ -197,6 +84,11 @@ impl PumpkinItem for EmptyBucketItem { #[async_trait] impl PumpkinItem for FilledBucketItem { async fn normal_use(&self, item: &Item, player: &Player) { + if item.id == Item::MILK_BUCKET.id { + // TODO implement milk bucket + return; + } + let world = player.world().await.clone(); let (start_pos, end_pos) = get_start_and_end_pos(player); let checker = async |pos: &BlockPos, world_inner: &Arc| { @@ -207,67 +99,24 @@ impl PumpkinItem for FilledBucketItem { state_id != Block::AIR.id }; - let Some((pos, direction)) = world.raycast(start_pos, end_pos, checker).await else { - return; - }; + let (block_pos, block_direction) = world.raytrace(start_pos, end_pos, checker).await; - if item.id != Item::LAVA_BUCKET.id && world.dimension_type == DimensionType::TheNether { - return; - } - let (block, state) = world.get_block_and_block_state(&pos).await; - if waterlogged_check(&block, &state).is_some() && item.id == Item::WATER_BUCKET.id { - let state_id = set_waterlogged(&block, &state, true); + if let (Some(pos), Some(direction)) = (block_pos, block_direction) { world - .set_block_state(&pos, state_id, BlockFlags::NOTIFY_NEIGHBORS) + .set_block_state( + &pos.offset(direction.to_offset()), + // Block::WATER.default_state_id, + if item.id == Item::WATER_BUCKET.id { + Block::WATER.default_state_id + } else { + Block::LAVA.default_state_id + }, + BlockFlags::NOTIFY_NEIGHBORS, + ) .await; - world.schedule_fluid_tick(block.id, pos, 5).await; - } else { - let (block, state) = world - .get_block_and_block_state(&pos.offset(direction.to_offset())) - .await; - - if waterlogged_check(&block, &state).is_some() { - if item.id == Item::LAVA_BUCKET.id { - return; - } - let state_id = set_waterlogged(&block, &state, true); - - world - .set_block_state( - &pos.offset(direction.to_offset()), - state_id, - BlockFlags::NOTIFY_NEIGHBORS, - ) - .await; - world - .schedule_fluid_tick(block.id, pos.offset(direction.to_offset()), 5) - .await; - } else if state.id == Block::AIR.default_state_id { - world - .set_block_state( - &pos.offset(direction.to_offset()), - if item.id == Item::LAVA_BUCKET.id { - Block::LAVA.default_state_id - } else { - Block::WATER.default_state_id - }, - BlockFlags::NOTIFY_NEIGHBORS, - ) - .await; - } else { - return; + if player.gamemode.load() != GameMode::Creative { + //TODO: Pickup in inv } } - - //TODO: Spawn entity if applicable - if player.gamemode.load() != GameMode::Creative { - let item_stack = ItemStack::new(1, &Item::BUCKET); - player - .inventory - .set_stack(player.inventory.get_selected_slot().into(), item_stack) - .await; - } } } - -//TODO: Implement MilkBucketItem diff --git a/pumpkin/src/item/items/flint_and_steel.rs b/pumpkin/src/item/items/flint_and_steel.rs index e1afde81d..697f3ff9d 100644 --- a/pumpkin/src/item/items/flint_and_steel.rs +++ b/pumpkin/src/item/items/flint_and_steel.rs @@ -32,7 +32,7 @@ impl PumpkinItem for FlintAndSteelItem { // TODO: check CampfireBlock, CandleBlock and CandleCakeBlock let world = player.world().await; let pos = location.offset(face.to_offset()); - if FireBlockBase::can_place_at(&world, &pos).await { + if FireBlockBase::can_place_at(world.as_ref(), &pos).await { let fire_block = FireBlockBase::get_fire_type(&world, &pos).await; world diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 6c10788d3..10709a3b2 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1429,17 +1429,14 @@ impl Player { world.add_block_entity(Arc::new(updated_sign)).await; } - pub async fn handle_use_item(&self, use_item: &SUseItem, server: &Server) { + pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Server) { if !self.has_client_loaded() { return; } let inventory = self.inventory(); let binding = inventory.held_item(); let held = binding.lock().await; - let item = held.item; - drop(held); - server.item_registry.on_use(item, self).await; - self.update_sequence(use_item.sequence.0); + server.item_registry.on_use(held.item, self).await; } pub async fn handle_set_held_item(&self, held: SSetHeldItem) { @@ -1683,13 +1680,14 @@ impl Player { if !server .block_registry .can_place_at( - server, - world, - self, + Some(server), + Some(world), + world.as_ref(), + Some(self), &block, &final_block_pos, final_face, - &use_item_on, + Some(&use_item_on), ) .await { diff --git a/pumpkin/src/net/proxy/velocity.rs b/pumpkin/src/net/proxy/velocity.rs index be2e26a03..40f9420ed 100644 --- a/pumpkin/src/net/proxy/velocity.rs +++ b/pumpkin/src/net/proxy/velocity.rs @@ -7,7 +7,7 @@ use std::{ }; use bytes::{BufMut, BytesMut}; -use hmac::{Hmac, KeyInit, Mac}; +use hmac::{Hmac, Mac}; use pumpkin_config::networking::proxy::VelocityConfig; use pumpkin_protocol::{ Property, client::login::CLoginPluginRequest, ser::NetworkReadExt, @@ -48,7 +48,7 @@ pub enum VelocityError { pub async fn velocity_login(client: &Client) { // TODO: Validate the packet transaction id from the plugin response with this - let velocity_message_id: i32 = rand::rng().random(); + let velocity_message_id: i32 = rand::thread_rng().r#gen(); let mut buf = BytesMut::new(); buf.put_u8(MAX_SUPPORTED_FORWARDING_VERSION); diff --git a/pumpkin/src/net/query.rs b/pumpkin/src/net/query.rs index c6e3c093d..e8617bbf8 100644 --- a/pumpkin/src/net/query.rs +++ b/pumpkin/src/net/query.rs @@ -90,7 +90,7 @@ async fn handle_packet( match raw_packet.packet_type { PacketType::Handshake => { if let Ok(packet) = SHandshake::decode(&mut raw_packet).await { - let challenge_token = rand::rng().random_range(1..=i32::MAX); + let challenge_token = rand::thread_rng().gen_range(1..=i32::MAX); let response = CHandshake { session_id: packet.session_id, challenge_token, diff --git a/pumpkin/src/plugin/loader/native.rs b/pumpkin/src/plugin/loader/native.rs index 079574d75..d8f319a1a 100644 --- a/pumpkin/src/plugin/loader/native.rs +++ b/pumpkin/src/plugin/loader/native.rs @@ -1,5 +1,6 @@ use std::any::Any; +#[cfg(not(target_family = "wasm"))] use libloading::Library; use super::{LoaderError, Path, Plugin, PluginLoader, PluginMetadata, async_trait}; diff --git a/pumpkin/src/server/key_store.rs b/pumpkin/src/server/key_store.rs index 215b03c65..76c32d355 100644 --- a/pumpkin/src/server/key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -1,5 +1,6 @@ use num_bigint::BigInt; use pumpkin_protocol::client::login::CEncryptionRequest; +use rand::rngs::OsRng; use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, traits::PublicKeyParts as _}; use sha1::Sha1; use sha2::Digest; @@ -18,8 +19,8 @@ impl KeyStore { let private_key = Self::generate_private_key(); let public_key_der = rsa_der::public_key_to_der( - &private_key.n().to_be_bytes(), - &private_key.e().to_be_bytes(), + &private_key.n().to_bytes_be(), + &private_key.e().to_bytes_be(), ) .into_boxed_slice(); Self { @@ -30,7 +31,7 @@ impl KeyStore { fn generate_private_key() -> RsaPrivateKey { // Found out that OsRng is faster than rand::thread_rng here - let mut rng = rand::rng(); + let mut rng = OsRng; // let pub_key = RsaPublicKey::from(&priv_key); RsaPrivateKey::new(&mut rng, 1024).expect("Failed to generate a key") diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index dbe5aa28e..fb1f72dc0 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -29,7 +29,7 @@ use pumpkin_world::world_info::anvil::{ AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME, }; use pumpkin_world::world_info::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter}; -use rand::seq::IndexedRandom; +use rand::prelude::SliceRandom; use rsa::RsaPublicKey; use std::fs; use std::net::IpAddr; @@ -142,14 +142,14 @@ impl Server { let seed = level_info.world_gen_settings.seed; log::info!("Loading Overworld: {seed}"); let overworld = World::load( - Dimension::Overworld.into_level(world_path.clone(), seed), + Dimension::Overworld.into_level(world_path.clone(), block_registry.clone(), seed), level_info.clone(), DimensionType::Overworld, block_registry.clone(), ); log::info!("Loading Nether: {seed}"); let nether = World::load( - Dimension::Nether.into_level(world_path.clone(), seed), + Dimension::Nether.into_level(world_path.clone(), block_registry.clone(), seed), level_info.clone(), DimensionType::TheNether, block_registry.clone(), @@ -456,7 +456,7 @@ impl Server { pub async fn get_random_player(&self) -> Option> { let players = self.get_all_players().await; - players.choose(&mut rand::rng()).map(Arc::<_>::clone) + players.choose(&mut rand::thread_rng()).map(Arc::<_>::clone) } /// Searches for a player by their UUID across all worlds. diff --git a/pumpkin/src/server/seasonal_events.rs b/pumpkin/src/server/seasonal_events.rs index 6a3e6194e..2f6feed6b 100644 --- a/pumpkin/src/server/seasonal_events.rs +++ b/pumpkin/src/server/seasonal_events.rs @@ -1,6 +1,6 @@ use chrono::{Datelike, Local}; use pumpkin_config::advanced_config; -use rand::seq::SliceRandom; +use rand::{seq::SliceRandom, thread_rng}; // In fact Mojang also has some Seasonal Events, so we can use that later to match Vanilla :D @@ -19,7 +19,7 @@ pub fn modify_chat_message(message: &str) -> Option { if words.is_empty() { return None; } - let mut rng = rand::rng(); + let mut rng = thread_rng(); words.shuffle(&mut rng); let result = words.join(" "); Some(result) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 8de4a9e41..947d36186 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -28,8 +28,8 @@ use bytes::{BufMut, Bytes}; use explosion::Explosion; use pumpkin_config::BasicConfiguration; use pumpkin_data::BlockDirection; -use pumpkin_data::fluid::Falling; -use pumpkin_data::fluid::FluidProperties; +use pumpkin_data::block_properties::{BlockProperties, Integer0To15, WaterLikeProperties}; +use pumpkin_data::entity::EffectType; use pumpkin_data::{ Block, block_properties::{ @@ -41,7 +41,6 @@ use pumpkin_data::{ sound::{Sound, SoundCategory}, world::{RAW, WorldEvent}, }; -use pumpkin_data::{block_properties::get_block_collision_shapes, entity::EffectType}; use pumpkin_macros::send_cancellable; use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; use pumpkin_protocol::client::play::{ @@ -69,26 +68,22 @@ use pumpkin_registry::DimensionType; use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3}; use pumpkin_util::math::{position::chunk_section_from_pos, vector2::Vector2}; use pumpkin_util::text::{TextComponent, color::NamedColor}; -use pumpkin_world::chunk::ChunkData; use pumpkin_world::{ BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity, - level::SyncChunk, }; +use pumpkin_world::{chunk::ChunkData, world::BlockAccessor}; use pumpkin_world::{chunk::TickPriority, level::Level}; use pumpkin_world::{ entity::entity_data_flags::{DATA_PLAYER_MAIN_HAND, DATA_PLAYER_MODE_CUSTOMISATION}, world::GetBlockError, }; use pumpkin_world::{world::BlockFlags, world_info::LevelData}; -use rand::Rng; +use rand::{Rng, thread_rng}; use scoreboard::Scoreboard; use serde::Serialize; use time::LevelTime; -use tokio::sync::{RwLock, mpsc}; -use tokio::{ - select, - sync::{Mutex, mpsc::UnboundedReceiver}, -}; +use tokio::sync::Mutex; +use tokio::sync::RwLock; pub mod border; pub mod bossbar; @@ -98,8 +93,6 @@ pub mod weather; use weather::Weather; -type FlowingFluidProperties = pumpkin_data::fluid::FlowingWaterLikeFluidProperties; - impl PumpkinError for GetBlockError { fn is_kick(&self) -> bool { false @@ -362,7 +355,7 @@ impl World { volume: f32, pitch: f32, ) { - let seed = rand::rng().random::(); + let seed = thread_rng().r#gen::(); let packet = CSoundEffect::new(IdOr::Id(sound_id), category, position, volume, pitch, seed); self.broadcast_packet_all(&packet).await; } @@ -483,6 +476,9 @@ impl World { let Ok(fluid) = self.get_fluid(&scheduled_tick.block_pos).await else { continue; }; + if scheduled_tick.target_block_id != fluid.id { + continue; + } if let Some(pumpkin_fluid) = self.block_registry.get_pumpkin_fluid(&fluid) { pumpkin_fluid .on_scheduled_tick(self, &fluid, &scheduled_tick.block_pos) @@ -966,7 +962,7 @@ impl World { rel_x * rel_x + rel_z * rel_z }); - let mut receiver = self.receive_chunks(chunks); + let mut receiver = self.level.receive_chunks(chunks); let level = self.level.clone(); player.clone().spawn_task(async move { @@ -1337,7 +1333,7 @@ impl World { flags: BlockFlags, ) -> BlockStateId { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); - let chunk = self.get_chunk(chunk_coordinate).await; + let chunk = self.level.get_chunk(chunk_coordinate).await; let mut chunk = chunk.write().await; let replaced_block_state_id = chunk .section @@ -1478,39 +1474,6 @@ impl World { .is_block_tick_scheduled(block_pos, block.id) .await } - // Stream the chunks (don't collect them and then do stuff with them) - /// Spawns a tokio task to stream chunks. - /// Important: must be called from an async function (or changed to accept a tokio runtime - /// handle) - pub fn receive_chunks( - &self, - chunks: Vec>, - ) -> UnboundedReceiver<(SyncChunk, bool)> { - let (sender, receiver) = mpsc::unbounded_channel(); - // Put this in another thread so we aren't blocking on it - let level = self.level.clone(); - self.level.spawn_task(async move { - let cancel_notifier = level.shutdown_notifier.notified(); - let fetch_task = level.fetch_chunks(&chunks, sender); - - // Don't continue to handle chunks if we are shutting down - select! { - () = cancel_notifier => {}, - () = fetch_task => {} - }; - }); - - receiver - } - - pub async fn receive_chunk(&self, chunk_pos: Vector2) -> (Arc>, bool) { - let mut receiver = self.receive_chunks(vec![chunk_pos]); - - receiver - .recv() - .await - .expect("Channel closed for unknown reason") - } pub async fn break_block( self: &Arc, @@ -1540,10 +1503,9 @@ impl World { .unwrap_or(false) { // Broken block was waterlogged - let mut water_props = FlowingFluidProperties::default(&Fluid::FLOWING_WATER); - water_props.level = pumpkin_data::fluid::Level::L8; - water_props.falling = Falling::False; - water_props.to_state_id(&Fluid::FLOWING_WATER) + let mut water_props = WaterLikeProperties::default(&Block::WATER); + water_props.level = Integer0To15::L15; + water_props.to_state_id(&Block::WATER) } else { 0 }; @@ -1576,29 +1538,6 @@ impl World { .await; } - pub async fn get_chunk(&self, chunk_coordinate: Vector2) -> Arc> { - match self.level.try_get_chunk(chunk_coordinate) { - Some(chunk) => chunk.clone(), - None => self.receive_chunk(chunk_coordinate).await.0, - } - } - - pub async fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId { - let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); - let chunk = self.get_chunk(chunk_coordinate).await; - - let chunk = chunk.read().await; - let Some(id) = chunk.section.get_block_absolute_y( - relative.x as usize, - relative.y, - relative.z as usize, - ) else { - return Block::AIR.default_state_id; - }; - - id - } - /// Gets a `Block` from the block registry. Returns `Block::AIR` if the block was not found. pub async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block { let id = self.get_block_state_id(position).await; @@ -1610,27 +1549,11 @@ impl World { position: &BlockPos, ) -> Result { let id = self.get_block_state_id(position).await; - let fluid = Fluid::from_state_id(id).ok_or(GetBlockError::InvalidBlockId); - if let Ok(fluid) = fluid { - return Ok(fluid); - } - let block = get_block_by_state_id(id).ok_or(GetBlockError::InvalidBlockId)?; - block - .properties(id) - .and_then(|props| { - props - .to_props() - .into_iter() - .find(|p| p.0 == "waterlogged") - .map(|(_, value)| { - if value == true.to_string() { - Ok(Fluid::FLOWING_WATER) - } else { - Err(GetBlockError::InvalidBlockId) - } - }) - }) - .unwrap_or(Err(GetBlockError::InvalidBlockId)) + Fluid::from_state_id(id).ok_or(GetBlockError::InvalidBlockId) + } + + pub async fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId { + self.level.get_block_state(position).await.state_id } /// Gets the `BlockState` from the block registry. Returns Air if the block state was not found. @@ -1749,6 +1672,7 @@ impl World { block_pos: &BlockPos, ) -> Option<(NbtCompound, Arc)> { let chunk = self + .level .get_chunk(block_pos.chunk_and_chunk_relative_position().0) .await; let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; @@ -1759,6 +1683,7 @@ impl World { pub async fn add_block_entity(&self, block_entity: Arc) { let block_pos = block_entity.get_position(); let chunk = self + .level .get_chunk(block_pos.chunk_and_chunk_relative_position().0) .await; let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; @@ -1784,6 +1709,7 @@ impl World { pub async fn remove_block_entity(&self, block_pos: &BlockPos) { let chunk = self + .level .get_chunk(block_pos.chunk_and_chunk_relative_position().0) .await; let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; @@ -1791,103 +1717,14 @@ impl World { chunk.dirty = true; } - fn intersects_aabb_with_direction( - from: Vector3, - to: Vector3, - min: Vector3, - max: Vector3, - ) -> Option { - let dir = to.sub(&from); - let mut tmin: f64 = 0.0; - let mut tmax: f64 = 1.0; - - let mut hit_axis = None; - let mut hit_is_min = false; - - macro_rules! check_axis { - ($axis:ident, $dir_axis:ident, $min_axis:ident, $max_axis:ident, $direction_min:expr, $direction_max:expr) => {{ - if dir.$dir_axis.abs() < 1e-8 { - if from.$dir_axis < min.$min_axis || from.$dir_axis > max.$max_axis { - return None; - } - } else { - let inv_d = 1.0 / dir.$dir_axis; - let t_near = (min.$min_axis - from.$dir_axis) * inv_d; - let t_far = (max.$max_axis - from.$dir_axis) * inv_d; - - // Determine entry and exit points based on ray direction - let (t_entry, t_exit, is_min_face) = if inv_d >= 0.0 { - (t_near, t_far, true) - } else { - (t_far, t_near, false) - }; - - if t_entry > tmin { - tmin = t_entry; - hit_axis = Some(stringify!($axis)); - hit_is_min = is_min_face; - } - tmax = tmax.min(t_exit); - if tmax < tmin { - return None; - } - } - }}; - } - - check_axis!(x, x, x, x, BlockDirection::West, BlockDirection::East); - check_axis!(y, y, y, y, BlockDirection::Down, BlockDirection::Up); - check_axis!(z, z, z, z, BlockDirection::North, BlockDirection::South); - - match (hit_axis, hit_is_min) { - (Some("x"), true) => Some(BlockDirection::West), - (Some("x"), false) => Some(BlockDirection::East), - (Some("y"), true) => Some(BlockDirection::Down), - (Some("y"), false) => Some(BlockDirection::Up), - (Some("z"), true) => Some(BlockDirection::North), - (Some("z"), false) => Some(BlockDirection::South), - _ => None, - } - } - - pub async fn block_collision_check( - self: &Arc, - block_pos: &BlockPos, - from: Vector3, - to: Vector3, - ) -> (bool, Option) { - let state_id = self.get_block_state_id(block_pos).await; - //TODO this should use bounding boxes instead of collision shapes - // But currently we don't have a way to get all the bounding boxes of a block - let Some(collision_shapes) = get_block_collision_shapes(state_id) else { - return (false, None); - }; - - if collision_shapes.is_empty() { - return (true, None); - } - - for shape in &collision_shapes { - let world_min = shape.min.add(&block_pos.0.to_f64()); - let world_max = shape.max.add(&block_pos.0.to_f64()); - - let direction = Self::intersects_aabb_with_direction(from, to, world_min, world_max); - if direction.is_some() { - return (true, direction); - } - } - - (false, None) - } - - pub async fn raycast( + pub async fn raytrace( self: &Arc, start_pos: Vector3, end_pos: Vector3, hit_check: impl AsyncFn(&BlockPos, &Arc) -> bool, - ) -> Option<(BlockPos, BlockDirection)> { + ) -> (Option, Option) { if start_pos == end_pos { - return None; + return (None, None); } let adjust = -1.0e-7f64; @@ -1896,11 +1733,8 @@ impl World { let mut block = BlockPos::floored(from.x, from.y, from.z); - let (collision, direction) = self.block_collision_check(&block, from, to).await; - if let Some(dir) = direction { - if collision { - return Some((block, dir)); - } + if hit_check(&block, self).await { + return (Some(block), None); } let difference = to.sub(&from); @@ -1978,17 +1812,11 @@ impl World { }; if hit_check(&block, self).await { - let (collision, direction) = self.block_collision_check(&block, from, to).await; - if collision { - if let Some(direction) = direction { - return Some((block, direction)); - } - return Some((block, block_direction)); - } + return (Some(block), Some(block_direction)); } } - None + (None, None) } } @@ -2003,10 +1831,6 @@ impl pumpkin_world::world::SimpleWorld for World { Self::set_block_state(&self, position, block_state_id, flags).await } - async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block { - Self::get_block(self, position).await - } - async fn update_neighbor(self: Arc, neighbor_block_pos: &BlockPos, source_block: &Block) { Self::update_neighbor(&self, neighbor_block_pos, source_block).await; } @@ -2023,3 +1847,24 @@ impl pumpkin_world::world::SimpleWorld for World { self.remove_block_entity(block_pos).await; } } + +#[async_trait] +impl BlockAccessor for World { + async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block { + Self::get_block(self, position).await + } + async fn get_block_state(&self, position: &BlockPos) -> pumpkin_data::BlockState { + Self::get_block_state(self, position).await + } + + async fn get_block_and_block_state( + &self, + position: &BlockPos, + ) -> (pumpkin_data::Block, pumpkin_data::BlockState) { + let id = self.get_block_state(position).await.id; + get_block_and_state_by_state_id(id).unwrap_or(( + Block::AIR, + get_state_by_state_id(Block::AIR.default_state_id).unwrap(), + )) + } +} diff --git a/pumpkin/src/world/weather.rs b/pumpkin/src/world/weather.rs index a1a8bd6c0..03d867e2c 100644 --- a/pumpkin/src/world/weather.rs +++ b/pumpkin/src/world/weather.rs @@ -139,9 +139,10 @@ impl Weather { } } else if self.thundering { self.thunder_time = - rand::rng().random_range(THUNDER_DURATION_MIN..=THUNDER_DURATION_MAX); + rand::thread_rng().gen_range(THUNDER_DURATION_MIN..=THUNDER_DURATION_MAX); } else { - self.thunder_time = rand::rng().random_range(THUNDER_DELAY_MIN..=THUNDER_DELAY_MAX); + self.thunder_time = + rand::thread_rng().gen_range(THUNDER_DELAY_MIN..=THUNDER_DELAY_MAX); } // Handle rain timing @@ -151,9 +152,10 @@ impl Weather { self.raining = !self.raining; } } else if self.raining { - self.rain_time = rand::rng().random_range(RAIN_DURATION_MIN..=RAIN_DURATION_MAX); + self.rain_time = + rand::thread_rng().gen_range(RAIN_DURATION_MIN..=RAIN_DURATION_MAX); } else { - self.rain_time = rand::rng().random_range(RAIN_DELAY_MIN..=RAIN_DELAY_MAX); + self.rain_time = rand::thread_rng().gen_range(RAIN_DELAY_MIN..=RAIN_DELAY_MAX); } } }