fix: many generation fixes

This commit is contained in:
Alexander Medvedev 2026-09-09 18:01:31 +02:00
parent 8d4639e25a
commit df983f8477
38 changed files with 23699 additions and 415 deletions

View File

@ -109,7 +109,7 @@ villager = ["item", "sound"]
slot_ranges = [] slot_ranges = []
noise_parameter = [] noise_parameter = []
biome = [] biome = ["carver"]
chunk_status = [] chunk_status = []
entity_pose = [] entity_pose = []
entity_status = [] entity_status = []

View File

@ -15,6 +15,7 @@ pub struct Biome {
pub registry_id: &'static str, pub registry_id: &'static str,
pub be_network_id: u8, pub be_network_id: u8,
pub weather: Weather, pub weather: Weather,
pub carvers: &'static [&'static crate::carver::CarverConfig],
pub features: &'static [&'static [crate::placed_feature::PlacedFeature]], pub features: &'static [&'static [crate::placed_feature::PlacedFeature]],
pub creature_spawn_probability: f32, pub creature_spawn_probability: f32,
pub spawners: SpawnGroups, pub spawners: SpawnGroups,
@ -152,6 +153,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.03f32, creature_spawn_probability: 0.03f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -310,6 +316,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -448,6 +459,7 @@ impl Biome {
crate::placed_feature::PlacedFeature::OreDebrisSmall, crate::placed_feature::PlacedFeature::OreDebrisSmall,
], ],
], ],
carvers: &[&crate::carver::NETHER_CAVE],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -542,6 +554,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -678,6 +695,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -825,6 +847,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -972,6 +999,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1098,6 +1130,7 @@ impl Biome {
crate::placed_feature::PlacedFeature::CrimsonForestVegetation, crate::placed_feature::PlacedFeature::CrimsonForestVegetation,
], ],
], ],
carvers: &[&crate::carver::NETHER_CAVE],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1200,6 +1233,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1352,6 +1390,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1504,6 +1547,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[], monster: &[],
@ -1588,6 +1636,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1743,6 +1796,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -1911,6 +1969,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2068,6 +2131,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2223,6 +2291,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2296,6 +2369,7 @@ impl Biome {
be_network_id: 9u8, be_network_id: 9u8,
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32), weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
features: &[], features: &[],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -2330,6 +2404,7 @@ impl Biome {
&[], &[],
&[crate::placed_feature::PlacedFeature::ChorusPlant], &[crate::placed_feature::PlacedFeature::ChorusPlant],
], ],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -2353,6 +2428,7 @@ impl Biome {
be_network_id: 9u8, be_network_id: 9u8,
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32), weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
features: &[], features: &[],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -2439,6 +2515,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.03f32, creature_spawn_probability: 0.03f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2595,6 +2676,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2752,6 +2838,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -2911,6 +3002,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3056,6 +3152,11 @@ impl Biome {
&[crate::placed_feature::PlacedFeature::GlowLichen], &[crate::placed_feature::PlacedFeature::GlowLichen],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3190,6 +3291,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3328,6 +3434,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3476,6 +3587,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.07f32, creature_spawn_probability: 0.07f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3613,6 +3729,11 @@ impl Biome {
&[crate::placed_feature::PlacedFeature::GlowLichen], &[crate::placed_feature::PlacedFeature::GlowLichen],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3749,6 +3870,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -3921,6 +4047,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4088,6 +4219,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4224,6 +4360,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4369,6 +4510,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4511,6 +4657,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[], monster: &[],
@ -4573,6 +4724,7 @@ impl Biome {
crate::placed_feature::PlacedFeature::RedMushroomNormal, crate::placed_feature::PlacedFeature::RedMushroomNormal,
], ],
], ],
carvers: &[&crate::carver::NETHER_CAVE],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4685,6 +4837,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -4842,6 +4999,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5000,6 +5162,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5173,6 +5340,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5338,6 +5510,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5469,6 +5646,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5636,6 +5818,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5779,6 +5966,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -5950,6 +6142,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6069,6 +6266,7 @@ impl Biome {
be_network_id: 9u8, be_network_id: 9u8,
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32), weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
features: &[&[crate::placed_feature::PlacedFeature::EndIslandDecorated]], features: &[&[crate::placed_feature::PlacedFeature::EndIslandDecorated]],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -6152,6 +6350,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6281,6 +6484,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.07f32, creature_spawn_probability: 0.07f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6426,6 +6634,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6568,6 +6781,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6699,6 +6917,7 @@ impl Biome {
&[], &[],
&[crate::placed_feature::PlacedFeature::SpringLava], &[crate::placed_feature::PlacedFeature::SpringLava],
], ],
carvers: &[&crate::carver::NETHER_CAVE],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6801,6 +7020,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -6952,6 +7176,11 @@ impl Biome {
&[crate::placed_feature::PlacedFeature::GlowLichen], &[crate::placed_feature::PlacedFeature::GlowLichen],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7080,6 +7309,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7207,6 +7441,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7339,6 +7578,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7510,6 +7754,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7677,6 +7926,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -7793,6 +8047,7 @@ impl Biome {
&[], &[],
&[crate::placed_feature::PlacedFeature::EndPlatform], &[crate::placed_feature::PlacedFeature::EndPlatform],
], ],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -7828,6 +8083,7 @@ impl Biome {
&[], &[],
&[crate::placed_feature::PlacedFeature::VoidStartPlatform], &[crate::placed_feature::PlacedFeature::VoidStartPlatform],
], ],
carvers: &[],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[], monster: &[],
@ -7911,6 +8167,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -8044,6 +8305,7 @@ impl Biome {
crate::placed_feature::PlacedFeature::TwistingVines, crate::placed_feature::PlacedFeature::TwistingVines,
], ],
], ],
carvers: &[&crate::carver::NETHER_CAVE],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[Spawner { monster: &[Spawner {
@ -8134,6 +8396,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -8291,6 +8558,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -8448,6 +8720,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -8603,6 +8880,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.1f32, creature_spawn_probability: 0.1f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[
@ -8776,6 +9058,11 @@ impl Biome {
], ],
&[crate::placed_feature::PlacedFeature::FreezeTopLayer], &[crate::placed_feature::PlacedFeature::FreezeTopLayer],
], ],
carvers: &[
&crate::carver::CAVE,
&crate::carver::CAVE_EXTRA_UNDERGROUND,
&crate::carver::CANYON,
],
creature_spawn_probability: 0.04f32, creature_spawn_probability: 0.04f32,
spawners: SpawnGroups { spawners: SpawnGroups {
monster: &[ monster: &[

View File

@ -8,25 +8,30 @@ use pumpkin_util::math::int_provider::{
UniformIntProvider, VeryBiasedToBottomIntProvider, UniformIntProvider, VeryBiasedToBottomIntProvider,
}; };
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset}; use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
#[derive(Clone, Debug)]
pub enum HeightProvider { pub enum HeightProvider {
Uniform(UniformHeightProvider), Uniform(UniformHeightProvider),
Trapezoid(TrapezoidHeightProvider), Trapezoid(TrapezoidHeightProvider),
VeryBiasedToBottom(VeryBiasedToBottomHeightProvider), VeryBiasedToBottom(VeryBiasedToBottomHeightProvider),
} }
#[derive(Clone, Debug)]
pub struct UniformHeightProvider { pub struct UniformHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
} }
#[derive(Clone, Debug)]
pub struct TrapezoidHeightProvider { pub struct TrapezoidHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub plateau: Option<i32>, pub plateau: Option<i32>,
} }
#[derive(Clone, Debug)]
pub struct VeryBiasedToBottomHeightProvider { pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZero<u32>>, pub inner: Option<std::num::NonZero<u32>>,
} }
#[derive(Clone, Debug)]
pub struct CaveCarverConfig { pub struct CaveCarverConfig {
pub count: IntProvider, pub count: IntProvider,
pub horizontal_radius_multiplier: FloatProvider, pub horizontal_radius_multiplier: FloatProvider,
@ -52,6 +57,7 @@ impl CaveCarverConfig {
} }
} }
} }
#[derive(Clone, Debug)]
pub struct CanyonShapeConfig { pub struct CanyonShapeConfig {
pub distance_factor: FloatProvider, pub distance_factor: FloatProvider,
pub thickness: FloatProvider, pub thickness: FloatProvider,
@ -61,14 +67,17 @@ pub struct CanyonShapeConfig {
pub vertical_radius_center_factor: f32, pub vertical_radius_center_factor: f32,
pub y_scale: f32, pub y_scale: f32,
} }
#[derive(Clone, Debug)]
pub struct CanyonCarverConfig { pub struct CanyonCarverConfig {
pub vertical_rotation: FloatProvider, pub vertical_rotation: FloatProvider,
pub shape: CanyonShapeConfig, pub shape: CanyonShapeConfig,
} }
#[derive(Clone, Debug)]
pub enum CarverAdditionalConfig { pub enum CarverAdditionalConfig {
Cave(CaveCarverConfig), Cave(CaveCarverConfig),
Canyon(CanyonCarverConfig), Canyon(CanyonCarverConfig),
} }
#[derive(Clone, Debug)]
pub struct CarverConfig { pub struct CarverConfig {
pub probability: f32, pub probability: f32,
pub y: HeightProvider, pub y: HeightProvider,

View File

@ -23,6 +23,20 @@ pub enum MaterialRule {
Badlands(BadLandsMaterialRule), Badlands(BadLandsMaterialRule),
} }
impl MaterialRule { impl MaterialRule {
#[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" | "amplified" | "large_biomes" => Some(&OVERWORLD),
"nether" => Some(&NETHER),
"end" => Some(&END),
"bedrock_floor" => Some(&BEDROCK_FLOOR),
"bedrock_roof" => Some(&BEDROCK_ROOF),
"overworld_caves" => Some(&OVERWORLD_CAVES),
"overworld_floating_islands" => Some(&OVERWORLD_FLOATING_ISLANDS),
_ => None,
}
}
#[must_use] #[must_use]
pub fn from_dimension(dimension: &Dimension) -> &'static Self { pub fn from_dimension(dimension: &Dimension) -> &'static Self {
if dimension == &Dimension::OVERWORLD { if dimension == &Dimension::OVERWORLD {

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
use crate::BlockState; use crate::BlockState;
use crate::biome::ParameterPoint; use crate::biome::ParameterPoint;
use crate::dimension::Dimension; use crate::dimension::Dimension;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoiseSettings { pub struct NoiseSettings {
pub aquifers_enabled: bool, pub aquifers_enabled: bool,
pub ore_veins_enabled: bool, pub ore_veins_enabled: bool,
@ -13,6 +14,7 @@ pub struct NoiseSettings {
pub spawn_target: &'static [ParameterPoint], pub spawn_target: &'static [ParameterPoint],
} }
pub type GenerationSettings = NoiseSettings; pub type GenerationSettings = NoiseSettings;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GenerationShapeConfig { pub struct GenerationShapeConfig {
pub min_y: i8, pub min_y: i8,
pub height: u16, pub height: u16,
@ -224,6 +226,20 @@ impl NoiseSettings {
], ],
}; };
#[must_use] #[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" => Some(&Self::OVERWORLD),
"amplified" => Some(&Self::AMPLIFIED),
"large_biomes" => Some(&Self::LARGE_BIOMES),
"nether" => Some(&Self::NETHER),
"end" => Some(&Self::END),
"caves" => Some(&Self::CAVES),
"floating_islands" => Some(&Self::FLOATING_ISLANDS),
_ => None,
}
}
#[must_use]
pub fn from_dimension(dimension: &Dimension) -> &'static Self { pub fn from_dimension(dimension: &Dimension) -> &'static Self {
if dimension == &Dimension::OVERWORLD { if dimension == &Dimension::OVERWORLD {
&Self::OVERWORLD &Self::OVERWORLD

View File

@ -170,6 +170,28 @@ pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
^ world_seed ^ world_seed
} }
/// Generates a large feature seed for structure selection and placement.
///
/// Matches vanilla Minecraft's `WorldgenRandom.setLargeFeatureSeed`.
///
/// # Arguments
/// - `world_seed` – The base world seed.
/// - `chunk_x` – The X chunk coordinate.
/// - `chunk_z` – The Z chunk coordinate.
///
/// # Returns
/// A large feature seed for the given chunk.
#[inline]
#[must_use]
pub fn get_large_feature_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
let mut random = LegacyRand::from_seed(world_seed);
let x_scale = random.next_i64();
let z_scale = random.next_i64();
((chunk_x as i64).wrapping_mul(x_scale)
^ (chunk_z as i64).wrapping_mul(z_scale)
^ (world_seed as i64)) as u64
}
#[expect(clippy::return_self_not_must_use)] #[expect(clippy::return_self_not_must_use)]
pub trait RandomImpl { pub trait RandomImpl {
fn split(&mut self) -> Self; fn split(&mut self) -> Self;

View File

@ -7,6 +7,7 @@ use crate::{
}, },
}; };
#[derive(Clone, Copy, Debug)]
pub struct TheEndBiomeSupplier; pub struct TheEndBiomeSupplier;
impl TheEndBiomeSupplier { impl TheEndBiomeSupplier {

View File

@ -25,6 +25,7 @@ pub trait BiomeSupplier {
fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome; fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome;
} }
#[derive(Clone, Copy)]
pub struct MultiNoiseBiomeSupplier { pub struct MultiNoiseBiomeSupplier {
source: &'static BiomeTree, source: &'static BiomeTree,
} }
@ -33,6 +34,16 @@ impl MultiNoiseBiomeSupplier {
pub const OVERWORLD: Self = Self::new(&OVERWORLD_BIOME_SOURCE); pub const OVERWORLD: Self = Self::new(&OVERWORLD_BIOME_SOURCE);
pub const NETHER: Self = Self::new(&NETHER_BIOME_SOURCE); pub const NETHER: Self = Self::new(&NETHER_BIOME_SOURCE);
#[must_use]
pub fn from_preset(preset: &str) -> Option<Self> {
let preset = preset.strip_prefix("minecraft:").unwrap_or(preset);
match preset {
"overworld" | "large_biomes" | "amplified" => Some(Self::OVERWORLD),
"nether" => Some(Self::NETHER),
_ => None,
}
}
const fn new(source: &'static BiomeTree) -> Self { const fn new(source: &'static BiomeTree) -> Self {
Self { source } Self { source }
} }
@ -46,6 +57,94 @@ impl BiomeSupplier for MultiNoiseBiomeSupplier {
} }
} }
#[derive(Clone, Copy)]
pub struct FixedBiomeSupplier {
pub biome: &'static Biome,
}
impl FixedBiomeSupplier {
#[must_use]
pub const fn new(biome: &'static Biome) -> Self {
Self { biome }
}
}
impl BiomeSupplier for FixedBiomeSupplier {
fn biome(
&self,
_x: i32,
_y: i32,
_z: i32,
_noise: &mut MultiNoiseSampler<'_>,
) -> &'static Biome {
self.biome
}
}
#[derive(Clone, Copy)]
pub enum ActiveBiomeSupplier {
MultiNoise(MultiNoiseBiomeSupplier),
End(end::TheEndBiomeSupplier),
Fixed(FixedBiomeSupplier),
}
impl BiomeSupplier for ActiveBiomeSupplier {
fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome {
match self {
Self::MultiNoise(supplier) => supplier.biome(x, y, z, noise),
Self::End(supplier) => supplier.biome(x, y, z, noise),
Self::Fixed(supplier) => supplier.biome(x, y, z, noise),
}
}
}
impl ActiveBiomeSupplier {
#[must_use]
pub fn from_biome_source(
source: Option<&crate::world_info::BiomeSource>,
dimension: &pumpkin_data::dimension::Dimension,
) -> Self {
match source {
Some(crate::world_info::BiomeSource::Fixed { biome, .. }) => {
let clean = biome.strip_prefix("minecraft:").unwrap_or(biome);
let resolved = pumpkin_data::biome::Biome::from_name(clean)
.unwrap_or(&pumpkin_data::biome::Biome::PLAINS);
Self::Fixed(FixedBiomeSupplier::new(resolved))
}
Some(crate::world_info::BiomeSource::WithPreset { preset, .. }) => {
let clean = preset.strip_prefix("minecraft:").unwrap_or(preset);
if clean == "nether" {
Self::MultiNoise(MultiNoiseBiomeSupplier::NETHER)
} else {
Self::MultiNoise(MultiNoiseBiomeSupplier::OVERWORLD)
}
}
Some(crate::world_info::BiomeSource::Simple { biome_type }) => {
let clean = biome_type.strip_prefix("minecraft:").unwrap_or(biome_type);
if clean == "the_end" {
Self::End(end::TheEndBiomeSupplier)
} else if clean == "the_nether" {
Self::MultiNoise(MultiNoiseBiomeSupplier::NETHER)
} else {
Self::from_dimension(dimension)
}
}
None => Self::from_dimension(dimension),
}
}
#[must_use]
pub fn from_dimension(dimension: &pumpkin_data::dimension::Dimension) -> Self {
if dimension == &pumpkin_data::dimension::Dimension::THE_END {
Self::End(end::TheEndBiomeSupplier)
} else if dimension == &pumpkin_data::dimension::Dimension::THE_NETHER {
Self::MultiNoise(MultiNoiseBiomeSupplier::NETHER)
} else {
Self::MultiNoise(MultiNoiseBiomeSupplier::OVERWORLD)
}
}
}
#[must_use] #[must_use]
pub fn hash_seed(seed: u64) -> i64 { pub fn hash_seed(seed: u64) -> i64 {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();

View File

@ -3,19 +3,22 @@ pub mod cave;
pub mod mask; pub mod mask;
use crate::ProtoChunk; use crate::ProtoChunk;
use crate::biome::BiomeSupplier;
use crate::generation::GlobalRandomConfig; use crate::generation::GlobalRandomConfig;
use crate::generation::generator::VanillaGenerator; use crate::generation::generator::VanillaGenerator;
use crate::generation::noise::aquifer_sampler::CarverAquiferSampler; use crate::generation::noise::aquifer_sampler::CarverAquiferSampler;
use crate::generation::noise::perlin::DoublePerlinNoiseSampler; use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
use crate::generation::noise::router::surface_height_sampler::{ use crate::generation::noise::router::surface_height_sampler::{
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
}; };
use crate::generation::surface::rule::try_apply_material_rule; use crate::generation::surface::rule::try_apply_material_rule;
use crate::generation::surface::terrain::SurfaceTerrainBuilder; use crate::generation::surface::terrain::SurfaceTerrainBuilder;
use crate::generation::surface::{MaterialRuleContext, steep_material_condition}; use crate::generation::surface::{MaterialRuleContext, steep_material_condition};
use crate::generation::{biome_coords, section_coords};
use pumpkin_data::block_state::BlockState; use pumpkin_data::block_state::BlockState;
use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE};
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig}; use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig};
#[cfg(test)]
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::Fluid; use pumpkin_data::fluid::Fluid;
use pumpkin_data::material_rule::MaterialRule; use pumpkin_data::material_rule::MaterialRule;
@ -23,9 +26,6 @@ use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::{RandomGenerator, RandomImpl}; use pumpkin_util::random::{RandomGenerator, RandomImpl};
const OVERWORLD_CARVERS: [&CarverConfig; 3] = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON];
const NETHER_CARVERS: [&CarverConfig; 1] = [&NETHER_CAVE];
pub struct CarverBlockIds { pub struct CarverBlockIds {
pub air: &'static BlockState, pub air: &'static BlockState,
pub cave_air: &'static BlockState, pub cave_air: &'static BlockState,
@ -63,6 +63,8 @@ pub struct CarvingContext<'a> {
pub secondary_noise: &'a DoublePerlinNoiseSampler, pub secondary_noise: &'a DoublePerlinNoiseSampler,
pub terrain_builder: &'a SurfaceTerrainBuilder, pub terrain_builder: &'a SurfaceTerrainBuilder,
pub sea_level: i32, pub sea_level: i32,
pub default_block: &'static BlockState,
pub default_fluid: &'static BlockState,
pub surface_rule: &'a MaterialRule, pub surface_rule: &'a MaterialRule,
pub surface_height_sampler: SurfaceHeightEstimateSampler<'a>, pub surface_height_sampler: SurfaceHeightEstimateSampler<'a>,
pub carver_aquifer: Option<CarverAquiferSampler<'a>>, pub carver_aquifer: Option<CarverAquiferSampler<'a>>,
@ -119,6 +121,7 @@ pub trait Carver {
); );
} }
#[allow(clippy::too_many_lines)]
pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) { pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
// Vanilla applyCarvers uses a range of 8 chunks (17x17 area) // Vanilla applyCarvers uses a range of 8 chunks (17x17 area)
let radius = 8; let radius = 8;
@ -126,7 +129,8 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
let chunk_z = chunk.z; let chunk_z = chunk.z;
let chunk_pos = Vector2::new(chunk_x, chunk_z); let chunk_pos = Vector2::new(chunk_x, chunk_z);
let carvers_to_use = carvers_for_dimension(&generator.dimension); let supplier: &dyn BiomeSupplier = &generator.biome_supplier;
let mut multi_noise_sampler = MultiNoiseSampler::generate(&generator.base_router.multi_noise);
let generation_shape = &generator.settings.shape; let generation_shape = &generator.settings.shape;
let surface_config = SurfaceHeightSamplerBuilderOptions::new( let surface_config = SurfaceHeightSamplerBuilderOptions::new(
@ -156,11 +160,15 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
secondary_noise: &generator.terrain_cache.secondary_noise, secondary_noise: &generator.terrain_cache.secondary_noise,
terrain_builder: &generator.terrain_cache.terrain_builder, terrain_builder: &generator.terrain_cache.terrain_builder,
sea_level: generator.settings.sea_level, sea_level: generator.settings.sea_level,
default_block: generator.settings.default_block,
default_fluid: generator.settings.default_fluid,
surface_rule: generator.surface_rule, surface_rule: generator.surface_rule,
surface_height_sampler, surface_height_sampler,
carver_aquifer, carver_aquifer,
}; };
let center_biome = chunk.get_biome(0, 0, 0);
let mut run = CarveRun { let mut run = CarveRun {
ctx: &mut context, ctx: &mut context,
chunk, chunk,
@ -176,9 +184,18 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
let carver_z = chunk_z + dz; let carver_z = chunk_z + dz;
let carver_chunk_pos = Vector2::new(carver_x, carver_z); let carver_chunk_pos = Vector2::new(carver_x, carver_z);
// In vanilla, carvers are per-biome. Here we use the hardcoded list but let carver_biome = if dx == 0 && dz == 0 {
// maintain the random seed logic. center_biome
for (index, &config) in carvers_to_use.iter().enumerate() { } else {
supplier.biome(
biome_coords::from_block(section_coords::section_to_block(carver_x)),
0,
biome_coords::from_block(section_coords::section_to_block(carver_z)),
&mut multi_noise_sampler,
)
};
for (index, &config) in carver_biome.carvers.iter().enumerate() {
let seed = get_large_feature_seed( let seed = get_large_feature_seed(
generator.random_config.seed + index as u64, generator.random_config.seed + index as u64,
carver_x, carver_x,
@ -242,16 +259,6 @@ const fn new_carver_random(seed: u64, non_vanilla_random: bool) -> RandomGenerat
} }
} }
fn carvers_for_dimension(dimension: &Dimension) -> &'static [&'static CarverConfig] {
if dimension == &Dimension::OVERWORLD {
&OVERWORLD_CARVERS
} else if dimension == &Dimension::THE_NETHER {
&NETHER_CARVERS
} else {
&[]
}
}
fn carve_top_material( fn carve_top_material(
run: &mut CarveRun, run: &mut CarveRun,
x: i32, x: i32,
@ -294,7 +301,7 @@ fn overworld_carve_state(
.state .state
.map(|state| (state, result.should_schedule_fluid_update)) .map(|state| (state, result.should_schedule_fluid_update))
} else if y < run.ctx.sea_level { } else if y < run.ctx.sea_level {
Some((run.ids.lava, false)) Some((run.ctx.default_fluid, false))
} else { } else {
Some((run.ids.air, false)) Some((run.ids.air, false))
} }
@ -384,6 +391,8 @@ fn with_carve_run_options<F>(
secondary_noise: &generator.terrain_cache.secondary_noise, secondary_noise: &generator.terrain_cache.secondary_noise,
terrain_builder: &generator.terrain_cache.terrain_builder, terrain_builder: &generator.terrain_cache.terrain_builder,
sea_level: generator.settings.sea_level, sea_level: generator.settings.sea_level,
default_block: generator.settings.default_block,
default_fluid: generator.settings.default_fluid,
surface_rule: surface_rule.unwrap_or(generator.surface_rule), surface_rule: surface_rule.unwrap_or(generator.surface_rule),
surface_height_sampler, surface_height_sampler,
carver_aquifer, carver_aquifer,
@ -545,4 +554,23 @@ mod tests {
let index = (x & 15) as usize * 16 + (z & 15) as usize; let index = (x & 15) as usize * 16 + (z & 15) as usize;
chunk.flat_surface_height_map[index] = height; chunk.flat_surface_height_map[index] = height;
} }
#[test]
fn biome_carvers_driven_by_data() {
use pumpkin_data::biome::Biome;
use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE};
assert_eq!(Biome::PLAINS.carvers.len(), 3);
assert!(std::ptr::eq(Biome::PLAINS.carvers[0], &CAVE));
assert!(std::ptr::eq(
Biome::PLAINS.carvers[1],
&CAVE_EXTRA_UNDERGROUND
));
assert!(std::ptr::eq(Biome::PLAINS.carvers[2], &CANYON));
assert_eq!(Biome::NETHER_WASTES.carvers.len(), 1);
assert!(std::ptr::eq(Biome::NETHER_WASTES.carvers[0], &NETHER_CAVE));
assert!(Biome::THE_END.carvers.is_empty());
}
} }

