fix: many generation fixes
This commit is contained in:
parent
8d4639e25a
commit
df983f8477
@ -109,7 +109,7 @@ villager = ["item", "sound"]
|
||||
slot_ranges = []
|
||||
|
||||
noise_parameter = []
|
||||
biome = []
|
||||
biome = ["carver"]
|
||||
chunk_status = []
|
||||
entity_pose = []
|
||||
entity_status = []
|
||||
|
||||
@ -15,6 +15,7 @@ pub struct Biome {
|
||||
pub registry_id: &'static str,
|
||||
pub be_network_id: u8,
|
||||
pub weather: Weather,
|
||||
pub carvers: &'static [&'static crate::carver::CarverConfig],
|
||||
pub features: &'static [&'static [crate::placed_feature::PlacedFeature]],
|
||||
pub creature_spawn_probability: f32,
|
||||
pub spawners: SpawnGroups,
|
||||
@ -152,6 +153,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.03f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -310,6 +316,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -448,6 +459,7 @@ impl Biome {
|
||||
crate::placed_feature::PlacedFeature::OreDebrisSmall,
|
||||
],
|
||||
],
|
||||
carvers: &[&crate::carver::NETHER_CAVE],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -542,6 +554,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -678,6 +695,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -825,6 +847,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -972,6 +999,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1098,6 +1130,7 @@ impl Biome {
|
||||
crate::placed_feature::PlacedFeature::CrimsonForestVegetation,
|
||||
],
|
||||
],
|
||||
carvers: &[&crate::carver::NETHER_CAVE],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1200,6 +1233,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1352,6 +1390,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1504,6 +1547,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[],
|
||||
@ -1588,6 +1636,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1743,6 +1796,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -1911,6 +1969,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2068,6 +2131,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2223,6 +2291,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2296,6 +2369,7 @@ impl Biome {
|
||||
be_network_id: 9u8,
|
||||
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
|
||||
features: &[],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -2330,6 +2404,7 @@ impl Biome {
|
||||
&[],
|
||||
&[crate::placed_feature::PlacedFeature::ChorusPlant],
|
||||
],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -2353,6 +2428,7 @@ impl Biome {
|
||||
be_network_id: 9u8,
|
||||
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
|
||||
features: &[],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -2439,6 +2515,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.03f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2595,6 +2676,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2752,6 +2838,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -2911,6 +3002,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3056,6 +3152,11 @@ impl Biome {
|
||||
&[crate::placed_feature::PlacedFeature::GlowLichen],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3190,6 +3291,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3328,6 +3434,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3476,6 +3587,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.07f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3613,6 +3729,11 @@ impl Biome {
|
||||
&[crate::placed_feature::PlacedFeature::GlowLichen],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3749,6 +3870,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -3921,6 +4047,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4088,6 +4219,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4224,6 +4360,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4369,6 +4510,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4511,6 +4657,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[],
|
||||
@ -4573,6 +4724,7 @@ impl Biome {
|
||||
crate::placed_feature::PlacedFeature::RedMushroomNormal,
|
||||
],
|
||||
],
|
||||
carvers: &[&crate::carver::NETHER_CAVE],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4685,6 +4837,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -4842,6 +4999,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5000,6 +5162,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5173,6 +5340,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5338,6 +5510,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5469,6 +5646,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5636,6 +5818,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5779,6 +5966,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -5950,6 +6142,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6069,6 +6266,7 @@ impl Biome {
|
||||
be_network_id: 9u8,
|
||||
weather: Weather::new(false, 0.5f32, TemperatureModifier::None, 0.5f32),
|
||||
features: &[&[crate::placed_feature::PlacedFeature::EndIslandDecorated]],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -6152,6 +6350,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6281,6 +6484,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.07f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6426,6 +6634,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6568,6 +6781,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6699,6 +6917,7 @@ impl Biome {
|
||||
&[],
|
||||
&[crate::placed_feature::PlacedFeature::SpringLava],
|
||||
],
|
||||
carvers: &[&crate::carver::NETHER_CAVE],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6801,6 +7020,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -6952,6 +7176,11 @@ impl Biome {
|
||||
&[crate::placed_feature::PlacedFeature::GlowLichen],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7080,6 +7309,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7207,6 +7441,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7339,6 +7578,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7510,6 +7754,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7677,6 +7926,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -7793,6 +8047,7 @@ impl Biome {
|
||||
&[],
|
||||
&[crate::placed_feature::PlacedFeature::EndPlatform],
|
||||
],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -7828,6 +8083,7 @@ impl Biome {
|
||||
&[],
|
||||
&[crate::placed_feature::PlacedFeature::VoidStartPlatform],
|
||||
],
|
||||
carvers: &[],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[],
|
||||
@ -7911,6 +8167,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -8044,6 +8305,7 @@ impl Biome {
|
||||
crate::placed_feature::PlacedFeature::TwistingVines,
|
||||
],
|
||||
],
|
||||
carvers: &[&crate::carver::NETHER_CAVE],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[Spawner {
|
||||
@ -8134,6 +8396,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -8291,6 +8558,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -8448,6 +8720,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -8603,6 +8880,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.1f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
@ -8776,6 +9058,11 @@ impl Biome {
|
||||
],
|
||||
&[crate::placed_feature::PlacedFeature::FreezeTopLayer],
|
||||
],
|
||||
carvers: &[
|
||||
&crate::carver::CAVE,
|
||||
&crate::carver::CAVE_EXTRA_UNDERGROUND,
|
||||
&crate::carver::CANYON,
|
||||
],
|
||||
creature_spawn_probability: 0.04f32,
|
||||
spawners: SpawnGroups {
|
||||
monster: &[
|
||||
|
||||
@ -8,25 +8,30 @@ use pumpkin_util::math::int_provider::{
|
||||
UniformIntProvider, VeryBiasedToBottomIntProvider,
|
||||
};
|
||||
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HeightProvider {
|
||||
Uniform(UniformHeightProvider),
|
||||
Trapezoid(TrapezoidHeightProvider),
|
||||
VeryBiasedToBottom(VeryBiasedToBottomHeightProvider),
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UniformHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrapezoidHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
pub plateau: Option<i32>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VeryBiasedToBottomHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
pub inner: Option<std::num::NonZero<u32>>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CaveCarverConfig {
|
||||
pub count: IntProvider,
|
||||
pub horizontal_radius_multiplier: FloatProvider,
|
||||
@ -52,6 +57,7 @@ impl CaveCarverConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CanyonShapeConfig {
|
||||
pub distance_factor: FloatProvider,
|
||||
pub thickness: FloatProvider,
|
||||
@ -61,14 +67,17 @@ pub struct CanyonShapeConfig {
|
||||
pub vertical_radius_center_factor: f32,
|
||||
pub y_scale: f32,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CanyonCarverConfig {
|
||||
pub vertical_rotation: FloatProvider,
|
||||
pub shape: CanyonShapeConfig,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CarverAdditionalConfig {
|
||||
Cave(CaveCarverConfig),
|
||||
Canyon(CanyonCarverConfig),
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CarverConfig {
|
||||
pub probability: f32,
|
||||
pub y: HeightProvider,
|
||||
|
||||
@ -23,6 +23,20 @@ pub enum MaterialRule {
|
||||
Badlands(BadLandsMaterialRule),
|
||||
}
|
||||
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]
|
||||
pub fn from_dimension(dimension: &Dimension) -> &'static Self {
|
||||
if dimension == &Dimension::OVERWORLD {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
use crate::BlockState;
|
||||
use crate::biome::ParameterPoint;
|
||||
use crate::dimension::Dimension;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct NoiseSettings {
|
||||
pub aquifers_enabled: bool,
|
||||
pub ore_veins_enabled: bool,
|
||||
@ -13,6 +14,7 @@ pub struct NoiseSettings {
|
||||
pub spawn_target: &'static [ParameterPoint],
|
||||
}
|
||||
pub type GenerationSettings = NoiseSettings;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct GenerationShapeConfig {
|
||||
pub min_y: i8,
|
||||
pub height: u16,
|
||||
@ -224,6 +226,20 @@ impl NoiseSettings {
|
||||
],
|
||||
};
|
||||
#[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 {
|
||||
if dimension == &Dimension::OVERWORLD {
|
||||
&Self::OVERWORLD
|
||||
|
||||
@ -170,6 +170,28 @@ pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
|
||||
^ 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)]
|
||||
pub trait RandomImpl {
|
||||
fn split(&mut self) -> Self;
|
||||
|
||||
@ -7,6 +7,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TheEndBiomeSupplier;
|
||||
|
||||
impl TheEndBiomeSupplier {
|
||||
|
||||
@ -25,6 +25,7 @@ pub trait BiomeSupplier {
|
||||
fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MultiNoiseBiomeSupplier {
|
||||
source: &'static BiomeTree,
|
||||
}
|
||||
@ -33,6 +34,16 @@ impl MultiNoiseBiomeSupplier {
|
||||
pub const OVERWORLD: Self = Self::new(&OVERWORLD_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 {
|
||||
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]
|
||||
pub fn hash_seed(seed: u64) -> i64 {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
@ -3,19 +3,22 @@ pub mod cave;
|
||||
pub mod mask;
|
||||
|
||||
use crate::ProtoChunk;
|
||||
use crate::biome::BiomeSupplier;
|
||||
use crate::generation::GlobalRandomConfig;
|
||||
use crate::generation::generator::VanillaGenerator;
|
||||
use crate::generation::noise::aquifer_sampler::CarverAquiferSampler;
|
||||
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
|
||||
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
|
||||
use crate::generation::noise::router::surface_height_sampler::{
|
||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||
};
|
||||
use crate::generation::surface::rule::try_apply_material_rule;
|
||||
use crate::generation::surface::terrain::SurfaceTerrainBuilder;
|
||||
use crate::generation::surface::{MaterialRuleContext, steep_material_condition};
|
||||
use crate::generation::{biome_coords, section_coords};
|
||||
use pumpkin_data::block_state::BlockState;
|
||||
use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE};
|
||||
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig};
|
||||
#[cfg(test)]
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
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::random::{RandomGenerator, RandomImpl};
|
||||
|
||||
const OVERWORLD_CARVERS: [&CarverConfig; 3] = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON];
|
||||
const NETHER_CARVERS: [&CarverConfig; 1] = [&NETHER_CAVE];
|
||||
|
||||
pub struct CarverBlockIds {
|
||||
pub air: &'static BlockState,
|
||||
pub cave_air: &'static BlockState,
|
||||
@ -63,6 +63,8 @@ pub struct CarvingContext<'a> {
|
||||
pub secondary_noise: &'a DoublePerlinNoiseSampler,
|
||||
pub terrain_builder: &'a SurfaceTerrainBuilder,
|
||||
pub sea_level: i32,
|
||||
pub default_block: &'static BlockState,
|
||||
pub default_fluid: &'static BlockState,
|
||||
pub surface_rule: &'a MaterialRule,
|
||||
pub surface_height_sampler: SurfaceHeightEstimateSampler<'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) {
|
||||
// Vanilla applyCarvers uses a range of 8 chunks (17x17 area)
|
||||
let radius = 8;
|
||||
@ -126,7 +129,8 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
let chunk_z = 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 surface_config = SurfaceHeightSamplerBuilderOptions::new(
|
||||
@ -156,11 +160,15 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
secondary_noise: &generator.terrain_cache.secondary_noise,
|
||||
terrain_builder: &generator.terrain_cache.terrain_builder,
|
||||
sea_level: generator.settings.sea_level,
|
||||
default_block: generator.settings.default_block,
|
||||
default_fluid: generator.settings.default_fluid,
|
||||
surface_rule: generator.surface_rule,
|
||||
surface_height_sampler,
|
||||
carver_aquifer,
|
||||
};
|
||||
|
||||
let center_biome = chunk.get_biome(0, 0, 0);
|
||||
|
||||
let mut run = CarveRun {
|
||||
ctx: &mut context,
|
||||
chunk,
|
||||
@ -176,9 +184,18 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
let carver_z = chunk_z + dz;
|
||||
let carver_chunk_pos = Vector2::new(carver_x, carver_z);
|
||||
|
||||
// In vanilla, carvers are per-biome. Here we use the hardcoded list but
|
||||
// maintain the random seed logic.
|
||||
for (index, &config) in carvers_to_use.iter().enumerate() {
|
||||
let carver_biome = if dx == 0 && dz == 0 {
|
||||
center_biome
|
||||
} 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(
|
||||
generator.random_config.seed + index as u64,
|
||||
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(
|
||||
run: &mut CarveRun,
|
||||
x: i32,
|
||||
@ -294,7 +301,7 @@ fn overworld_carve_state(
|
||||
.state
|
||||
.map(|state| (state, result.should_schedule_fluid_update))
|
||||
} else if y < run.ctx.sea_level {
|
||||
Some((run.ids.lava, false))
|
||||
Some((run.ctx.default_fluid, false))
|
||||
} else {
|
||||
Some((run.ids.air, false))
|
||||
}
|
||||
@ -384,6 +391,8 @@ fn with_carve_run_options<F>(
|
||||
secondary_noise: &generator.terrain_cache.secondary_noise,
|
||||
terrain_builder: &generator.terrain_cache.terrain_builder,
|
||||
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_height_sampler,
|
||||
carver_aquifer,
|
||||
@ -545,4 +554,23 @@ mod tests {
|
||||
let index = (x & 15) as usize * 16 + (z & 15) as usize;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::{
|
||||
math::position::BlockPos,
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
@ -14,6 +15,19 @@ pub fn mushroom_tree_height(random: &mut RandomGenerator) -> i32 {
|
||||
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;
|
||||
|
||||
impl HugeBrownMushroomFeature {
|
||||
@ -23,14 +37,38 @@ impl HugeBrownMushroomFeature {
|
||||
pub fn generate<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
_min_y: i8,
|
||||
_height: u16,
|
||||
min_y: i8,
|
||||
height: u16,
|
||||
_feature: pumpkin_data::placed_feature::PlacedFeature,
|
||||
random: &mut RandomGenerator,
|
||||
pos: BlockPos,
|
||||
) -> bool {
|
||||
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 cap_y = pos.0.y + tree_height;
|
||||
for j in -radius..=radius {
|
||||
@ -52,7 +90,7 @@ impl HugeBrownMushroomFeature {
|
||||
};
|
||||
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);
|
||||
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));
|
||||
for i in 0..tree_height {
|
||||
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
|
||||
|
||||
@ -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 super::huge_brown_mushroom::place_mushroom_block;
|
||||
use crate::generation::proto_chunk::GenerationCache;
|
||||
|
||||
pub struct HugeRedMushroomFeature;
|
||||
@ -12,14 +14,42 @@ impl HugeRedMushroomFeature {
|
||||
pub fn generate<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
_min_y: i8,
|
||||
_height: u16,
|
||||
min_y: i8,
|
||||
height: u16,
|
||||
_feature: pumpkin_data::placed_feature::PlacedFeature,
|
||||
random: &mut RandomGenerator,
|
||||
pos: BlockPos,
|
||||
) -> bool {
|
||||
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;
|
||||
for i in (tree_height - 3)..=tree_height {
|
||||
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 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));
|
||||
for i in 0..tree_height {
|
||||
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
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
use pumpkin_data::BlockState;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::material_rule::MaterialRule;
|
||||
use pumpkin_data::noise_router::{
|
||||
END_BASE_NOISE_ROUTER, NETHER_BASE_NOISE_ROUTER, OVERWORLD_BASE_NOISE_ROUTER,
|
||||
};
|
||||
use pumpkin_data::noise_router::BaseNoiseRouters;
|
||||
use pumpkin_data::noise_settings::NoiseSettings;
|
||||
|
||||
use super::noise::router::proto_noise_router::ProtoNoiseRouters;
|
||||
@ -128,6 +126,7 @@ pub struct VanillaGenerator {
|
||||
pub dimension: Dimension,
|
||||
pub settings: &'static NoiseSettings,
|
||||
pub surface_rule: &'static MaterialRule,
|
||||
pub biome_supplier: crate::biome::ActiveBiomeSupplier,
|
||||
pub biome_mixer_seed: i64,
|
||||
|
||||
pub terrain_cache: TerrainCache,
|
||||
@ -137,6 +136,7 @@ pub struct VanillaGenerator {
|
||||
pub global_structure_cache: crate::generation::structure::placement::GlobalStructureCache,
|
||||
pub structure_calculator: StructurePlacementCalculator,
|
||||
pub structure_allowed_biomes: FxHashMap<usize, Vec<u16>>,
|
||||
pub enabled_structure_sets: Option<rustc_hash::FxHashSet<usize>>,
|
||||
}
|
||||
|
||||
impl VanillaGenerator {
|
||||
@ -154,30 +154,41 @@ impl VanillaGenerator {
|
||||
&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 {
|
||||
fn new(seed: Seed, dimension: Dimension) -> Self {
|
||||
let settings = NoiseSettings::from_dimension(&dimension);
|
||||
let surface_rule = MaterialRule::from_dimension(&dimension);
|
||||
#[must_use]
|
||||
pub fn new_with_all_settings(
|
||||
seed: Seed,
|
||||
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);
|
||||
|
||||
// TODO: The generation settings contains (part of?) the noise routers too; do we keep the separate or
|
||||
// use only the generation settings?
|
||||
let base = if dimension == Dimension::OVERWORLD {
|
||||
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 base = settings_name
|
||||
.and_then(BaseNoiseRouters::from_name)
|
||||
.unwrap_or_else(|| BaseNoiseRouters::from_dimension(&dimension));
|
||||
let terrain_cache = TerrainCache::from_random(&random_config);
|
||||
|
||||
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 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 {
|
||||
random_config,
|
||||
base_router,
|
||||
dimension,
|
||||
settings,
|
||||
surface_rule,
|
||||
biome_supplier,
|
||||
biome_mixer_seed,
|
||||
terrain_cache,
|
||||
default_block,
|
||||
global_structure_cache:
|
||||
crate::generation::structure::placement::GlobalStructureCache::new(),
|
||||
global_structure_cache,
|
||||
structure_calculator: StructurePlacementCalculator::new(seed.0 as i64),
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +109,6 @@ pub fn find_nearest_structure_start(
|
||||
generator: &WorldGenerator,
|
||||
) -> Option<BlockPos> {
|
||||
use crate::{
|
||||
ProtoChunk,
|
||||
biome::{BiomeSupplier, MultiNoiseBiomeSupplier},
|
||||
generation::{
|
||||
noise::router::{
|
||||
@ -156,15 +155,12 @@ pub fn find_nearest_structure_start(
|
||||
region_origin_z + region_z_offset,
|
||||
structure_set.placement.salt,
|
||||
);
|
||||
let placement_chunk = ProtoChunk::new(chunk_x, chunk_z, generator);
|
||||
if !should_generate_structure(
|
||||
&structure_set.placement,
|
||||
&noise_generator.structure_calculator,
|
||||
chunk_x,
|
||||
chunk_z,
|
||||
global_cache,
|
||||
&placement_chunk,
|
||||
&[],
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@ -194,7 +190,12 @@ pub fn find_nearest_structure_start(
|
||||
chunk_z,
|
||||
random: create_chunk_random(world_seed, chunk_x, chunk_z),
|
||||
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),
|
||||
structure_key: Some(key),
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@ pub mod rule;
|
||||
pub mod structure;
|
||||
mod surface;
|
||||
|
||||
use generator::{GeneratorInit, VanillaGenerator};
|
||||
use generator::VanillaGenerator;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_util::{
|
||||
random::xoroshiro128::{Xoroshiro, XoroshiroSplitter},
|
||||
@ -32,6 +32,42 @@ pub fn get_world_gen(
|
||||
is_flat: bool,
|
||||
flat_layers: Vec<generator::FlatLayer>,
|
||||
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> {
|
||||
if is_flat {
|
||||
Box::new(generator::WorldGenerator::Flat(Box::new(
|
||||
@ -39,7 +75,13 @@ pub fn get_world_gen(
|
||||
)))
|
||||
} else {
|
||||
Box::new(generator::WorldGenerator::Noise(Box::new(
|
||||
VanillaGenerator::new(seed, dimension),
|
||||
VanillaGenerator::new_with_all_settings(
|
||||
seed,
|
||||
dimension,
|
||||
generator_settings,
|
||||
biome_source,
|
||||
structure_overrides,
|
||||
),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::fluid::{Fluid, FluidState};
|
||||
use pumpkin_data::structures::{
|
||||
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::{BlockId, BlockStateId, tag};
|
||||
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::{
|
||||
HeightMap,
|
||||
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||
random::{
|
||||
RandomGenerator, get_decorator_seed, worldgen_random::WorldgenRandom,
|
||||
xoroshiro128::Xoroshiro,
|
||||
},
|
||||
random::{RandomGenerator, get_decorator_seed, worldgen_random::WorldgenRandom},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@ -36,7 +32,7 @@ use super::{
|
||||
section_coords,
|
||||
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::{ChunkData, ChunkHeightmapType, ChunkLight};
|
||||
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::tick::{ScheduledTick, TickPriority};
|
||||
|
||||
enum ActiveSupplier {
|
||||
Overworld(MultiNoiseBiomeSupplier),
|
||||
Nether(MultiNoiseBiomeSupplier),
|
||||
End(TheEndBiomeSupplier),
|
||||
}
|
||||
|
||||
pub trait GenerationCache: HeightLimitView + BlockAccessor {
|
||||
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
|
||||
fn get_center_chunk(&self) -> &ProtoChunk;
|
||||
@ -795,18 +785,7 @@ impl ProtoChunk {
|
||||
generator: &super::generator::VanillaGenerator,
|
||||
multi_noise_sampler: &mut MultiNoiseSampler,
|
||||
) {
|
||||
let dimension = &generator.dimension;
|
||||
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 base_supplier: &dyn BiomeSupplier = &generator.biome_supplier;
|
||||
let blender = Blender::empty();
|
||||
let biome_supplier = blender.get_biome_supplier(base_supplier);
|
||||
let min_y = self.bottom_y();
|
||||
@ -1289,17 +1268,14 @@ impl ProtoChunk {
|
||||
crate::generation::structure::height_sampler::NoiseHeightSampler::new(generator);
|
||||
|
||||
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(
|
||||
&set.placement,
|
||||
calculator,
|
||||
self.x,
|
||||
self.z,
|
||||
global_cache,
|
||||
self,
|
||||
allowed_biomes,
|
||||
) {
|
||||
if !should_generate_structure(&set.placement, calculator, self.x, self.z, global_cache)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1317,9 +1293,8 @@ impl ProtoChunk {
|
||||
}
|
||||
|
||||
let mut candidates = set.structures.to_vec();
|
||||
let carver_seed = get_carver_seed(seed, self.x, self.z);
|
||||
let mut random: RandomGenerator =
|
||||
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
|
||||
let large_feature_seed = get_large_feature_seed(seed, self.x, self.z);
|
||||
let mut random = LegacyRand::from_seed(large_feature_seed);
|
||||
|
||||
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 start_y = height_sampler.estimate_ocean_floor_height(center_x, center_z);
|
||||
if !crate::generation::structure::structures::ocean_monument::has_valid_biomes(
|
||||
&MultiNoiseBiomeSupplier::OVERWORLD,
|
||||
&generator.biome_supplier,
|
||||
&mut sampler,
|
||||
self.x,
|
||||
self.z,
|
||||
@ -1407,7 +1382,6 @@ impl ProtoChunk {
|
||||
debug_assert_eq!(self.stage, StagedChunkEnum::StructureStart);
|
||||
let random_config = &generator.random_config;
|
||||
let settings = generator.settings;
|
||||
let dimension = &generator.dimension;
|
||||
let noise_router = &generator.base_router;
|
||||
let global_cache = &generator.global_structure_cache;
|
||||
let calculator = &generator.structure_calculator;
|
||||
@ -1419,18 +1393,7 @@ impl ProtoChunk {
|
||||
|
||||
let seed = random_config.seed as i64;
|
||||
|
||||
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 base_supplier: &dyn BiomeSupplier = &generator.biome_supplier;
|
||||
let blender = Blender::empty();
|
||||
let biome_supplier = blender.get_biome_supplier(base_supplier);
|
||||
let mut multi_noise_sampler = MultiNoiseSampler::generate(&noise_router.multi_noise);
|
||||
@ -1441,9 +1404,19 @@ impl ProtoChunk {
|
||||
let mut references = Vec::new();
|
||||
// Constant across every chunk in the dimension, so hoist it out of the loop
|
||||
// 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() {
|
||||
if let Some(ref enabled) = generator.enabled_structure_sets
|
||||
&& !enabled.contains(&set_index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut candidate_chunks = Vec::new();
|
||||
|
||||
match &set.placement.placement_type {
|
||||
@ -1465,15 +1438,8 @@ impl ProtoChunk {
|
||||
}
|
||||
}
|
||||
}
|
||||
StructurePlacementType::ConcentricRings(rings) => {
|
||||
let allowed_biomes = Self::get_allowed_biomes(set);
|
||||
let strongholds = global_cache.get_or_calculate_strongholds(
|
||||
seed,
|
||||
rings,
|
||||
self,
|
||||
&allowed_biomes,
|
||||
);
|
||||
for &(cx, cz) in strongholds {
|
||||
StructurePlacementType::ConcentricRings(_) => {
|
||||
for &(cx, cz) in global_cache.get_stronghold_chunks() {
|
||||
if (cx - self.x).abs() <= 8 && (cz - self.z).abs() <= 8 {
|
||||
candidate_chunks.push((cx, cz));
|
||||
}
|
||||
@ -1488,8 +1454,6 @@ impl ProtoChunk {
|
||||
candidate_chunk_x,
|
||||
candidate_chunk_z,
|
||||
global_cache,
|
||||
self,
|
||||
&generator.structure_allowed_biomes[&set_index],
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@ -1520,6 +1484,7 @@ impl ProtoChunk {
|
||||
),
|
||||
sea_level: settings.sea_level,
|
||||
min_y: chunk_min_y,
|
||||
height: chunk_height,
|
||||
height_sampler: Some(&mut height_sampler),
|
||||
structure_key: Some(entry.structure),
|
||||
};
|
||||
|
||||
@ -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 rustc_hash::FxHashMap;
|
||||
|
||||
@ -24,6 +24,7 @@ pub struct NoiseHeightSampler<'a> {
|
||||
preliminary: SurfaceHeightEstimateSampler<'a>,
|
||||
heights: FxHashMap<(i32, i32), i32>,
|
||||
ocean_floor_heights: FxHashMap<(i32, i32), i32>,
|
||||
columns: FxHashMap<(i32, i32), (i32, Vec<&'static BlockState>)>,
|
||||
}
|
||||
|
||||
impl<'a> NoiseHeightSampler<'a> {
|
||||
@ -42,10 +43,15 @@ impl<'a> NoiseHeightSampler<'a> {
|
||||
preliminary,
|
||||
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 shape = &settings.shape;
|
||||
let fluid_sampler = StandardChunkFluidLevelSampler::new(
|
||||
@ -77,7 +83,8 @@ impl<'a> NoiseHeightSampler<'a> {
|
||||
);
|
||||
|
||||
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 index = volume.index_unchecked(0, y, 0);
|
||||
let state = noise
|
||||
@ -89,6 +96,20 @@ impl<'a> NoiseHeightSampler<'a> {
|
||||
&mut self.preliminary,
|
||||
)
|
||||
.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 {
|
||||
blocks_movement(state, BlockId::from_state_id(state.id))
|
||||
} 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);
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +147,8 @@ pub fn try_generate_structure(
|
||||
chunk_z: chunk.z,
|
||||
random,
|
||||
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,
|
||||
structure_key: Some(*key),
|
||||
};
|
||||
|
||||
@ -6,13 +6,18 @@ use pumpkin_data::structures::{
|
||||
use pumpkin_util::{
|
||||
math::floor_div,
|
||||
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::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 pumpkin_data::structures::StructureKeys;
|
||||
|
||||
@ -32,6 +37,13 @@ pub struct GlobalStructureCache {
|
||||
/// surrounding chunk whose structure references overlap it.
|
||||
structure_starts: OnceLock<DashMap<(StructureKeys, i32, i32), Option<StructurePosition>>>,
|
||||
}
|
||||
|
||||
struct RingTask {
|
||||
initial_x: i32,
|
||||
initial_z: i32,
|
||||
search_rng: LegacyRand,
|
||||
}
|
||||
|
||||
impl GlobalStructureCache {
|
||||
/// Creates a new, empty global structure cache.
|
||||
#[must_use]
|
||||
@ -48,6 +60,10 @@ impl GlobalStructureCache {
|
||||
.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,
|
||||
/// computing it via `compute` on the first request and caching the result.
|
||||
///
|
||||
@ -70,36 +86,35 @@ impl GlobalStructureCache {
|
||||
computed
|
||||
}
|
||||
|
||||
/// Retrieves the list of chunk coordinates for Concentric Ring structures.
|
||||
/// If the cache is empty, it calculates the 128 ring positions mathematically.
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn get_or_calculate_strongholds(
|
||||
&self,
|
||||
/// Calculates the 128 ring positions matching vanilla Minecraft's
|
||||
/// `ChunkGeneratorStructureState.generateRingPositions` exactly.
|
||||
#[must_use]
|
||||
pub fn calculate_strongholds(
|
||||
seed: i64,
|
||||
placement: &ConcentricRingsStructurePlacement,
|
||||
biome_supplier: &ProtoChunk,
|
||||
allowed_biomes: &[u16],
|
||||
) -> &[(i32, i32)] {
|
||||
self.stronghold_chunks.get_or_init(|| {
|
||||
let mut chunks = Vec::with_capacity(placement.count as usize);
|
||||
multi_noise: &ProtoMultiNoiseRouter,
|
||||
) -> Vec<(i32, i32)> {
|
||||
let distance_param = f64::from(placement.distance);
|
||||
let mut spread = placement.spread;
|
||||
let count = placement.count;
|
||||
|
||||
let distance_param = f64::from(placement.distance); // Usually 32
|
||||
let mut spread = placement.spread; // Usually 3
|
||||
let count = placement.count; // Usually 128
|
||||
let preferred_biomes = pumpkin_data::tag::get_tag_ids(
|
||||
pumpkin_data::tag::RegistryKey::WorldgenBiome,
|
||||
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.
|
||||
// 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 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 tasks = Vec::with_capacity(count as usize);
|
||||
|
||||
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
|
||||
+ distance_param * f64::from(circle) * 6.0
|
||||
+ (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_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 search_rng = LegacyRand::from_seed(fork_seed as u64);
|
||||
|
||||
let mut biome_search_generator =
|
||||
RandomGenerator::Legacy(LegacyRand::from_seed(fork_seed as u64));
|
||||
tasks.push(RingTask {
|
||||
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);
|
||||
position_in_circle += 1;
|
||||
|
||||
@ -161,7 +143,97 @@ impl GlobalStructureCache {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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]
|
||||
// #[expect(clippy::too_many_arguments)]
|
||||
pub fn should_generate_structure(
|
||||
placement: &StructurePlacement,
|
||||
calculator: &StructurePlacementCalculator,
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
global_cache: &GlobalStructureCache,
|
||||
biome_supplier: &ProtoChunk,
|
||||
allowed_biomes: &[u16],
|
||||
) -> bool {
|
||||
is_start_chunk(
|
||||
&placement.placement_type,
|
||||
@ -190,8 +259,6 @@ pub fn should_generate_structure(
|
||||
chunk_z,
|
||||
placement.salt,
|
||||
global_cache,
|
||||
biome_supplier,
|
||||
allowed_biomes,
|
||||
) && apply_frequency_reduction(
|
||||
placement.frequency_reduction_method,
|
||||
calculator.seed,
|
||||
@ -205,18 +272,9 @@ pub fn should_generate_structure(
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(zone.other_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_z - zone.chunk_count..=chunk_z + zone.chunk_count).any(|z| {
|
||||
should_generate_structure(
|
||||
&set.placement,
|
||||
calculator,
|
||||
x,
|
||||
z,
|
||||
global_cache,
|
||||
biome_supplier,
|
||||
&allowed_biomes,
|
||||
)
|
||||
should_generate_structure(&set.placement, calculator, x, z, global_cache)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -266,14 +324,13 @@ fn should_generate_frequency(
|
||||
random.next_f32() < frequency
|
||||
}
|
||||
FrequencyReductionMethod::LegacyType3 => {
|
||||
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z);
|
||||
let mut random = LegacyRand::from_seed(carver_seed);
|
||||
let feature_seed = get_large_feature_seed(seed as u64, chunk_x, chunk_z);
|
||||
let mut random = LegacyRand::from_seed(feature_seed);
|
||||
random.next_f64() < f64::from(frequency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn is_start_chunk(
|
||||
placement_type: &StructurePlacementType,
|
||||
calculator: &StructurePlacementCalculator,
|
||||
@ -281,20 +338,14 @@ fn is_start_chunk(
|
||||
chunk_z: i32,
|
||||
salt: u32,
|
||||
global_cache: &GlobalStructureCache,
|
||||
biome_supplier: &ProtoChunk,
|
||||
allowed_biomes: &[u16],
|
||||
) -> bool {
|
||||
match placement_type {
|
||||
StructurePlacementType::RandomSpread(placement) => {
|
||||
is_start_chunk_random_spread(placement, calculator, chunk_x, chunk_z, salt)
|
||||
}
|
||||
StructurePlacementType::ConcentricRings(placement) => {
|
||||
let strongholds = global_cache.get_or_calculate_strongholds(
|
||||
calculator.seed,
|
||||
placement,
|
||||
biome_supplier,
|
||||
allowed_biomes,
|
||||
);
|
||||
let strongholds =
|
||||
global_cache.get_or_calculate_strongholds(calculator.seed, placement, None);
|
||||
strongholds.contains(&(chunk_x, chunk_z))
|
||||
}
|
||||
}
|
||||
@ -351,24 +402,16 @@ fn is_start_chunk_random_spread(
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pumpkin_data::{
|
||||
dimension::Dimension,
|
||||
structures::{RandomSpreadStructurePlacement, StructurePlacementCalculator, StructureSet},
|
||||
use pumpkin_data::structures::{
|
||||
RandomSpreadStructurePlacement, StructurePlacementCalculator, StructureSet,
|
||||
};
|
||||
use pumpkin_util::random::{
|
||||
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
|
||||
};
|
||||
use pumpkin_util::world_seed::Seed;
|
||||
|
||||
use crate::{
|
||||
ProtoChunk,
|
||||
generation::{
|
||||
get_world_gen,
|
||||
structure::placement::{
|
||||
use crate::generation::structure::placement::{
|
||||
GlobalStructureCache, apply_frequency_reduction, get_start_chunk_random_spread,
|
||||
is_start_chunk, should_generate_structure,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -395,14 +438,6 @@ mod tests {
|
||||
let seed = 0;
|
||||
let calculator = StructurePlacementCalculator::new(seed);
|
||||
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 villages = &StructureSet::VILLAGES;
|
||||
let excluded = (-1002, -595);
|
||||
@ -413,8 +448,6 @@ mod tests {
|
||||
excluded.1,
|
||||
outposts.placement.salt,
|
||||
&cache,
|
||||
&chunk,
|
||||
&[],
|
||||
));
|
||||
assert!(apply_frequency_reduction(
|
||||
outposts.placement.frequency_reduction_method,
|
||||
@ -433,8 +466,6 @@ mod tests {
|
||||
excluded.1 + dz,
|
||||
villages.placement.salt,
|
||||
&cache,
|
||||
&chunk,
|
||||
&[],
|
||||
)
|
||||
})
|
||||
}));
|
||||
@ -445,8 +476,6 @@ mod tests {
|
||||
excluded.0,
|
||||
excluded.1,
|
||||
&cache,
|
||||
&chunk,
|
||||
&[],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,6 +341,7 @@ mod tests {
|
||||
random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)),
|
||||
sea_level: 63,
|
||||
min_y: 0,
|
||||
height: 128,
|
||||
height_sampler: Some(&mut sampler),
|
||||
structure_key: None,
|
||||
})
|
||||
@ -374,6 +375,7 @@ mod tests {
|
||||
random: RandomGenerator::Legacy(LegacyRand::from_seed(0)),
|
||||
sea_level: 63,
|
||||
min_y: 0,
|
||||
height: 128,
|
||||
height_sampler: Some(&mut sampler),
|
||||
structure_key: None,
|
||||
})
|
||||
|
||||
@ -823,10 +823,9 @@ impl StructureGenerator for JigsawGenerator {
|
||||
.structure_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 {
|
||||
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 {
|
||||
context.sea_level
|
||||
@ -1012,6 +1011,7 @@ mod tests {
|
||||
random: super::super::create_chunk_random(0, 0, 0),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
height_sampler: None,
|
||||
structure_key: Some(pumpkin_data::structures::StructureKeys::AncientCity),
|
||||
};
|
||||
@ -1123,6 +1123,7 @@ mod tests {
|
||||
random: super::super::create_chunk_random(SEED, 75, -82),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
height_sampler: Some(&mut height_sampler),
|
||||
structure_key: Some(pumpkin_data::structures::StructureKeys::PillagerOutpost),
|
||||
};
|
||||
|
||||
@ -619,7 +619,7 @@ impl JigsawPlacement {
|
||||
box_.move_pos(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_)
|
||||
{
|
||||
tracing::debug!(
|
||||
|
||||
@ -635,6 +635,7 @@ mod tests {
|
||||
random: RandomGenerator::Legacy(LegacyRand::from_seed(seed)),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
height_sampler: Some(&mut sampler),
|
||||
structure_key: None,
|
||||
})
|
||||
|
||||
@ -835,6 +835,15 @@ pub trait HeightSampler {
|
||||
fn estimate_ocean_floor_height(&mut self, block_x: i32, block_z: i32) -> i32 {
|
||||
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
|
||||
@ -852,6 +861,7 @@ pub struct StructureGeneratorContext<'a> {
|
||||
pub random: RandomGenerator,
|
||||
pub sea_level: i32,
|
||||
pub min_y: i32,
|
||||
pub height: u16,
|
||||
pub height_sampler: Option<&'a mut dyn HeightSampler>,
|
||||
pub structure_key: Option<pumpkin_data::structures::StructureKeys>,
|
||||
}
|
||||
|
||||
@ -45,9 +45,10 @@ pub const FOSSILS: [&str; 14] = [
|
||||
|
||||
/// Vanilla height provider bounds for nether fossils.
|
||||
/// 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_MAX: i32 = 253;
|
||||
const HEIGHT_MAX: i32 = 125;
|
||||
|
||||
pub struct NetherFossilGenerator;
|
||||
|
||||
@ -56,11 +57,12 @@ impl StructureGenerator for NetherFossilGenerator {
|
||||
&self,
|
||||
mut context: StructureGeneratorContext<'_>,
|
||||
) -> Option<StructurePosition> {
|
||||
// Vanilla random call order:
|
||||
// Vanilla random call order (NetherFossilStructure.java):
|
||||
// 1. nextInt(16) for X offset within chunk
|
||||
// 2. nextInt(16) for Z offset within chunk
|
||||
// 3. height.get(random) for initial Y (uniform 32..254)
|
||||
// 4. Column scan (no random calls)
|
||||
// 3. height.sample(random, generationContext) for initial Y (uniform 32..125)
|
||||
// 4. Column scan downward to seaLevel (no random calls)
|
||||
// If y <= seaLevel: return empty
|
||||
// 5. Rotation.getRandom(random) - nextInt(4)
|
||||
// 6. Util.getRandom(FOSSILS, random) - nextInt(14)
|
||||
|
||||
@ -72,12 +74,37 @@ impl StructureGenerator for NetherFossilGenerator {
|
||||
.map(|key| pumpkin_data::structures::Structure::get(&key));
|
||||
|
||||
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 {
|
||||
let height_range = HEIGHT_MAX - HEIGHT_MIN + 1;
|
||||
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 = Rotation::from_index(rotation_index);
|
||||
|
||||
@ -85,23 +112,16 @@ impl StructureGenerator for NetherFossilGenerator {
|
||||
let template_name = FOSSILS[template_index];
|
||||
|
||||
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 piece = NetherFossilPiece::new(
|
||||
template,
|
||||
template_name.to_string(),
|
||||
position,
|
||||
rotation,
|
||||
initial_y,
|
||||
context.sea_level,
|
||||
);
|
||||
let piece = NetherFossilPiece::new(template, template_name.to_string(), position, rotation);
|
||||
|
||||
collector.add_piece(Box::new(piece));
|
||||
|
||||
Some(StructurePosition {
|
||||
start_pos: BlockPos::new(x, initial_y, z),
|
||||
start_pos: BlockPos::new(x, y, z),
|
||||
collector: Arc::new(collector.into()),
|
||||
})
|
||||
}
|
||||
@ -113,8 +133,6 @@ pub struct NetherFossilPiece {
|
||||
pub template_name: String,
|
||||
pub place_settings: StructurePlaceSettings,
|
||||
pub template_position: Vector3<i32>,
|
||||
pub initial_y: i32,
|
||||
pub sea_level: i32,
|
||||
}
|
||||
|
||||
impl NetherFossilPiece {
|
||||
@ -124,8 +142,6 @@ impl NetherFossilPiece {
|
||||
template_name: String,
|
||||
template_position: Vector3<i32>,
|
||||
rotation: Rotation,
|
||||
initial_y: i32,
|
||||
sea_level: i32,
|
||||
) -> Self {
|
||||
let place_settings = make_settings(rotation);
|
||||
let bounding_box = template.get_bounding_box(&place_settings, template_position);
|
||||
@ -136,36 +152,9 @@ impl NetherFossilPiece {
|
||||
template_name,
|
||||
place_settings,
|
||||
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) {
|
||||
let rotation = self.place_settings.get_rotation();
|
||||
let mirror = self.place_settings.get_mirror();
|
||||
@ -278,15 +267,6 @@ impl StructurePieceBase for NetherFossilPiece {
|
||||
seed: i64,
|
||||
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 mut enlarged_box = *chunk_box;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,6 +545,7 @@ mod tests {
|
||||
random: RandomGenerator::Legacy(LegacyRand::from_seed(0)),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
height: 384,
|
||||
height_sampler: Some(&mut height_sampler),
|
||||
structure_key: None,
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ use crate::{
|
||||
},
|
||||
palette::has_random_ticking_fluid,
|
||||
},
|
||||
generation::get_world_gen,
|
||||
generation::get_world_gen_with_all_settings,
|
||||
tick::{OrderedTick, ScheduledTick, TickPriority},
|
||||
world::WorldPortalExt,
|
||||
};
|
||||
@ -202,42 +202,48 @@ impl Level {
|
||||
let mut is_flat = false;
|
||||
let mut flat_layers = Vec::new();
|
||||
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)
|
||||
&& 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;
|
||||
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
|
||||
{
|
||||
if let Some(b) = val.get("biome").and_then(|v| v.as_str()) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
generator_settings_name = Some(s.clone());
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
dimension,
|
||||
is_flat,
|
||||
flat_layers,
|
||||
flat_biome,
|
||||
generator_settings_name.as_deref(),
|
||||
biome_source.as_ref(),
|
||||
structure_overrides.as_deref(),
|
||||
));
|
||||
|
||||
let chunk_saver = match &level_config.chunk {
|
||||
|
||||
@ -277,17 +277,19 @@ pub fn read_world_gen_settings(level_folder: &Path) -> Option<WorldGenSettings>
|
||||
.get_string("type")
|
||||
.unwrap_or("minecraft:multi_noise")
|
||||
.to_string();
|
||||
match bs_c.get_string("preset") {
|
||||
Some(preset) => {
|
||||
if let Some(preset) = bs_c.get_string("preset") {
|
||||
crate::world_info::BiomeSource::WithPreset {
|
||||
preset: preset.to_string(),
|
||||
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 }
|
||||
}
|
||||
}
|
||||
});
|
||||
dimensions.insert(
|
||||
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("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 } => {
|
||||
bs_comp.put_string("type", biome_type.clone());
|
||||
}
|
||||
|
||||
@ -195,6 +195,18 @@ pub enum GeneratorSettings {
|
||||
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)]
|
||||
#[serde(untagged)]
|
||||
pub enum BiomeSource {
|
||||
@ -203,12 +215,177 @@ pub enum BiomeSource {
|
||||
#[serde(rename = "type")]
|
||||
biome_type: String,
|
||||
},
|
||||
Fixed {
|
||||
biome: String,
|
||||
#[serde(rename = "type")]
|
||||
biome_type: String,
|
||||
},
|
||||
Simple {
|
||||
#[serde(rename = "type")]
|
||||
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)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct DataPacks {
|
||||
@ -223,7 +400,7 @@ pub struct DataPacks {
|
||||
impl WorldGenSettings {
|
||||
#[must_use]
|
||||
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();
|
||||
dimensions.insert(
|
||||
"minecraft:overworld".to_string(),
|
||||
@ -245,7 +422,9 @@ impl WorldGenSettings {
|
||||
"minecraft:the_nether".to_string(),
|
||||
Dimension {
|
||||
generator: Generator {
|
||||
settings: Some(GeneratorSettings::Reference("minecraft:nether".to_string())),
|
||||
settings: Some(GeneratorSettings::Reference(
|
||||
"minecraft:nether".to_string(),
|
||||
)),
|
||||
biome_source: Some(BiomeSource::WithPreset {
|
||||
preset: "minecraft:nether".to_string(),
|
||||
biome_type: "minecraft:multi_noise".to_string(),
|
||||
@ -273,6 +452,20 @@ impl WorldGenSettings {
|
||||
dimensions,
|
||||
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]
|
||||
pub fn from_world_generator(
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ impl MushroomPlantBlock {
|
||||
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))
|
||||
}
|
||||
@ -71,33 +71,40 @@ impl MushroomPlantBlock {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let foliage_radius = if block == &Block::BROWN_MUSHROOM {
|
||||
3
|
||||
let below_block = world.get_block(&pos.down());
|
||||
let can_place_on = if block == &Block::BROWN_MUSHROOM {
|
||||
below_block.has_tag(&tag::Block::MINECRAFT_HUGE_BROWN_MUSHROOM_CAN_PLACE_ON)
|
||||
} 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 dz in -radius..=radius {
|
||||
let check_pos = pos.add(dx, dy, dz);
|
||||
if check_pos == *pos {
|
||||
continue;
|
||||
}
|
||||
if !world.is_loaded(&check_pos) {
|
||||
return false;
|
||||
}
|
||||
let check_state = world.get_block_state(&check_pos);
|
||||
let check_block = world.get_block(&check_pos);
|
||||
let can_replace = check_state.is_air()
|
||||
|| check_block.has_tag(&tag::Block::MINECRAFT_LEAVES)
|
||||
|| check_block.has_tag(&tag::Block::MINECRAFT_REPLACEABLE_BY_MUSHROOMS)
|
||||
|| check_state.replaceable();
|
||||
if !can_replace {
|
||||
if !check_state.is_air() && !check_block.has_tag(&tag::Block::MINECRAFT_LEAVES)
|
||||
{
|
||||
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) {
|
||||
let radius = 3;
|
||||
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 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);
|
||||
for i in 0..tree_height {
|
||||
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 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);
|
||||
for i in 0..tree_height {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +99,8 @@ impl JigsawBlockEntity {
|
||||
chunk_z: position.chunk_position().y,
|
||||
random: RandomGenerator::Xoroshiro(Xoroshiro::from_seed(rand::rng().next_u64())),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
min_y: world.dimension.min_y,
|
||||
height: world.dimension.height as u16,
|
||||
height_sampler: None,
|
||||
structure_key: None,
|
||||
};
|
||||
|
||||
@ -200,7 +200,11 @@ impl CommandExecutor for PlaceJigsawExecutor {
|
||||
chunk_z: 0,
|
||||
random,
|
||||
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,
|
||||
structure_key: None,
|
||||
};
|
||||
@ -307,7 +311,11 @@ impl CommandExecutor for PlaceStructureExecutor {
|
||||
chunk_z: block_pos.0.z >> 4,
|
||||
random,
|
||||
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,
|
||||
structure_key: Some(key),
|
||||
},
|
||||
@ -355,7 +363,11 @@ impl CommandExecutor for PlaceStructureExecutor {
|
||||
chunk_z: block_pos.0.z >> 4,
|
||||
random,
|
||||
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,
|
||||
structure_key: Some(key),
|
||||
},
|
||||
|
||||
@ -5661,12 +5661,16 @@ impl World {
|
||||
self.environment_attributes().get_value_activity(baby, pos)
|
||||
}
|
||||
|
||||
pub fn get_max_local_raw_brightness(&self, pos: &BlockPos) -> u8 {
|
||||
let sky_light = (self.get_sky_light_level(pos) as i32 - self.get_sky_darken()).max(0) as u8;
|
||||
pub fn get_raw_brightness(&self, pos: &BlockPos, sky_darken: u8) -> 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);
|
||||
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> {
|
||||
self.level
|
||||
.light_engine
|
||||
@ -7402,7 +7406,7 @@ impl WorldPortalExt for WorldPortal {
|
||||
) -> bool {
|
||||
self.0.block_registry.can_place_at(
|
||||
None,
|
||||
None,
|
||||
Some(&self.0),
|
||||
block_accessor,
|
||||
None,
|
||||
block,
|
||||
|
||||
@ -12,6 +12,24 @@ struct GeyserBiomeMapping {
|
||||
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`.
|
||||
#[derive(Deserialize)]
|
||||
pub struct Biome {
|
||||
@ -23,7 +41,8 @@ pub struct Biome {
|
||||
downfall: f32,
|
||||
/// Optional modifier that changes how temperature is applied.
|
||||
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.
|
||||
features: Vec<Vec<String>>,
|
||||
/// 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 temperature = biome.temperature;
|
||||
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
|
||||
.features
|
||||
.iter()
|
||||
@ -371,6 +398,7 @@ pub fn build() -> TokenStream {
|
||||
#downfall
|
||||
),
|
||||
features: &[#(#features),*],
|
||||
carvers: &[#(#carvers),*],
|
||||
creature_spawn_probability: #creature_spawn_probability,
|
||||
spawners: #spawners,
|
||||
spawn_costs: phf::phf_map! {
|
||||
@ -401,7 +429,7 @@ pub fn build() -> TokenStream {
|
||||
pub registry_id: &'static str,
|
||||
pub be_network_id: u8,
|
||||
pub weather: Weather,
|
||||
// carvers: &'static [&str],
|
||||
pub carvers: &'static [&'static crate::carver::CarverConfig],
|
||||
pub features: &'static [&'static [crate::placed_feature::PlacedFeature]],
|
||||
pub creature_spawn_probability: f32,
|
||||
pub spawners: SpawnGroups,
|
||||
|
||||
@ -117,29 +117,34 @@ pub fn build() -> TokenStream {
|
||||
};
|
||||
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HeightProvider {
|
||||
Uniform(UniformHeightProvider),
|
||||
Trapezoid(TrapezoidHeightProvider),
|
||||
VeryBiasedToBottom(VeryBiasedToBottomHeightProvider),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UniformHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrapezoidHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
pub plateau: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VeryBiasedToBottomHeightProvider {
|
||||
pub min_inclusive: YOffset,
|
||||
pub max_inclusive: YOffset,
|
||||
pub inner: Option<std::num::NonZero<u32>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CaveCarverConfig {
|
||||
pub count: IntProvider,
|
||||
pub horizontal_radius_multiplier: FloatProvider,
|
||||
@ -167,6 +172,7 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CanyonShapeConfig {
|
||||
pub distance_factor: FloatProvider,
|
||||
pub thickness: FloatProvider,
|
||||
@ -177,16 +183,19 @@ pub fn build() -> TokenStream {
|
||||
pub y_scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CanyonCarverConfig {
|
||||
pub vertical_rotation: FloatProvider,
|
||||
pub shape: CanyonShapeConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CarverAdditionalConfig {
|
||||
Cave(CaveCarverConfig),
|
||||
Canyon(CanyonCarverConfig),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CarverConfig {
|
||||
pub probability: f32,
|
||||
pub y: HeightProvider,
|
||||
|
||||
@ -598,6 +598,21 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
|
||||
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]
|
||||
pub fn from_dimension(dimension: &Dimension) -> &'static Self {
|
||||
if dimension == &Dimension::OVERWORLD {
|
||||
|
||||
@ -2975,12 +2975,16 @@ fn load_vanilla_noise_routers() -> NoiseRouterReprs {
|
||||
pub fn build() -> TokenStream {
|
||||
let mut reprs: NoiseRouterReprs = load_vanilla_noise_routers();
|
||||
|
||||
let _ = reprs.overworld_amplified;
|
||||
let _ = reprs.overworld_large_biomes;
|
||||
let _ = reprs.end_islands;
|
||||
|
||||
let (overworld_router, overworld_compiled) =
|
||||
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 (end_router, end_compiled) = reprs.end.into_token_stream_compiled("end");
|
||||
|
||||
@ -3004,6 +3008,8 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
|
||||
#overworld_compiled
|
||||
#amplified_compiled
|
||||
#large_biomes_compiled
|
||||
#nether_compiled
|
||||
#end_compiled
|
||||
|
||||
@ -3366,7 +3372,35 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
|
||||
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 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(&LIFIED_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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,6 +327,7 @@ pub fn build() -> TokenStream {
|
||||
use crate::BlockState;
|
||||
use crate::biome::ParameterPoint;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct NoiseSettings {
|
||||
pub aquifers_enabled: bool,
|
||||
pub ore_veins_enabled: bool,
|
||||
@ -340,6 +341,7 @@ pub fn build() -> TokenStream {
|
||||
|
||||
pub type GenerationSettings = NoiseSettings;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct GenerationShapeConfig {
|
||||
pub min_y: i8,
|
||||
pub height: u16,
|
||||
@ -392,6 +394,21 @@ pub fn build() -> TokenStream {
|
||||
impl NoiseSettings {
|
||||
#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]
|
||||
pub fn from_dimension(dimension: &Dimension) -> &'static Self {
|
||||
if dimension == &Dimension::OVERWORLD {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user