View File

@ -1,4 +1,5 @@
use pumpkin_data::{Block, BlockState, block_properties::BrownMushroomBlockLikeProperties}; use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockState, block_properties::BrownMushroomBlockLikeProperties, tag};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -14,6 +15,19 @@ pub fn mushroom_tree_height(random: &mut RandomGenerator) -> i32 {
height height
} }
pub fn place_mushroom_block<T: GenerationCache>(
chunk: &mut T,
pos: &BlockPos,
state: &'static BlockState,
) {
let (current_block, current_state) = chunk.get_block_and_state(pos);
if current_state.is_air()
|| current_block.has_tag(&tag::Block::MINECRAFT_REPLACEABLE_BY_MUSHROOMS)
{
chunk.set_block_state(&pos.0, state);
}
}
pub struct HugeBrownMushroomFeature; pub struct HugeBrownMushroomFeature;
impl HugeBrownMushroomFeature { impl HugeBrownMushroomFeature {
@ -23,14 +37,38 @@ impl HugeBrownMushroomFeature {
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
_min_y: i8, min_y: i8,
_height: u16, height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let tree_height = mushroom_tree_height(random); let tree_height = mushroom_tree_height(random);
let min_y = i32::from(min_y);
let max_y = min_y + i32::from(height);
if pos.0.y < min_y + 1 || pos.0.y + tree_height + 1 > max_y {
return false;
}
let below_block = chunk.get_block(&pos.down());
if !below_block.has_tag(&tag::Block::MINECRAFT_HUGE_BROWN_MUSHROOM_CAN_PLACE_ON) {
return false;
}
for dy in 0..=tree_height {
let radius = if dy <= 3 { 0 } else { Self::FOLIAGE_RADIUS };
for dx in -radius..=radius {
for dz in -radius..=radius {
let check_pos = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz);
let (block, state) = chunk.get_block_and_state(&check_pos);
if !state.is_air() && !block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
return false;
}
}
}
}
let radius = Self::FOLIAGE_RADIUS; let radius = Self::FOLIAGE_RADIUS;
let cap_y = pos.0.y + tree_height; let cap_y = pos.0.y + tree_height;
for j in -radius..=radius { for j in -radius..=radius {
@ -52,7 +90,7 @@ impl HugeBrownMushroomFeature {
}; };
let state_id = props.to_state_id(&Block::BROWN_MUSHROOM_BLOCK); let state_id = props.to_state_id(&Block::BROWN_MUSHROOM_BLOCK);
let cap_pos = BlockPos::new(pos.0.x + j, cap_y, pos.0.z + k); let cap_pos = BlockPos::new(pos.0.x + j, cap_y, pos.0.z + k);
chunk.set_block_state(&cap_pos.0, BlockState::from_id(state_id)); place_mushroom_block(chunk, &cap_pos, BlockState::from_id(state_id));
} }
} }
@ -67,7 +105,7 @@ impl HugeBrownMushroomFeature {
let stem_state = BlockState::from_id(stem_props.to_state_id(&Block::MUSHROOM_STEM)); let stem_state = BlockState::from_id(stem_props.to_state_id(&Block::MUSHROOM_STEM));
for i in 0..tree_height { for i in 0..tree_height {
let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z); let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z);
chunk.set_block_state(&stem_pos.0, stem_state); place_mushroom_block(chunk, &stem_pos, stem_state);
} }
true true

View File

@ -1,6 +1,8 @@
use pumpkin_data::{Block, BlockState, block_properties::BrownMushroomBlockLikeProperties}; use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockState, block_properties::BrownMushroomBlockLikeProperties, tag};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use super::huge_brown_mushroom::place_mushroom_block;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct HugeRedMushroomFeature; pub struct HugeRedMushroomFeature;
@ -12,14 +14,42 @@ impl HugeRedMushroomFeature {
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
_min_y: i8, min_y: i8,
_height: u16, height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let tree_height = super::huge_brown_mushroom::mushroom_tree_height(random); let tree_height = super::huge_brown_mushroom::mushroom_tree_height(random);
let min_y = i32::from(min_y);
let max_y = min_y + i32::from(height);
if pos.0.y < min_y + 1 || pos.0.y + tree_height + 1 > max_y {
return false;
}
let below_block = chunk.get_block(&pos.down());
if !below_block.has_tag(&tag::Block::MINECRAFT_HUGE_RED_MUSHROOM_CAN_PLACE_ON) {
return false;
}
for dy in 0..=tree_height {
let radius = if (dy < tree_height && dy >= tree_height - 3) || dy == tree_height {
Self::FOLIAGE_RADIUS
} else {
0
};
for dx in -radius..=radius {
for dz in -radius..=radius {
let check_pos = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz);
let (block, state) = chunk.get_block_and_state(&check_pos);
if !state.is_air() && !block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
return false;
}
}
}
}
let radius = Self::FOLIAGE_RADIUS; let radius = Self::FOLIAGE_RADIUS;
for i in (tree_height - 3)..=tree_height { for i in (tree_height - 3)..=tree_height {
let j = if i < tree_height { radius } else { radius - 1 }; let j = if i < tree_height { radius } else { radius - 1 };
@ -44,7 +74,7 @@ impl HugeRedMushroomFeature {
}; };
let state_id = props.to_state_id(&Block::RED_MUSHROOM_BLOCK); let state_id = props.to_state_id(&Block::RED_MUSHROOM_BLOCK);
let cap_pos = BlockPos::new(pos.0.x + l, pos.0.y + i, pos.0.z + m); let cap_pos = BlockPos::new(pos.0.x + l, pos.0.y + i, pos.0.z + m);
chunk.set_block_state(&cap_pos.0, BlockState::from_id(state_id)); place_mushroom_block(chunk, &cap_pos, BlockState::from_id(state_id));
} }
} }
} }
@ -60,7 +90,7 @@ impl HugeRedMushroomFeature {
let stem_state = BlockState::from_id(stem_props.to_state_id(&Block::MUSHROOM_STEM)); let stem_state = BlockState::from_id(stem_props.to_state_id(&Block::MUSHROOM_STEM));
for i in 0..tree_height { for i in 0..tree_height {
let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z); let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z);
chunk.set_block_state(&stem_pos.0, stem_state); place_mushroom_block(chunk, &stem_pos, stem_state);
} }
true true

View File

@ -1,9 +1,7 @@
use pumpkin_data::BlockState; use pumpkin_data::BlockState;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::material_rule::MaterialRule; use pumpkin_data::material_rule::MaterialRule;
use pumpkin_data::noise_router::{ use pumpkin_data::noise_router::BaseNoiseRouters;
END_BASE_NOISE_ROUTER, NETHER_BASE_NOISE_ROUTER, OVERWORLD_BASE_NOISE_ROUTER,
};
use pumpkin_data::noise_settings::NoiseSettings; use pumpkin_data::noise_settings::NoiseSettings;
use super::noise::router::proto_noise_router::ProtoNoiseRouters; use super::noise::router::proto_noise_router::ProtoNoiseRouters;
@ -128,6 +126,7 @@ pub struct VanillaGenerator {
pub dimension: Dimension, pub dimension: Dimension,
pub settings: &'static NoiseSettings, pub settings: &'static NoiseSettings,
pub surface_rule: &'static MaterialRule, pub surface_rule: &'static MaterialRule,
pub biome_supplier: crate::biome::ActiveBiomeSupplier,
pub biome_mixer_seed: i64, pub biome_mixer_seed: i64,
pub terrain_cache: TerrainCache, pub terrain_cache: TerrainCache,
@ -137,6 +136,7 @@ pub struct VanillaGenerator {
pub global_structure_cache: crate::generation::structure::placement::GlobalStructureCache, pub global_structure_cache: crate::generation::structure::placement::GlobalStructureCache,
pub structure_calculator: StructurePlacementCalculator, pub structure_calculator: StructurePlacementCalculator,
pub structure_allowed_biomes: FxHashMap<usize, Vec<u16>>, pub structure_allowed_biomes: FxHashMap<usize, Vec<u16>>,
pub enabled_structure_sets: Option<rustc_hash::FxHashSet<usize>>,
} }
impl VanillaGenerator { impl VanillaGenerator {
@ -154,30 +154,41 @@ impl VanillaGenerator {
&mut sampler, &mut sampler,
) )
} }
#[must_use]
pub fn new_with_settings(
seed: Seed,
dimension: Dimension,
settings_name: Option<&str>,
) -> Self {
Self::new_with_all_settings(seed, dimension, settings_name, None, None)
} }
impl GeneratorInit for VanillaGenerator { #[must_use]
fn new(seed: Seed, dimension: Dimension) -> Self { pub fn new_with_all_settings(
let settings = NoiseSettings::from_dimension(&dimension); seed: Seed,
let surface_rule = MaterialRule::from_dimension(&dimension); dimension: Dimension,
settings_name: Option<&str>,
biome_source: Option<&crate::world_info::BiomeSource>,
structure_overrides: Option<&[String]>,
) -> Self {
let settings = settings_name
.and_then(NoiseSettings::from_name)
.unwrap_or_else(|| NoiseSettings::from_dimension(&dimension));
let surface_rule = settings_name
.and_then(MaterialRule::from_name)
.unwrap_or_else(|| MaterialRule::from_dimension(&dimension));
let biome_supplier =
crate::biome::ActiveBiomeSupplier::from_biome_source(biome_source, &dimension);
let random_config = GlobalRandomConfig::new(seed.0, settings.legacy_random_source); let random_config = GlobalRandomConfig::new(seed.0, settings.legacy_random_source);
// TODO: The generation settings contains (part of?) the noise routers too; do we keep the separate or let base = settings_name
// use only the generation settings? .and_then(BaseNoiseRouters::from_name)
let base = if dimension == Dimension::OVERWORLD { .unwrap_or_else(|| BaseNoiseRouters::from_dimension(&dimension));
OVERWORLD_BASE_NOISE_ROUTER
} else if dimension == Dimension::THE_NETHER {
NETHER_BASE_NOISE_ROUTER
} else if dimension == Dimension::THE_END {
END_BASE_NOISE_ROUTER
} else {
tracing::error!("Unsupported dimension for noise router: {:?}", dimension);
OVERWORLD_BASE_NOISE_ROUTER
};
let terrain_cache = TerrainCache::from_random(&random_config); let terrain_cache = TerrainCache::from_random(&random_config);
let default_block = settings.default_block; let default_block = settings.default_block;
let base_router = ProtoNoiseRouters::generate(&base, &random_config); let base_router = ProtoNoiseRouters::generate(base, &random_config);
let biome_mixer_seed = crate::biome::hash_seed(seed.0); let biome_mixer_seed = crate::biome::hash_seed(seed.0);
let mut structure_allowed_biomes = FxHashMap::default(); let mut structure_allowed_biomes = FxHashMap::default();
@ -188,19 +199,157 @@ impl GeneratorInit for VanillaGenerator {
); );
} }
let enabled_structure_sets: Option<rustc_hash::FxHashSet<usize>> =
structure_overrides.map(|overrides| {
overrides
.iter()
.filter_map(|s| {
let clean = s.strip_prefix("minecraft:").unwrap_or(s);
StructureSet::NAMES.iter().position(|&name| name == clean)
})
.collect()
});
let global_structure_cache =
crate::generation::structure::placement::GlobalStructureCache::new();
if dimension == Dimension::OVERWORLD {
let pumpkin_data::structures::StructurePlacementType::ConcentricRings(rings) =
&StructureSet::STRONGHOLDS.placement.placement_type
else {
unreachable!()
};
let strongholds =
crate::generation::structure::placement::GlobalStructureCache::calculate_strongholds(
seed.0 as i64,
rings,
&base_router.multi_noise,
);
global_structure_cache.init_strongholds(strongholds);
}
Self { Self {
random_config, random_config,
base_router, base_router,
dimension, dimension,
settings, settings,
surface_rule, surface_rule,
biome_supplier,
biome_mixer_seed, biome_mixer_seed,
terrain_cache, terrain_cache,
default_block, default_block,
global_structure_cache: global_structure_cache,
crate::generation::structure::placement::GlobalStructureCache::new(),
structure_calculator: StructurePlacementCalculator::new(seed.0 as i64), structure_calculator: StructurePlacementCalculator::new(seed.0 as i64),
structure_allowed_biomes, structure_allowed_biomes,
enabled_structure_sets,
} }
} }
} }
impl GeneratorInit for VanillaGenerator {
fn new(seed: Seed, dimension: Dimension) -> Self {
Self::new_with_settings(seed, dimension, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vanilla_generator_settings() {
let seed = Seed(42);
// Default overworld
let default_gen = VanillaGenerator::new_with_settings(seed, Dimension::OVERWORLD, None);
assert_eq!(*default_gen.settings, NoiseSettings::OVERWORLD);
// Amplified preset
let amplified_gen = VanillaGenerator::new_with_settings(
seed,
Dimension::OVERWORLD,
Some("minecraft:amplified"),
);
assert_eq!(*amplified_gen.settings, NoiseSettings::AMPLIFIED);
// Large biomes preset
let large_biomes_gen = VanillaGenerator::new_with_settings(
seed,
Dimension::OVERWORLD,
Some("minecraft:large_biomes"),
);
assert_eq!(*large_biomes_gen.settings, NoiseSettings::LARGE_BIOMES);
// Nether
let nether_gen = VanillaGenerator::new_with_settings(
seed,
Dimension::THE_NETHER,
Some("minecraft:nether"),
);
assert_eq!(*nether_gen.settings, NoiseSettings::NETHER);
// End
let end_gen =
VanillaGenerator::new_with_settings(seed, Dimension::THE_END, Some("minecraft:end"));
assert_eq!(*end_gen.settings, NoiseSettings::END);
}
#[test]
fn vanilla_generator_structure_overrides() {
let seed = Seed(42);
// No overrides: None means all structure sets enabled
let all_gen =
VanillaGenerator::new_with_all_settings(seed, Dimension::OVERWORLD, None, None, None);
assert!(all_gen.enabled_structure_sets.is_none());
// Overrides with villages only
let village_overrides = vec!["minecraft:villages".to_string()];
let filtered_gen = VanillaGenerator::new_with_all_settings(
seed,
Dimension::OVERWORLD,
None,
None,
Some(&village_overrides),
);
let enabled = filtered_gen
.enabled_structure_sets
.expect("should have enabled structure set filter");
let village_idx = StructureSet::NAMES
.iter()
.position(|&n| n == "villages")
.unwrap();
let mineshaft_idx = StructureSet::NAMES
.iter()
.position(|&n| n == "mineshafts")
.unwrap();
assert!(enabled.contains(&village_idx));
assert!(!enabled.contains(&mineshaft_idx));
}
#[test]
fn vanilla_generator_fixed_biome_source() {
use crate::biome::BiomeSupplier;
let seed = Seed(42);
let biome_source = crate::world_info::BiomeSource::Fixed {
biome: "minecraft:desert".to_string(),
biome_type: "minecraft:fixed".to_string(),
};
let desert_gen = VanillaGenerator::new_with_all_settings(
seed,
Dimension::OVERWORLD,
None,
Some(&biome_source),
None,
);
let mut sampler =
crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler::generate(
&desert_gen.base_router.multi_noise,
);
let sampled_biome = desert_gen.biome_supplier.biome(0, 64, 0, &mut sampler);
assert_eq!(sampled_biome.registry_id, "desert");
}
}

View File

@ -109,7 +109,6 @@ pub fn find_nearest_structure_start(
generator: &WorldGenerator, generator: &WorldGenerator,
) -> Option<BlockPos> { ) -> Option<BlockPos> {
use crate::{ use crate::{
ProtoChunk,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier}, biome::{BiomeSupplier, MultiNoiseBiomeSupplier},
generation::{ generation::{
noise::router::{ noise::router::{
@ -156,15 +155,12 @@ pub fn find_nearest_structure_start(
region_origin_z + region_z_offset, region_origin_z + region_z_offset,
structure_set.placement.salt, structure_set.placement.salt,
); );
let placement_chunk = ProtoChunk::new(chunk_x, chunk_z, generator);
if !should_generate_structure( if !should_generate_structure(
&structure_set.placement, &structure_set.placement,
&noise_generator.structure_calculator, &noise_generator.structure_calculator,
chunk_x, chunk_x,
chunk_z, chunk_z,
global_cache, global_cache,
&placement_chunk,
&[],
) { ) {
continue; continue;
} }
@ -194,7 +190,12 @@ pub fn find_nearest_structure_start(
chunk_z, chunk_z,
random: create_chunk_random(world_seed, chunk_x, chunk_z), random: create_chunk_random(world_seed, chunk_x, chunk_z),
sea_level: settings.sea_level, sea_level: settings.sea_level,
min_y: noise_generator.dimension.min_y, min_y: (settings.shape.min_y as i32)
.max(noise_generator.dimension.min_y),
height: settings
.shape
.height
.min(noise_generator.dimension.height as u16),
height_sampler: Some(&mut height_sampler), height_sampler: Some(&mut height_sampler),
structure_key: Some(key), structure_key: Some(key),
}; };

View File

@ -18,7 +18,7 @@ pub mod rule;
pub mod structure; pub mod structure;
mod surface; mod surface;
use generator::{GeneratorInit, VanillaGenerator}; use generator::VanillaGenerator;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_util::{ use pumpkin_util::{
random::xoroshiro128::{Xoroshiro, XoroshiroSplitter}, random::xoroshiro128::{Xoroshiro, XoroshiroSplitter},
@ -32,6 +32,42 @@ pub fn get_world_gen(
is_flat: bool, is_flat: bool,
flat_layers: Vec<generator::FlatLayer>, flat_layers: Vec<generator::FlatLayer>,
flat_biome: String, flat_biome: String,
) -> Box<generator::WorldGenerator> {
get_world_gen_with_settings(seed, dimension, is_flat, flat_layers, flat_biome, None)
}
#[must_use]
pub fn get_world_gen_with_settings(
seed: Seed,
dimension: Dimension,
is_flat: bool,
flat_layers: Vec<generator::FlatLayer>,
flat_biome: String,
generator_settings: Option<&str>,
) -> Box<generator::WorldGenerator> {
get_world_gen_with_all_settings(
seed,
dimension,
is_flat,
flat_layers,
flat_biome,
generator_settings,
None,
None,
)
}
#[expect(clippy::too_many_arguments)]
#[must_use]
pub fn get_world_gen_with_all_settings(
seed: Seed,
dimension: Dimension,
is_flat: bool,
flat_layers: Vec<generator::FlatLayer>,
flat_biome: String,
generator_settings: Option<&str>,
biome_source: Option<&crate::world_info::BiomeSource>,
structure_overrides: Option<&[String]>,
) -> Box<generator::WorldGenerator> { ) -> Box<generator::WorldGenerator> {
if is_flat { if is_flat {
Box::new(generator::WorldGenerator::Flat(Box::new( Box::new(generator::WorldGenerator::Flat(Box::new(
@ -39,7 +75,13 @@ pub fn get_world_gen(
))) )))
} else { } else {
Box::new(generator::WorldGenerator::Noise(Box::new( Box::new(generator::WorldGenerator::Noise(Box::new(
VanillaGenerator::new(seed, dimension), VanillaGenerator::new_with_all_settings(
seed,
dimension,
generator_settings,
biome_source,
structure_overrides,
),
))) )))
} }
} }

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use pumpkin_data::block_properties::is_air; use pumpkin_data::block_properties::is_air;
use pumpkin_data::chunk::DoublePerlinNoiseParameters; use pumpkin_data::chunk::DoublePerlinNoiseParameters;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::{Fluid, FluidState}; use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::structures::{ use pumpkin_data::structures::{
Structure, StructureKeys, StructurePlacementType, StructureSet, WeightedEntry, Structure, StructureKeys, StructurePlacementType, StructureSet, WeightedEntry,
@ -12,14 +11,11 @@ use pumpkin_data::tag::RegistryKey;
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome}; use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
use pumpkin_data::{BlockId, BlockStateId, tag}; use pumpkin_data::{BlockId, BlockStateId, tag};
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter; use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
use pumpkin_util::random::{RandomImpl, get_carver_seed}; use pumpkin_util::random::{RandomImpl, get_large_feature_seed, legacy_rand::LegacyRand};
use pumpkin_util::{ use pumpkin_util::{
HeightMap, HeightMap,
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{ random::{RandomGenerator, get_decorator_seed, worldgen_random::WorldgenRandom},
RandomGenerator, get_decorator_seed, worldgen_random::WorldgenRandom,
xoroshiro128::Xoroshiro,
},
}; };
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -36,7 +32,7 @@ use super::{
section_coords, section_coords,
surface::{MaterialRuleContext, estimate_surface_height, terrain::SurfaceTerrainBuilder}, surface::{MaterialRuleContext, estimate_surface_height, terrain::SurfaceTerrainBuilder},
}; };
use crate::biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier}; use crate::biome::BiomeSupplier;
use crate::chunk::format::LightContainer; use crate::chunk::format::LightContainer;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight}; use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight};
use crate::chunk_system::{StagedChunkEnum, generation_cache::SurfaceBiomeNeighborhood}; use crate::chunk_system::{StagedChunkEnum, generation_cache::SurfaceBiomeNeighborhood};
@ -65,12 +61,6 @@ use pumpkin_nbt::compound::NbtCompound;
use crate::generation::structure::template::BlockPlacer; use crate::generation::structure::template::BlockPlacer;
use crate::tick::{ScheduledTick, TickPriority}; use crate::tick::{ScheduledTick, TickPriority};
enum ActiveSupplier {
Overworld(MultiNoiseBiomeSupplier),
Nether(MultiNoiseBiomeSupplier),
End(TheEndBiomeSupplier),
}
pub trait GenerationCache: HeightLimitView + BlockAccessor { pub trait GenerationCache: HeightLimitView + BlockAccessor {
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk; fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
fn get_center_chunk(&self) -> &ProtoChunk; fn get_center_chunk(&self) -> &ProtoChunk;
@ -795,18 +785,7 @@ impl ProtoChunk {
generator: &super::generator::VanillaGenerator, generator: &super::generator::VanillaGenerator,
multi_noise_sampler: &mut MultiNoiseSampler, multi_noise_sampler: &mut MultiNoiseSampler,
) { ) {
let dimension = &generator.dimension; let base_supplier: &dyn BiomeSupplier = &generator.biome_supplier;
let active_supplier = if dimension == &Dimension::THE_END {
ActiveSupplier::End(TheEndBiomeSupplier)
} else if dimension == &Dimension::THE_NETHER {
ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER)
} else {
ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD)
};
let base_supplier: &dyn BiomeSupplier = match &active_supplier {
ActiveSupplier::End(s) => s,
ActiveSupplier::Nether(s) | ActiveSupplier::Overworld(s) => s,
};
let blender = Blender::empty(); let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier); let biome_supplier = blender.get_biome_supplier(base_supplier);
let min_y = self.bottom_y(); let min_y = self.bottom_y();
@ -1289,17 +1268,14 @@ impl ProtoChunk {
crate::generation::structure::height_sampler::NoiseHeightSampler::new(generator); crate::generation::structure::height_sampler::NoiseHeightSampler::new(generator);
for (i, set) in StructureSet::ALL.iter().enumerate() { for (i, set) in StructureSet::ALL.iter().enumerate() {
let allowed_biomes = &generator.structure_allowed_biomes[&i]; if let Some(ref enabled) = generator.enabled_structure_sets
&& !enabled.contains(&i)
{
continue;
}
if !should_generate_structure( if !should_generate_structure(&set.placement, calculator, self.x, self.z, global_cache)
&set.placement, {
calculator,
self.x,
self.z,
global_cache,
self,
allowed_biomes,
) {
continue; continue;
} }
@ -1317,9 +1293,8 @@ impl ProtoChunk {
} }
let mut candidates = set.structures.to_vec(); let mut candidates = set.structures.to_vec();
let carver_seed = get_carver_seed(seed, self.x, self.z); let large_feature_seed = get_large_feature_seed(seed, self.x, self.z);
let mut random: RandomGenerator = let mut random = LegacyRand::from_seed(large_feature_seed);
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum(); let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum();
@ -1368,7 +1343,7 @@ impl ProtoChunk {
let center_z = chunk_pos::get_center_z(self.z); let center_z = chunk_pos::get_center_z(self.z);
let start_y = height_sampler.estimate_ocean_floor_height(center_x, center_z); let start_y = height_sampler.estimate_ocean_floor_height(center_x, center_z);
if !crate::generation::structure::structures::ocean_monument::has_valid_biomes( if !crate::generation::structure::structures::ocean_monument::has_valid_biomes(
&MultiNoiseBiomeSupplier::OVERWORLD, &generator.biome_supplier,
&mut sampler, &mut sampler,
self.x, self.x,
self.z, self.z,
@ -1407,7 +1382,6 @@ impl ProtoChunk {
debug_assert_eq!(self.stage, StagedChunkEnum::StructureStart); debug_assert_eq!(self.stage, StagedChunkEnum::StructureStart);
let random_config = &generator.random_config; let random_config = &generator.random_config;
let settings = generator.settings; let settings = generator.settings;
let dimension = &generator.dimension;
let noise_router = &generator.base_router; let noise_router = &generator.base_router;
let global_cache = &generator.global_structure_cache; let global_cache = &generator.global_structure_cache;
let calculator = &generator.structure_calculator; let calculator = &generator.structure_calculator;
@ -1419,18 +1393,7 @@ impl ProtoChunk {
let seed = random_config.seed as i64; let seed = random_config.seed as i64;
let active_supplier = if *dimension == Dimension::THE_END { let base_supplier: &dyn BiomeSupplier = &generator.biome_supplier;
ActiveSupplier::End(TheEndBiomeSupplier)
} else if *dimension == Dimension::THE_NETHER {
ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER)
} else {
ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD)
};
let base_supplier: &dyn BiomeSupplier = match &active_supplier {
ActiveSupplier::End(s) => s,
ActiveSupplier::Nether(s) | ActiveSupplier::Overworld(s) => s,
};
let blender = Blender::empty(); let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier); let biome_supplier = blender.get_biome_supplier(base_supplier);
let mut multi_noise_sampler = MultiNoiseSampler::generate(&noise_router.multi_noise); let mut multi_noise_sampler = MultiNoiseSampler::generate(&noise_router.multi_noise);
@ -1441,9 +1404,19 @@ impl ProtoChunk {
let mut references = Vec::new(); let mut references = Vec::new();
// Constant across every chunk in the dimension, so hoist it out of the loop // Constant across every chunk in the dimension, so hoist it out of the loop
// and out of the (cached) structure-start computation below. // and out of the (cached) structure-start computation below.
let chunk_min_y = self.bottom_y() as i32; // Matches vanilla WorldGenerationContext:
// minY = Math.max(heightAccessor.getMinY(), generator.getMinY())
// height = Math.min(heightAccessor.getHeight(), generator.getGenDepth())
let chunk_min_y = (self.generation_bottom_y() as i32).max(self.bottom_y() as i32);
let chunk_height = self.generation_height().min(self.height());
for (set_index, set) in StructureSet::ALL.iter().enumerate() { for (set_index, set) in StructureSet::ALL.iter().enumerate() {
if let Some(ref enabled) = generator.enabled_structure_sets
&& !enabled.contains(&set_index)
{
continue;
}
let mut candidate_chunks = Vec::new(); let mut candidate_chunks = Vec::new();
match &set.placement.placement_type { match &set.placement.placement_type {
@ -1465,15 +1438,8 @@ impl ProtoChunk {
} }
} }
} }
StructurePlacementType::ConcentricRings(rings) => { StructurePlacementType::ConcentricRings(_) => {
let allowed_biomes = Self::get_allowed_biomes(set); for &(cx, cz) in global_cache.get_stronghold_chunks() {
let strongholds = global_cache.get_or_calculate_strongholds(
seed,
rings,
self,
&allowed_biomes,
);
for &(cx, cz) in strongholds {
if (cx - self.x).abs() <= 8 && (cz - self.z).abs() <= 8 { if (cx - self.x).abs() <= 8 && (cz - self.z).abs() <= 8 {
candidate_chunks.push((cx, cz)); candidate_chunks.push((cx, cz));
} }
@ -1488,8 +1454,6 @@ impl ProtoChunk {
candidate_chunk_x, candidate_chunk_x,
candidate_chunk_z, candidate_chunk_z,
global_cache, global_cache,
self,
&generator.structure_allowed_biomes[&set_index],
) { ) {
continue; continue;
} }
@ -1520,6 +1484,7 @@ impl ProtoChunk {
), ),
sea_level: settings.sea_level, sea_level: settings.sea_level,
min_y: chunk_min_y, min_y: chunk_min_y,
height: chunk_height,
height_sampler: Some(&mut height_sampler), height_sampler: Some(&mut height_sampler),
structure_key: Some(entry.structure), structure_key: Some(entry.structure),
}; };

View File

@ -1,4 +1,4 @@
use pumpkin_data::{Block, BlockId, block_properties::blocks_movement}; use pumpkin_data::{Block, BlockId, BlockState, block_properties::blocks_movement};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -24,6 +24,7 @@ pub struct NoiseHeightSampler<'a> {
preliminary: SurfaceHeightEstimateSampler<'a>, preliminary: SurfaceHeightEstimateSampler<'a>,
heights: FxHashMap<(i32, i32), i32>, heights: FxHashMap<(i32, i32), i32>,
ocean_floor_heights: FxHashMap<(i32, i32), i32>, ocean_floor_heights: FxHashMap<(i32, i32), i32>,
columns: FxHashMap<(i32, i32), (i32, Vec<&'static BlockState>)>,
} }
impl<'a> NoiseHeightSampler<'a> { impl<'a> NoiseHeightSampler<'a> {
@ -42,10 +43,15 @@ impl<'a> NoiseHeightSampler<'a> {
preliminary, preliminary,
heights: FxHashMap::default(), heights: FxHashMap::default(),
ocean_floor_heights: FxHashMap::default(), ocean_floor_heights: FxHashMap::default(),
columns: FxHashMap::default(),
} }
} }
fn sample_column(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 { fn ensure_column_sampled(&mut self, x: i32, z: i32) {
if self.columns.contains_key(&(x, z)) {
return;
}
let settings = self.generator.settings; let settings = self.generator.settings;
let shape = &settings.shape; let shape = &settings.shape;
let fluid_sampler = StandardChunkFluidLevelSampler::new( let fluid_sampler = StandardChunkFluidLevelSampler::new(
@ -77,7 +83,8 @@ impl<'a> NoiseHeightSampler<'a> {
); );
let densities = noise.sample_density(); let densities = noise.sample_density();
for y in (0..volume.size_y).rev() { let mut blocks = Vec::with_capacity(volume.size_y);
for y in 0..volume.size_y {
let block_y = volume.block_y(y); let block_y = volume.block_y(y);
let index = volume.index_unchecked(0, y, 0); let index = volume.index_unchecked(0, y, 0);
let state = noise let state = noise
@ -89,6 +96,20 @@ impl<'a> NoiseHeightSampler<'a> {
&mut self.preliminary, &mut self.preliminary,
) )
.unwrap_or(self.generator.default_block); .unwrap_or(self.generator.default_block);
blocks.push(state);
}
self.columns
.insert((x, z), (i32::from(shape.min_y), blocks));
}
fn sample_column(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
self.ensure_column_sampled(x, z);
let Some((min_y, blocks)) = self.columns.get(&(x, z)) else {
return 0;
};
let min_y = *min_y;
for (idx, &state) in blocks.iter().enumerate().rev() {
let block_y = min_y + idx as i32;
if if ocean_floor { if if ocean_floor {
blocks_movement(state, BlockId::from_state_id(state.id)) blocks_movement(state, BlockId::from_state_id(state.id))
} else { } else {
@ -98,7 +119,7 @@ impl<'a> NoiseHeightSampler<'a> {
} }
} }
i32::from(shape.min_y) min_y
} }
} }
@ -124,4 +145,20 @@ impl HeightSampler for NoiseHeightSampler<'_> {
self.ocean_floor_heights.insert(key, height); self.ocean_floor_heights.insert(key, height);
height height
} }
fn sample_column_block(
&mut self,
block_x: i32,
block_z: i32,
y: i32,
) -> Option<&'static BlockState> {
self.ensure_column_sampled(block_x, block_z);
let (min_y, blocks) = self.columns.get(&(block_x, block_z))?;
let idx = y - *min_y;
if idx >= 0 && (idx as usize) < blocks.len() {
Some(blocks[idx as usize])
} else {
Some(Block::AIR.default_state)
}
}
} }

View File

@ -147,7 +147,8 @@ pub fn try_generate_structure(
chunk_z: chunk.z, chunk_z: chunk.z,
random, random,
sea_level, sea_level,
min_y: chunk.bottom_y() as i32, min_y: (chunk.generation_bottom_y() as i32).max(chunk.bottom_y() as i32),
height: chunk.generation_height().min(chunk.height()),
height_sampler, height_sampler,
structure_key: Some(*key), structure_key: Some(*key),
}; };

View File

@ -6,13 +6,18 @@ use pumpkin_data::structures::{
use pumpkin_util::{ use pumpkin_util::{
math::floor_div, math::floor_div,
random::{ random::{
RandomGenerator, RandomImpl, get_carver_seed, get_region_seed, legacy_rand::LegacyRand, RandomGenerator, RandomImpl, get_large_feature_seed, get_region_seed,
legacy_rand::LegacyRand,
}, },
}; };
use rayon::prelude::*;
use std::f64::consts::PI; use std::f64::consts::PI;
use std::sync::OnceLock; use std::sync::OnceLock;
use crate::ProtoChunk; use crate::biome::{BiomeSupplier, MultiNoiseBiomeSupplier};
use crate::generation::noise::router::{
multi_noise_sampler::MultiNoiseSampler, proto_noise_router::ProtoMultiNoiseRouter,
};
use dashmap::DashMap; use dashmap::DashMap;
use pumpkin_data::structures::StructureKeys; use pumpkin_data::structures::StructureKeys;
@ -32,6 +37,13 @@ pub struct GlobalStructureCache {
/// surrounding chunk whose structure references overlap it. /// surrounding chunk whose structure references overlap it.
structure_starts: OnceLock<DashMap<(StructureKeys, i32, i32), Option<StructurePosition>>>, structure_starts: OnceLock<DashMap<(StructureKeys, i32, i32), Option<StructurePosition>>>,
} }
struct RingTask {
initial_x: i32,
initial_z: i32,
search_rng: LegacyRand,
}
impl GlobalStructureCache { impl GlobalStructureCache {
/// Creates a new, empty global structure cache. /// Creates a new, empty global structure cache.
#[must_use] #[must_use]
@ -48,6 +60,10 @@ impl GlobalStructureCache {
.map_or(&[], std::vec::Vec::as_slice) .map_or(&[], std::vec::Vec::as_slice)
} }
pub fn init_strongholds(&self, chunks: Vec<(i32, i32)>) {
let _ = self.stronghold_chunks.set(chunks);
}
/// Returns the memoized structure start for the given structure and start chunk, /// Returns the memoized structure start for the given structure and start chunk,
/// computing it via `compute` on the first request and caching the result. /// computing it via `compute` on the first request and caching the result.
/// ///
@ -70,36 +86,35 @@ impl GlobalStructureCache {
computed computed
} }
/// Retrieves the list of chunk coordinates for Concentric Ring structures. /// Calculates the 128 ring positions matching vanilla Minecraft's
/// If the cache is empty, it calculates the 128 ring positions mathematically. /// `ChunkGeneratorStructureState.generateRingPositions` exactly.
#[allow(clippy::cast_precision_loss)] #[must_use]
pub fn get_or_calculate_strongholds( pub fn calculate_strongholds(
&self,
seed: i64, seed: i64,
placement: &ConcentricRingsStructurePlacement, placement: &ConcentricRingsStructurePlacement,
biome_supplier: &ProtoChunk, multi_noise: &ProtoMultiNoiseRouter,
allowed_biomes: &[u16], ) -> Vec<(i32, i32)> {
) -> &[(i32, i32)] { let distance_param = f64::from(placement.distance);
self.stronghold_chunks.get_or_init(|| { let mut spread = placement.spread;
let mut chunks = Vec::with_capacity(placement.count as usize); let count = placement.count;
let distance_param = f64::from(placement.distance); // Usually 32 let preferred_biomes = pumpkin_data::tag::get_tag_ids(
let mut spread = placement.spread; // Usually 3 pumpkin_data::tag::RegistryKey::WorldgenBiome,
let count = placement.count; // Usually 128 placement
.preferred_biomes
.strip_prefix('#')
.unwrap_or(placement.preferred_biomes),
)
.unwrap_or(&[]);
// The random generator for stronghold placement is based on the world seed and a fixed salt. let mut random = LegacyRand::from_seed(seed as u64);
// This ensures that the stronghold layout is consistent across all worlds with the same seed.
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(seed as u64));
// The initial angle includes a random rotation jitter for the whole world
let mut angle = random.next_f64() * PI * 2.0; let mut angle = random.next_f64() * PI * 2.0;
let mut position_in_circle = 0; let mut position_in_circle = 0;
let mut circle = 0; let mut circle = 0;
let mut tasks = Vec::with_capacity(count as usize);
for i in 0..count { for i in 0..count {
// 1. Distance Formula
// dist = (4 * spacing) + (spacing * ring_index * 6) + (random_jitter)
// The jitter is +/- (spacing * 1.25)
let dist = 4.0 * distance_param let dist = 4.0 * distance_param
+ distance_param * f64::from(circle) * 6.0 + distance_param * f64::from(circle) * 6.0
+ (random.next_f64() - 0.5) * (distance_param * 2.5); + (random.next_f64() - 0.5) * (distance_param * 2.5);
@ -107,48 +122,15 @@ impl GlobalStructureCache {
let initial_x = (angle.cos() * dist + 0.5).floor() as i32; let initial_x = (angle.cos() * dist + 0.5).floor() as i32;
let initial_z = (angle.sin() * dist + 0.5).floor() as i32; let initial_z = (angle.sin() * dist + 0.5).floor() as i32;
// 2. RNG Forking
// We must fork/split the random generator for the biome search.
// This keeps the main angle/distance sequence identical across all worlds.
let fork_seed = random.next_i64(); let fork_seed = random.next_i64();
let search_rng = LegacyRand::from_seed(fork_seed as u64);
let mut biome_search_generator = tasks.push(RingTask {
RandomGenerator::Legacy(LegacyRand::from_seed(fork_seed as u64)); initial_x,
initial_z,
search_rng,
});
// 3. Reservoir Sampling Biome Search
// Strongholds search the entire 112-block square
// and pick one valid location at random (Reservoir Sampling).
let mut found_pos = None;
let mut found_count = 0;
let center_block_x = (initial_x << 4) + 8;
let center_block_z = (initial_z << 4) + 8;
let step = 4;
let search_radius = 112;
for dz in (-search_radius..=search_radius).step_by(step as usize) {
for dx in (-search_radius..=search_radius).step_by(step as usize) {
let test_x = center_block_x + dx;
let test_z = center_block_z + dz;
let biome = biome_supplier.get_biome(test_x, 0, test_z);
if allowed_biomes.contains(&(biome.id as u16)) {
found_count += 1;
// Reservoir sampling: Pick the Nth valid biome with 1/N probability
if found_pos.is_none()
|| biome_search_generator.next_bounded_i32(found_count) == 0
{
found_pos = Some((test_x >> 4, test_z >> 4));
}
}
}
}
let (final_chunk_x, final_chunk_z) = found_pos.unwrap_or((initial_x, initial_z));
chunks.push((final_chunk_x, final_chunk_z));
// 4. Dynamic Ring Progression
angle += (PI * 2.0) / f64::from(spread); angle += (PI * 2.0) / f64::from(spread);
position_in_circle += 1; position_in_circle += 1;
@ -161,7 +143,97 @@ impl GlobalStructureCache {
angle += random.next_f64() * PI * 2.0; angle += random.next_f64() * PI * 2.0;
} }
} }
tasks
.into_par_iter()
.map(|mut task| {
let mut sampler = MultiNoiseSampler::generate(multi_noise);
let noise_center_x = (task.initial_x << 2) + 2;
let noise_center_z = (task.initial_z << 2) + 2;
let mut result = None;
let mut found = 0;
for z in -28..=28 {
for x in -28..=28 {
let noise_x = noise_center_x + x;
let noise_z = noise_center_z + z;
let biome = MultiNoiseBiomeSupplier::OVERWORLD.biome(
noise_x,
0,
noise_z,
&mut sampler,
);
if preferred_biomes.contains(&(biome.id as u16)) {
if result.is_none() || task.search_rng.next_bounded_i32(found + 1) == 0
{
result = Some((noise_x >> 2, noise_z >> 2));
}
found += 1;
}
}
}
result.unwrap_or((task.initial_x, task.initial_z))
})
.collect()
}
#[must_use]
pub fn calculate_strongholds_without_biomes(
seed: i64,
placement: &ConcentricRingsStructurePlacement,
) -> Vec<(i32, i32)> {
let distance_param = f64::from(placement.distance);
let mut spread = placement.spread;
let count = placement.count;
let mut random = LegacyRand::from_seed(seed as u64);
let mut angle = random.next_f64() * PI * 2.0;
let mut position_in_circle = 0;
let mut circle = 0;
let mut chunks = Vec::with_capacity(count as usize);
for i in 0..count {
let dist = 4.0 * distance_param
+ distance_param * f64::from(circle) * 6.0
+ (random.next_f64() - 0.5) * (distance_param * 2.5);
let initial_x = (angle.cos() * dist + 0.5).floor() as i32;
let initial_z = (angle.sin() * dist + 0.5).floor() as i32;
chunks.push((initial_x, initial_z));
angle += (PI * 2.0) / f64::from(spread);
position_in_circle += 1;
if position_in_circle == spread {
circle += 1;
position_in_circle = 0;
spread += 2 * spread / (circle + 1);
spread = spread.min(count - i);
angle += random.next_f64() * PI * 2.0;
}
}
chunks chunks
}
/// Retrieves the list of chunk coordinates for Concentric Ring structures.
/// If the cache is empty, it calculates the 128 ring positions.
pub fn get_or_calculate_strongholds(
&self,
seed: i64,
placement: &ConcentricRingsStructurePlacement,
multi_noise: Option<&ProtoMultiNoiseRouter>,
) -> &[(i32, i32)] {
self.stronghold_chunks.get_or_init(|| {
multi_noise.map_or_else(
|| Self::calculate_strongholds_without_biomes(seed, placement),
|multi_noise| Self::calculate_strongholds(seed, placement, multi_noise),
)
}) })
} }
} }
@ -173,15 +245,12 @@ impl Default for GlobalStructureCache {
} }
#[must_use] #[must_use]
// #[expect(clippy::too_many_arguments)]
pub fn should_generate_structure( pub fn should_generate_structure(
placement: &StructurePlacement, placement: &StructurePlacement,
calculator: &StructurePlacementCalculator, calculator: &StructurePlacementCalculator,
chunk_x: i32, chunk_x: i32,
chunk_z: i32, chunk_z: i32,
global_cache: &GlobalStructureCache, global_cache: &GlobalStructureCache,
biome_supplier: &ProtoChunk,
allowed_biomes: &[u16],
) -> bool { ) -> bool {
is_start_chunk( is_start_chunk(
&placement.placement_type, &placement.placement_type,
@ -190,8 +259,6 @@ pub fn should_generate_structure(
chunk_z, chunk_z,
placement.salt, placement.salt,
global_cache, global_cache,
biome_supplier,
allowed_biomes,
) && apply_frequency_reduction( ) && apply_frequency_reduction(
placement.frequency_reduction_method, placement.frequency_reduction_method,
calculator.seed, calculator.seed,
@ -205,18 +272,9 @@ pub fn should_generate_structure(
.strip_prefix("minecraft:") .strip_prefix("minecraft:")
.unwrap_or(zone.other_set); .unwrap_or(zone.other_set);
StructureSet::get(set_name).is_some_and(|set| { StructureSet::get(set_name).is_some_and(|set| {
let allowed_biomes = ProtoChunk::get_allowed_biomes(set);
(chunk_x - zone.chunk_count..=chunk_x + zone.chunk_count).any(|x| { (chunk_x - zone.chunk_count..=chunk_x + zone.chunk_count).any(|x| {
(chunk_z - zone.chunk_count..=chunk_z + zone.chunk_count).any(|z| { (chunk_z - zone.chunk_count..=chunk_z + zone.chunk_count).any(|z| {
should_generate_structure( should_generate_structure(&set.placement, calculator, x, z, global_cache)
&set.placement,
calculator,
x,
z,
global_cache,
biome_supplier,
&allowed_biomes,
)
}) })
}) })
}) })
@ -266,14 +324,13 @@ fn should_generate_frequency(
random.next_f32() < frequency random.next_f32() < frequency
} }
FrequencyReductionMethod::LegacyType3 => { FrequencyReductionMethod::LegacyType3 => {
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z); let feature_seed = get_large_feature_seed(seed as u64, chunk_x, chunk_z);
let mut random = LegacyRand::from_seed(carver_seed); let mut random = LegacyRand::from_seed(feature_seed);
random.next_f64() < f64::from(frequency) random.next_f64() < f64::from(frequency)
} }
} }
} }
#[expect(clippy::too_many_arguments)]
fn is_start_chunk( fn is_start_chunk(
placement_type: &StructurePlacementType, placement_type: &StructurePlacementType,
calculator: &StructurePlacementCalculator, calculator: &StructurePlacementCalculator,
@ -281,20 +338,14 @@ fn is_start_chunk(
chunk_z: i32, chunk_z: i32,
salt: u32, salt: u32,
global_cache: &GlobalStructureCache, global_cache: &GlobalStructureCache,
biome_supplier: &ProtoChunk,
allowed_biomes: &[u16],
) -> bool { ) -> bool {
match placement_type { match placement_type {
StructurePlacementType::RandomSpread(placement) => { StructurePlacementType::RandomSpread(placement) => {
is_start_chunk_random_spread(placement, calculator, chunk_x, chunk_z, salt) is_start_chunk_random_spread(placement, calculator, chunk_x, chunk_z, salt)
} }
StructurePlacementType::ConcentricRings(placement) => { StructurePlacementType::ConcentricRings(placement) => {
let strongholds = global_cache.get_or_calculate_strongholds( let strongholds =
calculator.seed, global_cache.get_or_calculate_strongholds(calculator.seed, placement, None);
placement,
biome_supplier,
allowed_biomes,
);
strongholds.contains(&(chunk_x, chunk_z)) strongholds.contains(&(chunk_x, chunk_z))
} }
} }
@ -351,24 +402,16 @@ fn is_start_chunk_random_spread(
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pumpkin_data::{ use pumpkin_data::structures::{
dimension::Dimension, RandomSpreadStructurePlacement, StructurePlacementCalculator, StructureSet,
structures::{RandomSpreadStructurePlacement, StructurePlacementCalculator, StructureSet},
}; };
use pumpkin_util::random::{ use pumpkin_util::random::{
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand, RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
}; };
use pumpkin_util::world_seed::Seed;
use crate::{ use crate::generation::structure::placement::{
ProtoChunk,
generation::{
get_world_gen,
structure::placement::{
GlobalStructureCache, apply_frequency_reduction, get_start_chunk_random_spread, GlobalStructureCache, apply_frequency_reduction, get_start_chunk_random_spread,
is_start_chunk, should_generate_structure, is_start_chunk, should_generate_structure,
},
},
}; };
#[test] #[test]
@ -395,14 +438,6 @@ mod tests {
let seed = 0; let seed = 0;
let calculator = StructurePlacementCalculator::new(seed); let calculator = StructurePlacementCalculator::new(seed);
let cache = GlobalStructureCache::new(); let cache = GlobalStructureCache::new();
let generator = get_world_gen(
Seed(seed as u64),
Dimension::OVERWORLD,
false,
Vec::new(),
String::new(),
);
let chunk = ProtoChunk::new(0, 0, &generator);
let outposts = &StructureSet::PILLAGER_OUTPOSTS; let outposts = &StructureSet::PILLAGER_OUTPOSTS;
let villages = &StructureSet::VILLAGES; let villages = &StructureSet::VILLAGES;
let excluded = (-1002, -595); let excluded = (-1002, -595);
@ -413,8 +448,6 @@ mod tests {
excluded.1, excluded.1,
outposts.placement.salt, outposts.placement.salt,
&cache, &cache,
&chunk,
&[],
)); ));
assert!(apply_frequency_reduction( assert!(apply_frequency_reduction(
outposts.placement.frequency_reduction_method, outposts.placement.frequency_reduction_method,
@ -433,8 +466,6 @@ mod tests {
excluded.1 + dz, excluded.1 + dz,
villages.placement.salt, villages.placement.salt,
&cache, &cache,
&chunk,
&[],
) )
}) })
})); }));
@ -445,8 +476,6 @@ mod tests {
excluded.0, excluded.0,
excluded.1, excluded.1,
&cache, &cache,
&chunk,
&[],
)); ));
} }
} }

View File

@ -341,6 +341,7 @@ mod tests {
random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)), random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)),
sea_level: 63, sea_level: 63,
min_y: 0, min_y: 0,
height: 128,
height_sampler: Some(&mut sampler), height_sampler: Some(&mut sampler),
structure_key: None, structure_key: None,
}) })
@ -374,6 +375,7 @@ mod tests {
random: RandomGenerator::Legacy(LegacyRand::from_seed(0)), random: RandomGenerator::Legacy(LegacyRand::from_seed(0)),
sea_level: 63, sea_level: 63,
min_y: 0, min_y: 0,
height: 128,
height_sampler: Some(&mut sampler), height_sampler: Some(&mut sampler),
structure_key: None, structure_key: None,
}) })

View File

@ -823,10 +823,9 @@ impl StructureGenerator for JigsawGenerator {
.structure_key .structure_key
.map(|key| pumpkin_data::structures::Structure::get(&key)); .map(|key| pumpkin_data::structures::Structure::get(&key));
let height = if context.min_y < 0 { 384 } else { 256 };
let start_y = if let Some(s) = structure { let start_y = if let Some(s) = structure {
s.start_height.map_or(context.sea_level, |hp| { s.start_height.map_or(context.sea_level, |hp| {
hp.get(&mut context.random, context.min_y as i8, height) hp.get(&mut context.random, context.min_y as i8, context.height)
}) })
} else { } else {
context.sea_level context.sea_level
@ -1012,6 +1011,7 @@ mod tests {
random: super::super::create_chunk_random(0, 0, 0), random: super::super::create_chunk_random(0, 0, 0),
sea_level: 63, sea_level: 63,
min_y: -64, min_y: -64,
height: 384,
height_sampler: None, height_sampler: None,
structure_key: Some(pumpkin_data::structures::StructureKeys::AncientCity), structure_key: Some(pumpkin_data::structures::StructureKeys::AncientCity),
}; };
@ -1123,6 +1123,7 @@ mod tests {
random: super::super::create_chunk_random(SEED, 75, -82), random: super::super::create_chunk_random(SEED, 75, -82),
sea_level: 63, sea_level: 63,
min_y: -64, min_y: -64,
height: 384,
height_sampler: Some(&mut height_sampler), height_sampler: Some(&mut height_sampler),
structure_key: Some(pumpkin_data::structures::StructureKeys::PillagerOutpost), structure_key: Some(pumpkin_data::structures::StructureKeys::PillagerOutpost),
}; };

View File

@ -619,7 +619,7 @@ impl JigsawPlacement {
box_.move_pos(0, y_offset, 0); box_.move_pos(0, y_offset, 0);
let piece_pos = adjusted_position.add(0, y_offset, 0); let piece_pos = adjusted_position.add(0, y_offset, 0);
let max_y = context.min_y + 384 - 1; let max_y = context.min_y + context.height as i32 - 1;
if is_start_too_close_to_world_height_limits(context.min_y, max_y, dimension_padding, &box_) if is_start_too_close_to_world_height_limits(context.min_y, max_y, dimension_padding, &box_)
{ {
tracing::debug!( tracing::debug!(

View File

@ -635,6 +635,7 @@ mod tests {
random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)), random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)),
sea_level: 63, sea_level: 63,
min_y: -64, min_y: -64,
height: 384,
height_sampler: Some(&mut sampler), height_sampler: Some(&mut sampler),
structure_key: None, structure_key: None,
}) })

View File

@ -835,6 +835,15 @@ pub trait HeightSampler {
fn estimate_ocean_floor_height(&mut self, block_x: i32, block_z: i32) -> i32 { fn estimate_ocean_floor_height(&mut self, block_x: i32, block_z: i32) -> i32 {
self.estimate_height(block_x, block_z) self.estimate_height(block_x, block_z)
} }
fn sample_column_block(
&mut self,
_block_x: i32,
_block_z: i32,
_y: i32,
) -> Option<&'static pumpkin_data::BlockState> {
None
}
} }
impl HeightSampler impl HeightSampler
@ -852,6 +861,7 @@ pub struct StructureGeneratorContext<'a> {
pub random: RandomGenerator, pub random: RandomGenerator,
pub sea_level: i32, pub sea_level: i32,
pub min_y: i32, pub min_y: i32,
pub height: u16,
pub height_sampler: Option<&'a mut dyn HeightSampler>, pub height_sampler: Option<&'a mut dyn HeightSampler>,
pub structure_key: Option<pumpkin_data::structures::StructureKeys>, pub structure_key: Option<pumpkin_data::structures::StructureKeys>,
} }

View File

@ -45,9 +45,10 @@ pub const FOSSILS: [&str; 14] = [
/// Vanilla height provider bounds for nether fossils. /// Vanilla height provider bounds for nether fossils.
/// From `nether_fossil.json`: uniform(absolute=32, `below_top=2`). /// From `nether_fossil.json`: uniform(absolute=32, `below_top=2`).
/// Vanilla `BelowTop`: height - 1 + `min_y` - offset = 256 - 1 + 0 - 2 = 253. /// In vanilla Nether, generator gen depth is 128:
/// `below_top=2`: height - 1 + `min_y` - offset = 128 - 1 + 0 - 2 = 125.
const HEIGHT_MIN: i32 = 32; const HEIGHT_MIN: i32 = 32;
const HEIGHT_MAX: i32 = 253; const HEIGHT_MAX: i32 = 125;
pub struct NetherFossilGenerator; pub struct NetherFossilGenerator;
@ -56,11 +57,12 @@ impl StructureGenerator for NetherFossilGenerator {
&self, &self,
mut context: StructureGeneratorContext<'_>, mut context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
// Vanilla random call order: // Vanilla random call order (NetherFossilStructure.java):
// 1. nextInt(16) for X offset within chunk // 1. nextInt(16) for X offset within chunk
// 2. nextInt(16) for Z offset within chunk // 2. nextInt(16) for Z offset within chunk
// 3. height.get(random) for initial Y (uniform 32..254) // 3. height.sample(random, generationContext) for initial Y (uniform 32..125)
// 4. Column scan (no random calls) // 4. Column scan downward to seaLevel (no random calls)
// If y <= seaLevel: return empty
// 5. Rotation.getRandom(random) - nextInt(4) // 5. Rotation.getRandom(random) - nextInt(4)
// 6. Util.getRandom(FOSSILS, random) - nextInt(14) // 6. Util.getRandom(FOSSILS, random) - nextInt(14)
@ -72,12 +74,37 @@ impl StructureGenerator for NetherFossilGenerator {
.map(|key| pumpkin_data::structures::Structure::get(&key)); .map(|key| pumpkin_data::structures::Structure::get(&key));
let initial_y = if let Some(hp) = structure.and_then(|s| s.start_height) { let initial_y = if let Some(hp) = structure.and_then(|s| s.start_height) {
hp.get(&mut context.random, context.min_y as i8, 256) hp.get(&mut context.random, context.min_y as i8, context.height)
} else { } else {
let height_range = HEIGHT_MAX - HEIGHT_MIN + 1; let height_range = HEIGHT_MAX - HEIGHT_MIN + 1;
HEIGHT_MIN + context.random.next_bounded_i32(height_range) HEIGHT_MIN + context.random.next_bounded_i32(height_range)
}; };
let mut y = initial_y;
if let Some(sampler) = context.height_sampler.as_deref_mut() {
let mut checked_column = false;
while y > context.sea_level {
let Some(current) = sampler.sample_column_block(x, z, y) else {
break;
};
checked_column = true;
y -= 1;
let below = sampler
.sample_column_block(x, z, y)
.unwrap_or(Block::AIR.default_state);
if current.is_air()
&& (Block::from_state_id(below.id) == &Block::SOUL_SAND
|| below.is_side_solid(BlockDirection::Up))
{
break;
}
}
if checked_column && y <= context.sea_level {
return None;
}
}
let rotation_index = context.random.next_bounded_i32(4) as u8; let rotation_index = context.random.next_bounded_i32(4) as u8;
let rotation = Rotation::from_index(rotation_index); let rotation = Rotation::from_index(rotation_index);
@ -85,23 +112,16 @@ impl StructureGenerator for NetherFossilGenerator {
let template_name = FOSSILS[template_index]; let template_name = FOSSILS[template_index];
let template = get_template(template_name)?; let template = get_template(template_name)?;
let position = Vector3::new(x, initial_y, z); let position = Vector3::new(x, y, z);
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
let piece = NetherFossilPiece::new( let piece = NetherFossilPiece::new(template, template_name.to_string(), position, rotation);
template,
template_name.to_string(),
position,
rotation,
initial_y,
context.sea_level,
);
collector.add_piece(Box::new(piece)); collector.add_piece(Box::new(piece));
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new(x, initial_y, z), start_pos: BlockPos::new(x, y, z),
collector: Arc::new(collector.into()), collector: Arc::new(collector.into()),
}) })
} }
@ -113,8 +133,6 @@ pub struct NetherFossilPiece {
pub template_name: String, pub template_name: String,
pub place_settings: StructurePlaceSettings, pub place_settings: StructurePlaceSettings,
pub template_position: Vector3<i32>, pub template_position: Vector3<i32>,
pub initial_y: i32,
pub sea_level: i32,
} }
impl NetherFossilPiece { impl NetherFossilPiece {
@ -124,8 +142,6 @@ impl NetherFossilPiece {
template_name: String, template_name: String,
template_position: Vector3<i32>, template_position: Vector3<i32>,
rotation: Rotation, rotation: Rotation,
initial_y: i32,
sea_level: i32,
) -> Self { ) -> Self {
let place_settings = make_settings(rotation); let place_settings = make_settings(rotation);
let bounding_box = template.get_bounding_box(&place_settings, template_position); let bounding_box = template.get_bounding_box(&place_settings, template_position);
@ -136,36 +152,9 @@ impl NetherFossilPiece {
template_name, template_name,
place_settings, place_settings,
template_position, template_position,
initial_y,
sea_level,
} }
} }
/// Vanilla column scan: search downward from `initial_y` for air above (soul sand OR solid block).
/// Returns the Y of the support block, or None if no valid position found above sea level.
fn find_placement_y(&self, chunk: &ProtoChunk) -> Option<i32> {
let origin = self.template_position;
let mut y = self.initial_y;
while y > self.sea_level {
let upper = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
y -= 1;
let lower = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
let upper_state = BlockState::from_id(upper);
let lower_state = BlockState::from_id(lower);
if upper_state.is_air()
&& (Block::from_state_id(lower) == &Block::SOUL_SAND
|| lower_state.is_side_solid(BlockDirection::Up))
{
break;
}
}
if y <= self.sea_level { None } else { Some(y) }
}
fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
let rotation = self.place_settings.get_rotation(); let rotation = self.place_settings.get_rotation();
let mirror = self.place_settings.get_mirror(); let mirror = self.place_settings.get_mirror();
@ -278,15 +267,6 @@ impl StructurePieceBase for NetherFossilPiece {
seed: i64, seed: i64,
chunk_box: &BlockBox, chunk_box: &BlockBox,
) { ) {
let Some(placement_y) = self.find_placement_y(chunk) else {
return;
};
self.template_position.y = placement_y;
self.piece.bounding_box = self
.template
.get_bounding_box(&self.place_settings, self.template_position);
let fossil_bb = self.piece.bounding_box; let fossil_bb = self.piece.bounding_box;
let mut enlarged_box = *chunk_box; let mut enlarged_box = *chunk_box;
enlarged_box.encompass(&fossil_bb); enlarged_box.encompass(&fossil_bb);
@ -311,3 +291,84 @@ fn make_settings(rotation: Rotation) -> StructurePlaceSettings {
}, },
])) ]))
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::generation::structure::structures::HeightSampler;
use pumpkin_data::Block;
struct MockColumnSampler {
ground_y: i32,
}
impl HeightSampler for MockColumnSampler {
fn estimate_height(&mut self, _block_x: i32, _block_z: i32) -> i32 {
self.ground_y
}
fn sample_column_block(
&mut self,
_block_x: i32,
_block_z: i32,
y: i32,
) -> Option<&'static BlockState> {
if y > self.ground_y {
Some(Block::AIR.default_state)
} else {
Some(Block::NETHERRACK.default_state)
}
}
}
#[test]
fn nether_fossil_never_places_on_roof() {
for seed in 0..100 {
let mut sampler = MockColumnSampler { ground_y: 60 };
let context = StructureGeneratorContext {
seed,
chunk_x: 0,
chunk_z: 0,
random: crate::generation::structure::structures::create_chunk_random(seed, 0, 0),
sea_level: 32,
min_y: 0,
height: 128,
height_sampler: Some(&mut sampler),
structure_key: Some(pumpkin_data::structures::StructureKeys::NetherFossil),
};
if let Some(pos) = NetherFossilGenerator.get_structure_position(context) {
assert!(
pos.start_pos.0.y <= 125,
"Fossil placed at {}, above logical nether height!",
pos.start_pos.0.y
);
assert!(
pos.start_pos.0.y >= 32,
"Fossil placed at {}, below sea level!",
pos.start_pos.0.y
);
}
}
}
#[test]
fn nether_fossil_rejects_when_no_ground_above_sea_level() {
let mut sampler = MockColumnSampler { ground_y: 20 }; // below sea_level (32)
let context = StructureGeneratorContext {
seed: 42,
chunk_x: 0,
chunk_z: 0,
random: crate::generation::structure::structures::create_chunk_random(42, 0, 0),
sea_level: 32,
min_y: 0,
height: 128,
height_sampler: Some(&mut sampler),
structure_key: Some(pumpkin_data::structures::StructureKeys::NetherFossil),
};
assert!(
NetherFossilGenerator
.get_structure_position(context)
.is_none()
);
}
}

View File

@ -545,6 +545,7 @@ mod tests {
random: RandomGenerator::Legacy(LegacyRand::from_seed(0)), random: RandomGenerator::Legacy(LegacyRand::from_seed(0)),
sea_level: 63, sea_level: 63,
min_y: -64, min_y: -64,
height: 384,
height_sampler: Some(&mut height_sampler), height_sampler: Some(&mut height_sampler),
structure_key: None, structure_key: None,
}; };

View File

@ -13,7 +13,7 @@ use crate::{
}, },
palette::has_random_ticking_fluid, palette::has_random_ticking_fluid,
}, },
generation::get_world_gen, generation::get_world_gen_with_all_settings,
tick::{OrderedTick, ScheduledTick, TickPriority}, tick::{OrderedTick, ScheduledTick, TickPriority},
world::WorldPortalExt, world::WorldPortalExt,
}; };
@ -202,42 +202,48 @@ impl Level {
let mut is_flat = false; let mut is_flat = false;
let mut flat_layers = Vec::new(); let mut flat_layers = Vec::new();
let mut flat_biome = "minecraft:plains".to_string(); let mut flat_biome = "minecraft:plains".to_string();
let mut generator_settings_name: Option<String> = None;
let mut biome_source: Option<crate::world_info::BiomeSource> = None;
let mut structure_overrides: Option<Vec<String>> = None;
if let Some(wgs) = crate::world_info::data_files::read_world_gen_settings(main_folder) if let Some(wgs) = crate::world_info::data_files::read_world_gen_settings(main_folder)
&& let Some(dim_settings) = wgs.dimensions.get(dimension.minecraft_name) && let Some(dim_settings) = wgs.dimensions.get(dimension.minecraft_name)
&& dim_settings.generator.generator_type == "minecraft:flat"
{ {
biome_source.clone_from(&dim_settings.generator.biome_source);
if dim_settings.generator.generator_type == "minecraft:flat" {
is_flat = true; is_flat = true;
if let Some(crate::world_info::GeneratorSettings::Compound(val)) = let flat_settings = dim_settings
.generator
.settings
.as_ref()
.and_then(crate::world_info::GeneratorSettings::as_flat_settings)
.or_else(|| {
crate::world_info::FlatLevelGeneratorPreset::from_name("classic_flat")
.map(|p| p.settings)
});
if let Some(flat_settings) = flat_settings {
flat_layers = flat_settings.to_flat_layers();
structure_overrides = flat_settings.structure_overrides_vec();
flat_biome = flat_settings.biome;
}
} else if let Some(crate::world_info::GeneratorSettings::Reference(s)) =
&dim_settings.generator.settings &dim_settings.generator.settings
{ {
if let Some(b) = val.get("biome").and_then(|v| v.as_str()) { generator_settings_name = Some(s.clone());
flat_biome = b.to_string();
}
if let Some(list) = val.get("layers").and_then(|v| v.as_array()) {
for layer in list {
let block = layer
.get("block")
.and_then(|v| v.as_str())
.unwrap_or("minecraft:air")
.to_string();
let height = layer
.get("height")
.and_then(serde_json::Value::as_i64)
.unwrap_or(1) as i32;
flat_layers.push(crate::generation::generator::FlatLayer { block, height });
}
}
} }
} }
let seed = Seed(seed as u64); let seed = Seed(seed as u64);
let world_gen: Arc<WorldGenerator> = Arc::from(get_world_gen( let world_gen: Arc<WorldGenerator> = Arc::from(get_world_gen_with_all_settings(
seed, seed,
dimension, dimension,
is_flat, is_flat,
flat_layers, flat_layers,
flat_biome, flat_biome,
generator_settings_name.as_deref(),
biome_source.as_ref(),
structure_overrides.as_deref(),
)); ));
let chunk_saver = match &level_config.chunk { let chunk_saver = match &level_config.chunk {

View File

@ -277,17 +277,19 @@ pub fn read_world_gen_settings(level_folder: &Path) -> Option<WorldGenSettings>
.get_string("type") .get_string("type")
.unwrap_or("minecraft:multi_noise") .unwrap_or("minecraft:multi_noise")
.to_string(); .to_string();
match bs_c.get_string("preset") { if let Some(preset) = bs_c.get_string("preset") {
Some(preset) => {
crate::world_info::BiomeSource::WithPreset { crate::world_info::BiomeSource::WithPreset {
preset: preset.to_string(), preset: preset.to_string(),
biome_type, biome_type,
} }
} else if let Some(biome) = bs_c.get_string("biome") {
crate::world_info::BiomeSource::Fixed {
biome: biome.to_string(),
biome_type,
} }
None => { } else {
crate::world_info::BiomeSource::Simple { biome_type } crate::world_info::BiomeSource::Simple { biome_type }
} }
}
}); });
dimensions.insert( dimensions.insert(
dim_name.to_string(), dim_name.to_string(),
@ -356,6 +358,10 @@ pub fn write_world_gen_settings(
bs_comp.put_string("preset", preset.clone()); bs_comp.put_string("preset", preset.clone());
bs_comp.put_string("type", biome_type.clone()); bs_comp.put_string("type", biome_type.clone());
} }
crate::world_info::BiomeSource::Fixed { biome, biome_type } => {
bs_comp.put_string("biome", biome.clone());
bs_comp.put_string("type", biome_type.clone());
}
crate::world_info::BiomeSource::Simple { biome_type } => { crate::world_info::BiomeSource::Simple { biome_type } => {
bs_comp.put_string("type", biome_type.clone()); bs_comp.put_string("type", biome_type.clone());
} }

View File

@ -195,6 +195,18 @@ pub enum GeneratorSettings {
Compound(serde_json::Value), Compound(serde_json::Value),
} }
impl GeneratorSettings {
#[must_use]
pub fn as_flat_settings(&self) -> Option<FlatPresetSettings> {
match self {
Self::Reference(preset_name) => {
FlatLevelGeneratorPreset::from_name(preset_name).map(|p| p.settings)
}
Self::Compound(val) => serde_json::from_value(val.clone()).ok(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)] #[serde(untagged)]
pub enum BiomeSource { pub enum BiomeSource {
@ -203,12 +215,177 @@ pub enum BiomeSource {
#[serde(rename = "type")] #[serde(rename = "type")]
biome_type: String, biome_type: String,
}, },
Fixed {
biome: String,
#[serde(rename = "type")]
biome_type: String,
},
Simple { Simple {
#[serde(rename = "type")] #[serde(rename = "type")]
biome_type: String, biome_type: String,
}, },
} }
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct WorldPreset {
pub dimensions: Dimensions,
}
impl WorldPreset {
pub const NORMAL_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/normal.json"
);
pub const AMPLIFIED_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/amplified.json"
);
pub const LARGE_BIOMES_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/large_biomes.json"
);
pub const FLAT_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/flat.json"
);
pub const FLAT_ALL_DIMENSIONS_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/flat_all_dimensions.json"
);
pub const SINGLE_BIOME_SURFACE_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/single_biome_surface.json"
);
pub const DEBUG_ALL_BLOCK_STATES_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/world_preset/debug_all_block_states.json"
);
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
let raw = match name {
"normal" => Self::NORMAL_RAW,
"amplified" => Self::AMPLIFIED_RAW,
"large_biomes" => Self::LARGE_BIOMES_RAW,
"flat" => Self::FLAT_RAW,
"flat_all_dimensions" => Self::FLAT_ALL_DIMENSIONS_RAW,
"single_biome_surface" => Self::SINGLE_BIOME_SURFACE_RAW,
"debug_all_block_states" => Self::DEBUG_ALL_BLOCK_STATES_RAW,
_ => return None,
};
serde_json::from_str(raw).ok()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum StructureOverrides {
Single(String),
Multiple(Vec<String>),
}
impl StructureOverrides {
#[must_use]
pub fn to_vec(&self) -> Vec<String> {
match self {
Self::Single(s) => vec![s.clone()],
Self::Multiple(list) => list.clone(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct FlatPresetLayer {
pub block: String,
#[serde(default = "default_layer_height")]
pub height: i32,
}
const fn default_layer_height() -> i32 {
1
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct FlatPresetSettings {
pub biome: String,
#[serde(default)]
pub features: bool,
#[serde(default)]
pub lakes: bool,
#[serde(default)]
pub layers: Vec<FlatPresetLayer>,
#[serde(default)]
pub structure_overrides: Option<StructureOverrides>,
}
impl FlatPresetSettings {
#[must_use]
pub fn to_flat_layers(&self) -> Vec<crate::generation::generator::FlatLayer> {
self.layers
.iter()
.map(|l| crate::generation::generator::FlatLayer {
block: l.block.clone(),
height: l.height,
})
.collect()
}
#[must_use]
pub fn structure_overrides_vec(&self) -> Option<Vec<String>> {
self.structure_overrides
.as_ref()
.map(StructureOverrides::to_vec)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct FlatLevelGeneratorPreset {
pub display: String,
pub settings: FlatPresetSettings,
}
impl FlatLevelGeneratorPreset {
pub const BOTTOMLESS_PIT_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/bottomless_pit.json"
);
pub const CLASSIC_FLAT_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/classic_flat.json"
);
pub const DESERT_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/desert.json"
);
pub const OVERWORLD_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/overworld.json"
);
pub const REDSTONE_READY_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/redstone_ready.json"
);
pub const SNOWY_KINGDOM_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/snowy_kingdom.json"
);
pub const THE_VOID_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/the_void.json"
);
pub const TUNNELERS_DREAM_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/tunnelers_dream.json"
);
pub const WATER_WORLD_RAW: &'static str = include_str!(
"../../../../assets/datapacks/26_2/data/minecraft/worldgen/flat_level_generator_preset/water_world.json"
);
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
let raw = match name {
"bottomless_pit" => Self::BOTTOMLESS_PIT_RAW,
"classic_flat" => Self::CLASSIC_FLAT_RAW,
"desert" => Self::DESERT_RAW,
"overworld" => Self::OVERWORLD_RAW,
"redstone_ready" => Self::REDSTONE_READY_RAW,
"snowy_kingdom" => Self::SNOWY_KINGDOM_RAW,
"the_void" => Self::THE_VOID_RAW,
"tunnelers_dream" => Self::TUNNELERS_DREAM_RAW,
"water_world" => Self::WATER_WORLD_RAW,
_ => return None,
};
serde_json::from_str(raw).ok()
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub struct DataPacks { pub struct DataPacks {
@ -223,7 +400,7 @@ pub struct DataPacks {
impl WorldGenSettings { impl WorldGenSettings {
#[must_use] #[must_use]
pub fn new(seed: Seed) -> Self { pub fn new(seed: Seed) -> Self {
// TODO: Adjust according to enabled worlds Self::from_preset_name("minecraft:normal", seed).unwrap_or_else(|| {
let mut dimensions = Dimensions::new(); let mut dimensions = Dimensions::new();
dimensions.insert( dimensions.insert(
"minecraft:overworld".to_string(), "minecraft:overworld".to_string(),
@ -245,7 +422,9 @@ impl WorldGenSettings {
"minecraft:the_nether".to_string(), "minecraft:the_nether".to_string(),
Dimension { Dimension {
generator: Generator { generator: Generator {
settings: Some(GeneratorSettings::Reference("minecraft:nether".to_string())), settings: Some(GeneratorSettings::Reference(
"minecraft:nether".to_string(),
)),
biome_source: Some(BiomeSource::WithPreset { biome_source: Some(BiomeSource::WithPreset {
preset: "minecraft:nether".to_string(), preset: "minecraft:nether".to_string(),
biome_type: "minecraft:multi_noise".to_string(), biome_type: "minecraft:multi_noise".to_string(),
@ -273,6 +452,20 @@ impl WorldGenSettings {
dimensions, dimensions,
seed: seed.0 as i64, seed: seed.0 as i64,
} }
})
}
#[must_use]
pub fn from_preset(preset: &WorldPreset, seed: Seed) -> Self {
Self {
dimensions: preset.dimensions.clone(),
seed: seed.0 as i64,
}
}
#[must_use]
pub fn from_preset_name(preset_name: &str, seed: Seed) -> Option<Self> {
WorldPreset::from_name(preset_name).map(|preset| Self::from_preset(&preset, seed))
} }
} }
@ -340,6 +533,15 @@ impl LevelData {
} }
} }
#[must_use]
pub fn default_with_preset(seed: Seed, preset_name: &str) -> Self {
let mut data = Self::default(seed);
if let Some(wgs) = WorldGenSettings::from_preset_name(preset_name, seed) {
data.world_gen_settings = wgs;
}
data
}
#[must_use] #[must_use]
pub fn from_world_generator( pub fn from_world_generator(
seed: Seed, seed: Seed,
@ -386,3 +588,148 @@ impl From<std::io::Error> for WorldInfoError {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn world_presets_parse() {
let preset_names = [
"normal",
"amplified",
"large_biomes",
"flat",
"flat_all_dimensions",
"single_biome_surface",
"debug_all_block_states",
];
for name in preset_names {
let preset = WorldPreset::from_name(name)
.unwrap_or_else(|| panic!("failed to load preset {name}"));
assert!(
!preset.dimensions.is_empty(),
"preset {name} has no dimensions"
);
let with_prefix = format!("minecraft:{name}");
assert!(
WorldPreset::from_name(&with_prefix).is_some(),
"failed to load preset with prefix {with_prefix}"
);
}
}
#[test]
fn world_gen_settings_from_preset() {
let seed = Seed(12345);
let normal = WorldGenSettings::from_preset_name("normal", seed).unwrap();
assert_eq!(normal.seed, 12345);
let overworld = normal.dimensions.get("minecraft:overworld").unwrap();
assert_eq!(overworld.generator.generator_type, "minecraft:noise");
assert_eq!(
overworld.generator.settings,
Some(GeneratorSettings::Reference(
"minecraft:overworld".to_string()
))
);
let amplified = WorldGenSettings::from_preset_name("amplified", seed).unwrap();
let amp_overworld = amplified.dimensions.get("minecraft:overworld").unwrap();
assert_eq!(
amp_overworld.generator.settings,
Some(GeneratorSettings::Reference(
"minecraft:amplified".to_string()
))
);
let large_biomes = WorldGenSettings::from_preset_name("large_biomes", seed).unwrap();
let lb_overworld = large_biomes.dimensions.get("minecraft:overworld").unwrap();
assert_eq!(
lb_overworld.generator.settings,
Some(GeneratorSettings::Reference(
"minecraft:large_biomes".to_string()
))
);
let flat = WorldGenSettings::from_preset_name("flat", seed).unwrap();
let flat_overworld = flat.dimensions.get("minecraft:overworld").unwrap();
assert_eq!(flat_overworld.generator.generator_type, "minecraft:flat");
assert!(matches!(
flat_overworld.generator.settings,
Some(GeneratorSettings::Compound(_))
));
let flat_settings = flat_overworld
.generator
.settings
.as_ref()
.unwrap()
.as_flat_settings()
.unwrap();
assert_eq!(flat_settings.biome, "minecraft:plains");
assert_eq!(flat_settings.layers.len(), 3);
assert_eq!(flat_settings.to_flat_layers().len(), 3);
}
#[test]
fn flat_level_generator_presets_parse() {
let preset_names = [
"bottomless_pit",
"classic_flat",
"desert",
"overworld",
"redstone_ready",
"snowy_kingdom",
"the_void",
"tunnelers_dream",
"water_world",
];
for name in preset_names {
let preset = FlatLevelGeneratorPreset::from_name(name)
.unwrap_or_else(|| panic!("failed to load flat preset {name}"));
assert!(!preset.settings.biome.is_empty());
assert!(!preset.settings.layers.is_empty());
assert_eq!(
preset.settings.layers.len(),
preset.settings.to_flat_layers().len()
);
let with_prefix = format!("minecraft:{name}");
assert!(
FlatLevelGeneratorPreset::from_name(&with_prefix).is_some(),
"failed to load flat preset with prefix {with_prefix}"
);
let from_ref = GeneratorSettings::Reference(with_prefix)
.as_flat_settings()
.expect("failed to parse reference as flat settings");
assert_eq!(from_ref.biome, preset.settings.biome);
}
// Test classic flat structure overrides (single string "minecraft:villages")
let classic = FlatLevelGeneratorPreset::from_name("classic_flat").unwrap();
assert_eq!(
classic.settings.structure_overrides_vec(),
Some(vec!["minecraft:villages".to_string()])
);
// Test tunnelers dream structure overrides (multiple)
let tunnelers = FlatLevelGeneratorPreset::from_name("tunnelers_dream").unwrap();
assert_eq!(
tunnelers.settings.structure_overrides_vec(),
Some(vec![
"minecraft:mineshafts".to_string(),
"minecraft:strongholds".to_string(),
])
);
// Test the void structure overrides (empty list)
let void = FlatLevelGeneratorPreset::from_name("the_void").unwrap();
assert_eq!(
void.settings.structure_overrides_vec(),
Some(Vec::<String>::new())
);
}
}

View File

@ -47,7 +47,7 @@ impl MushroomPlantBlock {
return true; return true;
} }
let is_dark_enough = world.is_none_or(|world| world.get_max_local_raw_brightness(pos) < 13); let is_dark_enough = world.is_none_or(|world| world.get_raw_brightness(pos, 0) < 13);
is_dark_enough && Self::may_place_on(block_accessor.get_block_state(&below_pos)) is_dark_enough && Self::may_place_on(block_accessor.get_block_state(&below_pos))
} }
@ -71,33 +71,40 @@ impl MushroomPlantBlock {
let tree_height = mushroom_tree_height(&mut rand::rng()); let tree_height = mushroom_tree_height(&mut rand::rng());
if !world.is_in_height_limit(pos.0.y + tree_height + 1) { let min_y = world.get_bottom_y();
let max_y = world.get_top_y();
if pos.0.y < min_y + 1 || pos.0.y + tree_height + 1 > max_y {
return false; return false;
} }
let foliage_radius = if block == &Block::BROWN_MUSHROOM { let below_block = world.get_block(&pos.down());
3 let can_place_on = if block == &Block::BROWN_MUSHROOM {
below_block.has_tag(&tag::Block::MINECRAFT_HUGE_BROWN_MUSHROOM_CAN_PLACE_ON)
} else { } else {
2 below_block.has_tag(&tag::Block::MINECRAFT_HUGE_RED_MUSHROOM_CAN_PLACE_ON)
};
if !can_place_on {
return false;
}
for dy in 0..=tree_height {
let radius = if block == &Block::BROWN_MUSHROOM {
if dy <= 3 { 0 } else { 3 }
} else if (dy < tree_height && dy >= tree_height - 3) || dy == tree_height {
2
} else {
0
}; };
for dy in 0..=tree_height + 1 {
let radius = if dy <= 3 { 0 } else { foliage_radius };
for dx in -radius..=radius { for dx in -radius..=radius {
for dz in -radius..=radius { for dz in -radius..=radius {
let check_pos = pos.add(dx, dy, dz); let check_pos = pos.add(dx, dy, dz);
if check_pos == *pos {
continue;
}
if !world.is_loaded(&check_pos) { if !world.is_loaded(&check_pos) {
return false; return false;
} }
let check_state = world.get_block_state(&check_pos); let check_state = world.get_block_state(&check_pos);
let check_block = world.get_block(&check_pos); let check_block = world.get_block(&check_pos);
let can_replace = check_state.is_air() if !check_state.is_air() && !check_block.has_tag(&tag::Block::MINECRAFT_LEAVES)
|| check_block.has_tag(&tag::Block::MINECRAFT_LEAVES) {
|| check_block.has_tag(&tag::Block::MINECRAFT_REPLACEABLE_BY_MUSHROOMS)
|| check_state.replaceable();
if !can_replace {
return false; return false;
} }
} }
@ -116,6 +123,14 @@ impl MushroomPlantBlock {
} }
} }
fn place_mushroom_block(world: &Arc<World>, pos: &BlockPos, state_id: BlockStateId) {
let block = world.get_block(pos);
let state = world.get_block_state(pos);
if state.is_air() || block.has_tag(&tag::Block::MINECRAFT_REPLACEABLE_BY_MUSHROOMS) {
world.set_block_state(pos, state_id, BlockFlags::NOTIFY_ALL);
}
}
fn place_huge_brown_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i32) { fn place_huge_brown_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i32) {
let radius = 3; let radius = 3;
let cap_y = pos.0.y + tree_height; let cap_y = pos.0.y + tree_height;
@ -138,7 +153,7 @@ fn place_huge_brown_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i3
}; };
let state_id = props.to_state_id(&Block::BROWN_MUSHROOM_BLOCK); let state_id = props.to_state_id(&Block::BROWN_MUSHROOM_BLOCK);
let cap_pos = BlockPos::new(pos.0.x + j, cap_y, pos.0.z + k); let cap_pos = BlockPos::new(pos.0.x + j, cap_y, pos.0.z + k);
world.set_block_state(&cap_pos, state_id, BlockFlags::NOTIFY_ALL); place_mushroom_block(world, &cap_pos, state_id);
} }
} }
@ -153,7 +168,7 @@ fn place_huge_brown_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i3
let stem_state = stem_props.to_state_id(&Block::MUSHROOM_STEM); let stem_state = stem_props.to_state_id(&Block::MUSHROOM_STEM);
for i in 0..tree_height { for i in 0..tree_height {
let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z); let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z);
world.set_block_state(&stem_pos, stem_state, BlockFlags::NOTIFY_ALL); place_mushroom_block(world, &stem_pos, stem_state);
} }
} }
@ -182,7 +197,7 @@ fn place_huge_red_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i32)
}; };
let state_id = props.to_state_id(&Block::RED_MUSHROOM_BLOCK); let state_id = props.to_state_id(&Block::RED_MUSHROOM_BLOCK);
let cap_pos = BlockPos::new(pos.0.x + l, pos.0.y + i, pos.0.z + m); let cap_pos = BlockPos::new(pos.0.x + l, pos.0.y + i, pos.0.z + m);
world.set_block_state(&cap_pos, state_id, BlockFlags::NOTIFY_ALL); place_mushroom_block(world, &cap_pos, state_id);
} }
} }
} }
@ -198,7 +213,7 @@ fn place_huge_red_mushroom(world: &Arc<World>, pos: &BlockPos, tree_height: i32)
let stem_state = stem_props.to_state_id(&Block::MUSHROOM_STEM); let stem_state = stem_props.to_state_id(&Block::MUSHROOM_STEM);
for i in 0..tree_height { for i in 0..tree_height {
let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z); let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z);
world.set_block_state(&stem_pos, stem_state, BlockFlags::NOTIFY_ALL); place_mushroom_block(world, &stem_pos, stem_state);
} }
} }

View File

@ -99,7 +99,8 @@ impl JigsawBlockEntity {
chunk_z: position.chunk_position().y, chunk_z: position.chunk_position().y,
random: RandomGenerator::Xoroshiro(Xoroshiro::from_seed(rand::rng().next_u64())), random: RandomGenerator::Xoroshiro(Xoroshiro::from_seed(rand::rng().next_u64())),
sea_level: 63, sea_level: 63,
min_y: -64, min_y: world.dimension.min_y,
height: world.dimension.height as u16,
height_sampler: None, height_sampler: None,
structure_key: None, structure_key: None,
}; };

View File

@ -200,7 +200,11 @@ impl CommandExecutor for PlaceJigsawExecutor {
chunk_z: 0, chunk_z: 0,
random, random,
sea_level: settings.sea_level, sea_level: settings.sea_level,
min_y: world_gen.dimension().min_y, min_y: (settings.shape.min_y as i32).max(world_gen.dimension().min_y),
height: settings
.shape
.height
.min(world_gen.dimension().height as u16),
height_sampler: None, height_sampler: None,
structure_key: None, structure_key: None,
}; };
@ -307,7 +311,11 @@ impl CommandExecutor for PlaceStructureExecutor {
chunk_z: block_pos.0.z >> 4, chunk_z: block_pos.0.z >> 4,
random, random,
sea_level: settings.sea_level, sea_level: settings.sea_level,
min_y: world_gen.dimension().min_y, min_y: (settings.shape.min_y as i32).max(world_gen.dimension().min_y),
height: settings
.shape
.height
.min(world_gen.dimension().height as u16),
height_sampler: None, height_sampler: None,
structure_key: Some(key), structure_key: Some(key),
}, },
@ -355,7 +363,11 @@ impl CommandExecutor for PlaceStructureExecutor {
chunk_z: block_pos.0.z >> 4, chunk_z: block_pos.0.z >> 4,
random, random,
sea_level: settings.sea_level, sea_level: settings.sea_level,
min_y: world_gen.dimension().min_y, min_y: (settings.shape.min_y as i32).max(world_gen.dimension().min_y),
height: settings
.shape
.height
.min(world_gen.dimension().height as u16),
height_sampler: None, height_sampler: None,
structure_key: Some(key), structure_key: Some(key),
}, },

View File

@ -5661,12 +5661,16 @@ impl World {
self.environment_attributes().get_value_activity(baby, pos) self.environment_attributes().get_value_activity(baby, pos)
} }
pub fn get_max_local_raw_brightness(&self, pos: &BlockPos) -> u8 { pub fn get_raw_brightness(&self, pos: &BlockPos, sky_darken: u8) -> u8 {
let sky_light = (self.get_sky_light_level(pos) as i32 - self.get_sky_darken()).max(0) as u8; let sky_light = self.get_sky_light_level(pos).saturating_sub(sky_darken);
let block_light = self.get_block_light_level(pos).unwrap_or(0); let block_light = self.get_block_light_level(pos).unwrap_or(0);
sky_light.max(block_light) sky_light.max(block_light)
} }
pub fn get_max_local_raw_brightness(&self, pos: &BlockPos) -> u8 {
self.get_raw_brightness(pos, self.get_sky_darken() as u8)
}
pub fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> { pub fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> {
self.level self.level
.light_engine .light_engine
@ -7402,7 +7406,7 @@ impl WorldPortalExt for WorldPortal {
) -> bool { ) -> bool {
self.0.block_registry.can_place_at( self.0.block_registry.can_place_at(
None, None,
None, Some(&self.0),
block_accessor, block_accessor,
None, None,
block, block,

View File

@ -12,6 +12,24 @@ struct GeyserBiomeMapping {
bedrock_id: u8, bedrock_id: u8,
} }
fn deserialize_carvers<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVec {
String(String),
Vec(Vec<String>),
}
match Option::<StringOrVec>::deserialize(deserializer)? {
Some(StringOrVec::String(s)) => Ok(vec![s]),
Some(StringOrVec::Vec(v)) => Ok(v),
None => Ok(Vec::new()),
}
}
/// Raw deserialization shape for a single biome entry from `biome.json`. /// Raw deserialization shape for a single biome entry from `biome.json`.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Biome { pub struct Biome {
@ -23,7 +41,8 @@ pub struct Biome {
downfall: f32, downfall: f32,
/// Optional modifier that changes how temperature is applied. /// Optional modifier that changes how temperature is applied.
temperature_modifier: Option<TemperatureModifier>, temperature_modifier: Option<TemperatureModifier>,
//carvers: Vec<String>, #[serde(default, deserialize_with = "deserialize_carvers")]
pub carvers: Vec<String>,
/// Nested lists of feature resource-location strings applied during world generation. /// Nested lists of feature resource-location strings applied during world generation.
features: Vec<Vec<String>>, features: Vec<Vec<String>>,
/// Probability per chunk tick that a creature spawns, if not overridden per-biome. /// Probability per chunk tick that a creature spawns, if not overridden per-biome.
@ -261,7 +280,15 @@ pub fn build() -> TokenStream {
let has_precipitation = biome.has_precipitation; let has_precipitation = biome.has_precipitation;
let temperature = biome.temperature; let temperature = biome.temperature;
let downfall = biome.downfall; let downfall = biome.downfall;
// let carvers = &biome.carvers; let carvers: Vec<TokenStream> = biome
.carvers
.iter()
.map(|c| {
let name = c.strip_prefix("minecraft:").unwrap_or(c);
let variant_name = format_ident!("{}", name.to_uppercase());
quote! { &crate::carver::#variant_name }
})
.collect();
let features: Vec<TokenStream> = biome let features: Vec<TokenStream> = biome
.features .features
.iter() .iter()
@ -371,6 +398,7 @@ pub fn build() -> TokenStream {
#downfall #downfall
), ),
features: &[#(#features),*], features: &[#(#features),*],
carvers: &[#(#carvers),*],
creature_spawn_probability: #creature_spawn_probability, creature_spawn_probability: #creature_spawn_probability,
spawners: #spawners, spawners: #spawners,
spawn_costs: phf::phf_map! { spawn_costs: phf::phf_map! {
@ -401,7 +429,7 @@ pub fn build() -> TokenStream {
pub registry_id: &'static str, pub registry_id: &'static str,
pub be_network_id: u8, pub be_network_id: u8,
pub weather: Weather, pub weather: Weather,
// carvers: &'static [&str], pub carvers: &'static [&'static crate::carver::CarverConfig],
pub features: &'static [&'static [crate::placed_feature::PlacedFeature]], pub features: &'static [&'static [crate::placed_feature::PlacedFeature]],
pub creature_spawn_probability: f32, pub creature_spawn_probability: f32,
pub spawners: SpawnGroups, pub spawners: SpawnGroups,

View File

@ -117,29 +117,34 @@ pub fn build() -> TokenStream {
}; };
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset}; use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
#[derive(Clone, Debug)]
pub enum HeightProvider { pub enum HeightProvider {
Uniform(UniformHeightProvider), Uniform(UniformHeightProvider),
Trapezoid(TrapezoidHeightProvider), Trapezoid(TrapezoidHeightProvider),
VeryBiasedToBottom(VeryBiasedToBottomHeightProvider), VeryBiasedToBottom(VeryBiasedToBottomHeightProvider),
} }
#[derive(Clone, Debug)]
pub struct UniformHeightProvider { pub struct UniformHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
} }
#[derive(Clone, Debug)]
pub struct TrapezoidHeightProvider { pub struct TrapezoidHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub plateau: Option<i32>, pub plateau: Option<i32>,
} }
#[derive(Clone, Debug)]
pub struct VeryBiasedToBottomHeightProvider { pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset, pub min_inclusive: YOffset,
pub max_inclusive: YOffset, pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZero<u32>>, pub inner: Option<std::num::NonZero<u32>>,
} }
#[derive(Clone, Debug)]
pub struct CaveCarverConfig { pub struct CaveCarverConfig {
pub count: IntProvider, pub count: IntProvider,
pub horizontal_radius_multiplier: FloatProvider, pub horizontal_radius_multiplier: FloatProvider,
@ -167,6 +172,7 @@ pub fn build() -> TokenStream {
} }
} }
#[derive(Clone, Debug)]
pub struct CanyonShapeConfig { pub struct CanyonShapeConfig {
pub distance_factor: FloatProvider, pub distance_factor: FloatProvider,
pub thickness: FloatProvider, pub thickness: FloatProvider,
@ -177,16 +183,19 @@ pub fn build() -> TokenStream {
pub y_scale: f32, pub y_scale: f32,
} }
#[derive(Clone, Debug)]
pub struct CanyonCarverConfig { pub struct CanyonCarverConfig {
pub vertical_rotation: FloatProvider, pub vertical_rotation: FloatProvider,
pub shape: CanyonShapeConfig, pub shape: CanyonShapeConfig,
} }
#[derive(Clone, Debug)]
pub enum CarverAdditionalConfig { pub enum CarverAdditionalConfig {
Cave(CaveCarverConfig), Cave(CaveCarverConfig),
Canyon(CanyonCarverConfig), Canyon(CanyonCarverConfig),
} }
#[derive(Clone, Debug)]
pub struct CarverConfig { pub struct CarverConfig {
pub probability: f32, pub probability: f32,
pub y: HeightProvider, pub y: HeightProvider,

View File

@ -598,6 +598,21 @@ pub fn build() -> TokenStream {
} }
impl MaterialRule { impl MaterialRule {
#[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" | "amplified" | "large_biomes" => Some(&OVERWORLD),
"nether" => Some(&NETHER),
"end" => Some(&END),
"bedrock_floor" => Some(&BEDROCK_FLOOR),
"bedrock_roof" => Some(&BEDROCK_ROOF),
"overworld_caves" => Some(&OVERWORLD_CAVES),
"overworld_floating_islands" => Some(&OVERWORLD_FLOATING_ISLANDS),
_ => None,
}
}
#[must_use] #[must_use]
pub fn from_dimension(dimension: &Dimension) -> &'static Self { pub fn from_dimension(dimension: &Dimension) -> &'static Self {
if dimension == &Dimension::OVERWORLD { if dimension == &Dimension::OVERWORLD {

View File

@ -2975,12 +2975,16 @@ fn load_vanilla_noise_routers() -> NoiseRouterReprs {
pub fn build() -> TokenStream { pub fn build() -> TokenStream {
let mut reprs: NoiseRouterReprs = load_vanilla_noise_routers(); let mut reprs: NoiseRouterReprs = load_vanilla_noise_routers();
let _ = reprs.overworld_amplified;
let _ = reprs.overworld_large_biomes;
let _ = reprs.end_islands; let _ = reprs.end_islands;
let (overworld_router, overworld_compiled) = let (overworld_router, overworld_compiled) =
reprs.overworld.into_token_stream_compiled("overworld"); reprs.overworld.into_token_stream_compiled("overworld");
let (amplified_router, amplified_compiled) = reprs
.overworld_amplified
.into_token_stream_compiled("amplified");
let (large_biomes_router, large_biomes_compiled) = reprs
.overworld_large_biomes
.into_token_stream_compiled("large_biomes");
let (nether_router, nether_compiled) = reprs.nether.into_token_stream_compiled("nether"); let (nether_router, nether_compiled) = reprs.nether.into_token_stream_compiled("nether");
let (end_router, end_compiled) = reprs.end.into_token_stream_compiled("end"); let (end_router, end_compiled) = reprs.end.into_token_stream_compiled("end");
@ -3004,6 +3008,8 @@ pub fn build() -> TokenStream {
} }
#overworld_compiled #overworld_compiled
#amplified_compiled
#large_biomes_compiled
#nether_compiled #nether_compiled
#end_compiled #end_compiled
@ -3366,7 +3372,35 @@ pub fn build() -> TokenStream {
} }
pub const OVERWORLD_BASE_NOISE_ROUTER: BaseNoiseRouters = #overworld_router; pub const OVERWORLD_BASE_NOISE_ROUTER: BaseNoiseRouters = #overworld_router;
pub const AMPLIFIED_BASE_NOISE_ROUTER: BaseNoiseRouters = #amplified_router;
pub const LARGE_BIOMES_BASE_NOISE_ROUTER: BaseNoiseRouters = #large_biomes_router;
pub const NETHER_BASE_NOISE_ROUTER: BaseNoiseRouters = #nether_router; pub const NETHER_BASE_NOISE_ROUTER: BaseNoiseRouters = #nether_router;
pub const END_BASE_NOISE_ROUTER: BaseNoiseRouters = #end_router; pub const END_BASE_NOISE_ROUTER: BaseNoiseRouters = #end_router;
impl BaseNoiseRouters {
#[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" => Some(&OVERWORLD_BASE_NOISE_ROUTER),
"amplified" => Some(&AMPLIFIED_BASE_NOISE_ROUTER),
"large_biomes" => Some(&LARGE_BIOMES_BASE_NOISE_ROUTER),
"nether" => Some(&NETHER_BASE_NOISE_ROUTER),
"end" => Some(&END_BASE_NOISE_ROUTER),
_ => None,
}
}
#[must_use]
pub fn from_dimension(dimension: &crate::dimension::Dimension) -> &'static Self {
if dimension == &crate::dimension::Dimension::OVERWORLD {
&OVERWORLD_BASE_NOISE_ROUTER
} else if dimension == &crate::dimension::Dimension::THE_NETHER {
&NETHER_BASE_NOISE_ROUTER
} else {
&END_BASE_NOISE_ROUTER
}
}
}
} }
} }

View File

@ -327,6 +327,7 @@ pub fn build() -> TokenStream {
use crate::BlockState; use crate::BlockState;
use crate::biome::ParameterPoint; use crate::biome::ParameterPoint;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoiseSettings { pub struct NoiseSettings {
pub aquifers_enabled: bool, pub aquifers_enabled: bool,
pub ore_veins_enabled: bool, pub ore_veins_enabled: bool,
@ -340,6 +341,7 @@ pub fn build() -> TokenStream {
pub type GenerationSettings = NoiseSettings; pub type GenerationSettings = NoiseSettings;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GenerationShapeConfig { pub struct GenerationShapeConfig {
pub min_y: i8, pub min_y: i8,
pub height: u16, pub height: u16,
@ -392,6 +394,21 @@ pub fn build() -> TokenStream {
impl NoiseSettings { impl NoiseSettings {
#const_defs #const_defs
#[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" => Some(&Self::OVERWORLD),
"amplified" => Some(&Self::AMPLIFIED),
"large_biomes" => Some(&Self::LARGE_BIOMES),
"nether" => Some(&Self::NETHER),
"end" => Some(&Self::END),
"caves" => Some(&Self::CAVES),
"floating_islands" => Some(&Self::FLOATING_ISLANDS),
_ => None,
}
}
#[must_use] #[must_use]
pub fn from_dimension(dimension: &Dimension) -> &'static Self { pub fn from_dimension(dimension: &Dimension) -> &'static Self {
if dimension == &Dimension::OVERWORLD { if dimension == &Dimension::OVERWORLD {