chore: move structure settings from runtime to compile time parsing

This commit is contained in:
Alexander Medvedev
2026-02-04 17:13:50 +01:00
parent 8e06e5e83a
commit 2a0d2f1cf3
10 changed files with 697 additions and 579 deletions

32
Cargo.lock generated
View File

@@ -312,13 +312,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chacha20"
version = "0.10.0-rc.9"
version = "0.10.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c81d916c6ae06736ec667b51f95ee5ff660a75f4ea6ce1bd932c942365c0ea43"
checksum = "c536927023d1c432e6e23a25ef45f6756094eac2ab460db5fb17a772acdfd312"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
]
[[package]]
@@ -630,9 +630,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossfire"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9c756fd351d7bc1f95698a33a90ce39c8f7458335cc7e95132d3d36124902a"
checksum = "86db11309bffee9435040d648c13c3efbdbcdcd3ead9872e65ce9dff4c3f79ce"
dependencies = [
"crossbeam-utils",
"futures-core",
@@ -666,7 +666,7 @@ checksum = "053c3561863ce55e3226ecc48b08679f4b66cb1b92b9afb42c2c402dfe8b9b51"
dependencies = [
"ctutils",
"num-traits",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
"serdect",
"zeroize",
]
@@ -698,7 +698,7 @@ checksum = "334a79c97c0b7fa536716dc132fd417d0afbf471440a41fc25a5d9f66d8771cd"
dependencies = [
"crypto-bigint 0.7.0-rc.22",
"libm",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
]
[[package]]
@@ -938,9 +938,9 @@ checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
name = "flate2"
version = "1.1.8"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1099,7 +1099,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
"wasip2",
"wasip3",
]
@@ -2355,11 +2355,11 @@ dependencies = [
[[package]]
name = "rand"
version = "0.10.0-rc.8"
source = "git+https://github.com/rust-random/rand#a4c9a8e565fa310de423e3ba89593b647b721e8c"
source = "git+https://github.com/rust-random/rand#9c98f59e8b042e5c7c714e933e49b384a4ce75a6"
dependencies = [
"chacha20",
"getrandom 0.4.0-rc.1",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
]
[[package]]
@@ -2373,9 +2373,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.10.0-rc-6"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70765ff7112b0fb2d272d24d9a2f907fc206211304328fe58b2db15a5649ef28"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rayon"
@@ -2471,7 +2471,7 @@ dependencies = [
"digest 0.11.0-rc.9",
"pkcs1",
"pkcs8 0.11.0-rc.10",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
"sha1",
"signature 3.0.0-rc.9",
"spki 0.8.0-rc.4",
@@ -2752,7 +2752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad0ce3b3f8efd7406f22e2ca5d02be21cdf3b3d1d53ab141f784de8965c7c7e"
dependencies = [
"digest 0.11.0-rc.9",
"rand_core 0.10.0-rc-6",
"rand_core 0.10.0",
]
[[package]]

View File

@@ -118,12 +118,12 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = "1.10.0"
criterion = { version = "0.8", default-features = false }
crossbeam-utils = "0.8.21"
crossfire = { version= "3.0.3", features = ["compat"]}
crossfire = { version= "3.0.4", features = ["compat"]}
dashmap = "6.1"
ecdsa = "0.16.9"
enum_dispatch = "0.3.13"
env_logger = "0.11.8"
flate2 = "1.1.8"
flate2 = "1.1.9"
heck = "0.5"
hmac = "=0.13.0-rc.4"
indexmap = "2.13"

View File

@@ -48,6 +48,7 @@ mod screen;
mod sound;
mod sound_category;
mod spawn_egg;
mod structures;
mod tag;
mod tracked_data;
mod world_event;
@@ -84,6 +85,7 @@ pub fn main() {
(spawn_egg::build, "spawn_egg.rs"),
(block::build, "block.rs"),
(item::build, "item.rs"),
(structures::build, "structures.rs"),
(chunk_gen_settings::build, "chunk_gen_settings.rs"),
(fluid::build, "fluid.rs"),
(entity_status::build, "entity_status.rs"),

View File

@@ -0,0 +1,466 @@
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use serde::Deserialize;
use std::{collections::HashMap, fs};
#[derive(Deserialize)]
pub struct StructureSetStruct {
pub placement: StructurePlacementStruct,
pub structures: Vec<WeightedEntryStruct>,
}
#[derive(Deserialize, Clone)]
pub struct WeightedEntryStruct {
pub structure: String,
pub weight: u32,
}
#[derive(Deserialize)]
pub struct StructurePlacementStruct {
frequency_reduction_method: Option<String>,
frequency: Option<f32>,
salt: u32,
#[serde(flatten)]
r#type: StructurePlacementTypeStruct,
}
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum StructurePlacementTypeStruct {
#[serde(rename = "minecraft:random_spread")]
RandomSpread {
spacing: i32,
separation: i32,
spread_type: Option<String>,
},
#[serde(rename = "minecraft:concentric_rings")]
ConcentricRings {
spread: i32,
distance: i32,
count: i32,
preferred_biomes: String,
},
}
#[derive(Deserialize, Clone)]
pub struct StructureStruct {
pub biomes: String,
pub step: String,
}
impl ToTokens for StructureSetStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let placement = &self.placement;
let structures = &self.structures;
tokens.extend(quote!(
StructureSet {
placement: #placement,
structures: &[#(#structures),*],
}
));
}
}
impl ToTokens for WeightedEntryStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let structure = structure_key_to_token(&self.structure);
let weight = self.weight;
tokens.extend(quote!(
WeightedEntry {
structure: #structure,
weight: #weight,
}
));
}
}
impl ToTokens for StructurePlacementStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let frequency_reduction = match &self.frequency_reduction_method {
Some(method) => {
let method_token = frequency_reduction_to_token(method);
quote!(Some(#method_token))
}
None => quote!(None),
};
let frequency = match self.frequency {
Some(f) => quote!(Some(#f)),
None => quote!(None),
};
let salt = self.salt;
let placement_type = &self.r#type;
tokens.extend(quote!(
StructurePlacement {
frequency_reduction_method: #frequency_reduction,
frequency: #frequency,
salt: #salt,
placement_type: #placement_type,
}
));
}
}
impl ToTokens for StructurePlacementTypeStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::RandomSpread {
spacing,
separation,
spread_type,
} => {
let spread_type_token = match spread_type {
Some(st) => {
let st_token = spread_type_to_token(st);
quote!(Some(#st_token))
}
None => quote!(None),
};
tokens.extend(quote!(
StructurePlacementType::RandomSpread(RandomSpreadStructurePlacement {
spacing: #spacing,
separation: #separation,
spread_type: #spread_type_token,
})
));
}
Self::ConcentricRings {
spread,
distance,
count,
preferred_biomes,
} => {
tokens.extend(quote!(
StructurePlacementType::ConcentricRings(ConcentricRingsStructurePlacement {
spread: #spread,
distance: #distance,
count: #count,
preferred_biomes: #preferred_biomes,
})
));
}
}
}
}
impl ToTokens for StructureStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let biomes = &self.biomes;
let step = generation_step_to_token(&self.step);
tokens.extend(quote!(
Structure {
biomes: #biomes,
step: #step,
}
));
}
}
// Helper functions
fn structure_key_to_token(key: &str) -> TokenStream {
let stripped = key.strip_prefix("minecraft:").unwrap_or(key);
match stripped {
"pillager_outpost" => quote!(StructureKeys::PillagerOutpost),
"mineshaft" => quote!(StructureKeys::Mineshaft),
"mineshaft_mesa" => quote!(StructureKeys::MineshaftMesa),
"mansion" => quote!(StructureKeys::Mansion),
"jungle_pyramid" => quote!(StructureKeys::JunglePyramid),
"desert_pyramid" => quote!(StructureKeys::DesertPyramid),
"igloo" => quote!(StructureKeys::Igloo),
"shipwreck" => quote!(StructureKeys::Shipwreck),
"shipwreck_beached" => quote!(StructureKeys::ShipwreckBeached),
"swamp_hut" => quote!(StructureKeys::SwampHut),
"stronghold" => quote!(StructureKeys::Stronghold),
"monument" => quote!(StructureKeys::Monument),
"ocean_ruin_cold" => quote!(StructureKeys::OceanRuinCold),
"ocean_ruin_warm" => quote!(StructureKeys::OceanRuinWarm),
"fortress" => quote!(StructureKeys::Fortress),
"nether_fossil" => quote!(StructureKeys::NetherFossil),
"end_city" => quote!(StructureKeys::EndCity),
"buried_treasure" => quote!(StructureKeys::BuriedTreasure),
"bastion_remnant" => quote!(StructureKeys::BastionRemnant),
"village_plains" => quote!(StructureKeys::VillagePlains),
"village_desert" => quote!(StructureKeys::VillageDesert),
"village_savanna" => quote!(StructureKeys::VillageSavanna),
"village_snowy" => quote!(StructureKeys::VillageSnowy),
"village_taiga" => quote!(StructureKeys::VillageTaiga),
"ruined_portal" => quote!(StructureKeys::RuinedPortal),
"ruined_portal_desert" => quote!(StructureKeys::RuinedPortalDesert),
"ruined_portal_jungle" => quote!(StructureKeys::RuinedPortalJungle),
"ruined_portal_swamp" => quote!(StructureKeys::RuinedPortalSwamp),
"ruined_portal_mountain" => quote!(StructureKeys::RuinedPortalMountain),
"ruined_portal_ocean" => quote!(StructureKeys::RuinedPortalOcean),
"ruined_portal_nether" => quote!(StructureKeys::RuinedPortalNether),
"ancient_city" => quote!(StructureKeys::AncientCity),
"trail_ruins" => quote!(StructureKeys::TrailRuins),
"trial_chambers" => quote!(StructureKeys::TrialChambers),
_ => panic!("Unknown structure key: {}", stripped),
}
}
fn frequency_reduction_to_token(method: &str) -> TokenStream {
match method {
"default" => quote!(FrequencyReductionMethod::Default),
"legacy_type_1" => quote!(FrequencyReductionMethod::LegacyType1),
"legacy_type_2" => quote!(FrequencyReductionMethod::LegacyType2),
"legacy_type_3" => quote!(FrequencyReductionMethod::LegacyType3),
_ => quote!(FrequencyReductionMethod::Default),
}
}
fn spread_type_to_token(spread: &str) -> TokenStream {
match spread {
"linear" => quote!(SpreadType::Linear),
"triangular" => quote!(SpreadType::Triangular),
_ => quote!(SpreadType::Linear),
}
}
fn generation_step_to_token(step: &str) -> TokenStream {
match step {
"raw_generation" => quote!(GenerationStep::RawGeneration),
"lakes" => quote!(GenerationStep::Lakes),
"local_modifications" => quote!(GenerationStep::LocalModifications),
"underground_structures" => quote!(GenerationStep::UndergroundStructures),
"surface_structures" => quote!(GenerationStep::SurfaceStructures),
"strongholds" => quote!(GenerationStep::Strongholds),
"underground_ores" => quote!(GenerationStep::UndergroundOres),
"underground_decoration" => quote!(GenerationStep::UndergroundDecoration),
"fluid_springs" => quote!(GenerationStep::FluidSprings),
"vegetal_decoration" => quote!(GenerationStep::VegetalDecoration),
"top_layer_modification" => quote!(GenerationStep::TopLayerModification),
_ => panic!("Unknown generation step: {}", step),
}
}
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/structures.json");
println!("cargo:rerun-if-changed=../assets/structure_set.json");
let structures_json: HashMap<String, StructureStruct> =
serde_json::from_str(&fs::read_to_string("../assets/structures.json").unwrap())
.expect("Failed to parse structures.json");
let structure_sets_json: HashMap<String, StructureSetStruct> =
serde_json::from_str(&fs::read_to_string("../assets/structure_set.json").unwrap())
.expect("Failed to parse structure_set.json");
let mut structure_const_defs = TokenStream::new();
let mut structure_lookup_arms = TokenStream::new();
for (name, structure) in &structures_json {
let stripped_name = name.strip_prefix("minecraft:").unwrap_or(name);
let upper_name = stripped_name.to_uppercase();
let const_name = format_ident!("{}", upper_name);
let key_variant = structure_key_to_token(name);
structure_const_defs.extend(quote!(
pub const #const_name: Structure = #structure;
));
structure_lookup_arms.extend(quote!(
#key_variant => &Self::#const_name,
));
}
let mut structure_set_const_defs = TokenStream::new();
let mut structure_set_lookup_arms = TokenStream::new();
let mut all_structure_set_idents = Vec::new();
for (name, structure_set) in &structure_sets_json {
let stripped_name = name.strip_prefix("minecraft:").unwrap_or(name);
let upper_name = stripped_name.to_uppercase().replace('/', "_");
let const_name = format_ident!("{}", upper_name);
structure_set_const_defs.extend(quote!(
pub const #const_name: StructureSet = #structure_set;
));
structure_set_lookup_arms.extend(quote!(
#stripped_name => Some(&Self::#const_name),
));
all_structure_set_idents.push(const_name);
}
quote!(
use pumpkin_util::math::floor_div;
use pumpkin_util::random::{
RandomGenerator, RandomImpl, get_carver_seed, get_region_seed,
legacy_rand::LegacyRand, xoroshiro128::Xoroshiro,
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum StructureKeys {
PillagerOutpost,
Mineshaft,
MineshaftMesa,
Mansion,
JunglePyramid,
DesertPyramid,
Igloo,
Shipwreck,
ShipwreckBeached,
SwampHut,
Stronghold,
Monument,
OceanRuinCold,
OceanRuinWarm,
Fortress,
NetherFossil,
EndCity,
BuriedTreasure,
BastionRemnant,
VillagePlains,
VillageDesert,
VillageSavanna,
VillageSnowy,
VillageTaiga,
RuinedPortal,
RuinedPortalDesert,
RuinedPortalJungle,
RuinedPortalSwamp,
RuinedPortalMountain,
RuinedPortalOcean,
RuinedPortalNether,
AncientCity,
TrailRuins,
TrialChambers,
}
pub struct StructureSet {
pub placement: StructurePlacement,
pub structures: &'static [WeightedEntry],
}
#[derive(Clone)]
pub struct WeightedEntry {
pub structure: StructureKeys,
pub weight: u32,
}
pub struct StructurePlacement {
pub frequency_reduction_method: Option<FrequencyReductionMethod>,
pub frequency: Option<f32>,
pub salt: u32,
pub placement_type: StructurePlacementType,
}
#[derive(Clone, Copy)]
pub enum FrequencyReductionMethod {
Default,
LegacyType1,
LegacyType2,
LegacyType3,
}
pub enum StructurePlacementType {
RandomSpread(RandomSpreadStructurePlacement),
ConcentricRings(ConcentricRingsStructurePlacement),
}
pub struct RandomSpreadStructurePlacement {
pub spacing: i32,
pub separation: i32,
pub spread_type: Option<SpreadType>,
}
pub struct ConcentricRingsStructurePlacement {
pub spread: i32,
pub distance: i32,
pub count: i32,
pub preferred_biomes: &'static str,
}
#[derive(Clone, Copy)]
pub enum SpreadType {
Linear,
Triangular,
}
impl SpreadType {
pub fn get(&self, random: &mut RandomGenerator, bound: i32) -> i32 {
match self {
Self::Linear => random.next_bounded_i32(bound),
Self::Triangular => i32::midpoint(
random.next_bounded_i32(bound),
random.next_bounded_i32(bound),
),
}
}
}
pub struct Structure {
pub biomes: &'static str,
pub step: GenerationStep,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GenerationStep {
RawGeneration,
Lakes,
LocalModifications,
UndergroundStructures,
SurfaceStructures,
Strongholds,
UndergroundOres,
UndergroundDecoration,
FluidSprings,
VegetalDecoration,
TopLayerModification,
}
impl GenerationStep {
#[must_use]
pub const fn ordinal(&self) -> usize {
*self as usize
}
}
pub struct StructurePlacementCalculator {
pub seed: i64,
}
impl StructurePlacementCalculator {
#[must_use]
pub const fn new(seed: i64) -> Self {
Self { seed }
}
}
impl Structure {
#structure_const_defs
#[must_use]
pub const fn get(key: &StructureKeys) -> &'static Self {
match *key {
#structure_lookup_arms
}
}
}
impl StructureSet {
#structure_set_const_defs
pub const ALL: &'static [StructureSet] = &[
#(Self::#all_structure_set_idents),*
];
#[must_use]
pub fn get(name: &str) -> Option<&'static Self> {
match name {
#structure_set_lookup_arms
_ => None,
}
}
}
)
}

View File

@@ -170,6 +170,10 @@ pub mod fuels;
#[path = "generated/effect.rs"]
pub mod effect;
#[rustfmt::skip]
#[path = "generated/structures.rs"]
pub mod structures;
#[rustfmt::skip]
#[path = "generated/potion.rs"]
pub mod potion;

View File

@@ -15,7 +15,6 @@ pub mod proto_chunk;
pub mod proto_chunk_test;
pub mod rule;
mod rule_test;
pub mod settings;
pub mod structure;
mod surface;

View File

@@ -5,6 +5,9 @@ use pumpkin_data::block_properties::is_air;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::structures::{
Structure, StructureKeys, StructurePlacementCalculator, StructureSet, WeightedEntry,
};
use pumpkin_data::tag;
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
use pumpkin_util::random::{RandomImpl, get_carver_seed};
@@ -35,9 +38,9 @@ use crate::generation::noise::aquifer_sampler::{
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
use crate::generation::noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK};
use crate::generation::structure::placement::StructurePlacementCalculator;
use crate::generation::structure::placement::should_generate_structure;
use crate::generation::structure::structures::StructureInstance;
use crate::generation::structure::{STRUCTURE_SETS, STRUCTURES, StructureKeys, WeightedEntry};
use crate::generation::structure::try_generate_structure;
use crate::generation::surface::rule::try_apply_material_rule;
use crate::{
BlockStateId, ProtoNoiseRouters,
@@ -972,9 +975,8 @@ impl ProtoChunk {
let center_z = center_chunk.z;
for (id, instance) in &center_chunk.structure_starts {
if let Some(s) = STRUCTURES.get(id)
&& s.step.ordinal() != step
{
let s = Structure::get(id);
if s.step.ordinal() != step {
continue;
}
@@ -1005,9 +1007,8 @@ impl ProtoChunk {
if let Some(neighbor) = cache.try_get_proto_chunk(neighbor_x, neighbor_z) {
for (id, instance) in &neighbor.structure_starts {
if let Some(s) = STRUCTURES.get(id)
&& s.step.ordinal() != step
{
let s = Structure::get(id);
if s.step.ordinal() != step {
continue;
}
@@ -1078,8 +1079,8 @@ impl ProtoChunk {
let seed = random_config.seed;
let calculator = StructurePlacementCalculator::new(seed as i64);
for (_set_name, set) in STRUCTURE_SETS.iter() {
if !set.placement.should_generate(&calculator, self.x, self.z) {
for set in StructureSet::ALL {
if !should_generate_structure(&set.placement, &calculator, self.x, self.z) {
continue;
}
@@ -1090,7 +1091,7 @@ impl ProtoChunk {
continue;
}
let mut candidates = set.structures.clone();
let mut candidates = set.structures.to_vec();
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed));
let carver_seed = get_carver_seed(&mut random, seed, self.x, self.z);
@@ -1130,17 +1131,19 @@ impl ProtoChunk {
entry: &WeightedEntry,
random_config: &GlobalRandomConfig,
) -> bool {
if let Some(structure) = STRUCTURES.get(&entry.structure) {
let position =
entry
.structure
.try_generate(structure, random_config.seed as i64, self, sea_level);
let structure = Structure::get(&entry.structure);
let position = try_generate_structure(
&entry.structure,
structure,
random_config.seed as i64,
self,
sea_level,
);
if let Some(pos) = position {
self.structure_starts
.insert(entry.structure.clone(), StructureInstance::Start(pos));
return true;
}
if let Some(pos) = position {
self.structure_starts
.insert(entry.structure, StructureInstance::Start(pos));
return true;
}
false
}
@@ -1175,7 +1178,7 @@ impl ProtoChunk {
.get_bounding_box()
.intersects_raw_xz(start_x, start_z, end_x, end_z)
{
references.push((key.clone(), start_data.start_pos));
references.push((*key, start_data.start_pos));
}
}
}

View File

@@ -1,104 +0,0 @@
// use std::{collections::HashMap, sync::LazyLock};
// use pumpkin_data::dimension::Dimension;
// use pumpkin_util::include_json_static;
// use serde::Deserialize;
// use crate::block::BlockStateCodec;
// use super::{biome_coords::to_block, height_limit::HeightLimitView, surface::rule::MaterialRule};
// pub static GENERATION_SETTINGS: LazyLock<HashMap<GeneratorSetting, GenerationSettings>> =
// LazyLock::new(
// || include_json_static!("../../../assets/chunk_gen_settings.json", HashMap<GeneratorSetting, GenerationSettings>),
// );
// pub fn gen_settings_from_dimension(dimension: &Dimension) -> &GenerationSettings {
// let setting = if dimension == &Dimension::OVERWORLD {
// GeneratorSetting::Overworld
// } else if dimension == &Dimension::THE_NETHER {
// GeneratorSetting::Nether
// } else {
// GeneratorSetting::End
// };
// GENERATION_SETTINGS.get(&setting).unwrap()
// }
// #[derive(Deserialize, Hash, PartialEq, Eq)]
// #[serde(rename_all = "snake_case")]
// pub enum GeneratorSetting {
// Overworld,
// LargeBiomes,
// Amplified,
// Nether,
// End,
// Caves,
// FloatingIslands,
// }
// #[derive(Deserialize)]
// pub struct GenerationSettings {
// pub aquifers_enabled: bool,
// pub ore_veins_enabled: bool,
// pub legacy_random_source: bool,
// pub sea_level: i32,
// pub default_fluid: BlockStateCodec,
// #[serde(rename = "noise")]
// pub shape: GenerationShapeConfig,
// pub surface_rule: MaterialRule,
// pub default_block: BlockStateCodec,
// }
// #[derive(Deserialize)]
// pub struct GenerationShapeConfig {
// pub min_y: i8,
// pub height: u16,
// size_horizontal: u8,
// size_vertical: u8,
// }
// impl GenerationShapeConfig {
// #[inline]
// #[must_use]
// pub fn vertical_cell_block_count(&self) -> u8 {
// to_block(self.size_vertical)
// }
// #[inline]
// #[must_use]
// pub fn horizontal_cell_block_count(&self) -> u8 {
// to_block(self.size_horizontal)
// }
// #[must_use]
// pub const fn max_y(&self) -> u16 {
// if self.min_y >= 0 {
// self.height + self.min_y as u16
// } else {
// (self.height as i32 + self.min_y as i32) as u16
// }
// }
// pub fn trim_height(&self, limit: &dyn HeightLimitView) -> Self {
// let new_min = self.min_y.max(limit.bottom_y());
// let this_top = if self.min_y >= 0 {
// self.height + self.min_y as u16
// } else {
// self.height - self.min_y.unsigned_abs() as u16
// };
// let new_top = this_top.min(limit.top_y());
// let new_height = if new_min >= 0 {
// new_top - new_min as u16
// } else {
// new_top + new_min.unsigned_abs() as u16
// };
// Self {
// min_y: new_min,
// height: new_height,
// size_horizontal: self.size_horizontal,
// size_vertical: self.size_vertical,
// }
// }
// }

View File

@@ -1,20 +1,16 @@
use std::{collections::HashMap, sync::LazyLock};
use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use pumpkin_util::include_json_static;
use serde::Deserialize;
use pumpkin_data::{
structures::{Structure, StructureKeys},
tag::{RegistryKey, get_tag_ids},
};
use crate::{
ProtoChunk,
generation::{
biome_coords,
structure::{
placement::StructurePlacement,
structures::{
StructureGenerator, StructureGeneratorContext, StructurePosition,
buried_treasure::BuriedTreasureGenerator, create_chunk_random,
stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator,
},
structure::structures::{
StructureGenerator, StructureGeneratorContext, StructurePosition,
buried_treasure::BuriedTreasureGenerator, create_chunk_random,
stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator,
},
},
};
@@ -24,223 +20,60 @@ pub mod placement;
pub mod shiftable_piece;
pub mod structures;
#[derive(Deserialize)]
pub struct StructureSet {
pub placement: StructurePlacement,
pub structures: Vec<WeightedEntry>,
}
#[must_use]
pub fn try_generate_structure(
key: &StructureKeys,
structure: &Structure,
seed: i64,
chunk: &ProtoChunk,
sea_level: i32,
) -> Option<StructurePosition> {
let random = create_chunk_random(seed, chunk.x, chunk.z);
let context = StructureGeneratorContext {
seed,
chunk_x: chunk.x,
chunk_z: chunk.z,
random,
sea_level,
min_y: chunk.bottom_y() as i32,
};
#[derive(Deserialize, Clone)]
pub struct WeightedEntry {
pub structure: StructureKeys,
pub weight: u32,
}
// #[derive(Deserialize)]
// pub enum StructureType {
// #[serde(rename = "minecraft:buried_treasure")]
// BuriedTreasure(BuriedTreasureGenerator),
// #[serde(rename = "minecraft:desert_pyramid")]
// DesertPyramid,
// #[serde(rename = "minecraft:end_city")]
// EndCity,
// #[serde(rename = "minecraft:fortress")]
// NetherFortress(NetherFortressGenerator),
// #[serde(rename = "minecraft:igloo")]
// Igloo,
// #[serde(rename = "minecraft:jigsaw")]
// Jigsaw,
// #[serde(rename = "minecraft:jungle_temple")]
// JungleTemple,
// #[serde(rename = "minecraft:mineshaft")]
// Mineshaft,
// #[serde(rename = "minecraft:nether_fossil")]
// NetherFossil,
// #[serde(rename = "minecraft:ocean_monument")]
// OceanMonument,
// #[serde(rename = "minecraft:ocean_ruin")]
// OceanRuin,
// #[serde(rename = "minecraft:ruined_portal")]
// RuinedPortal,
// #[serde(rename = "minecraft:shipwreck")]
// Shipwreck,
// #[serde(rename = "minecraft:stronghold")]
// Stronghold,
// #[serde(rename = "minecraft:swamp_hut")]
// SwampHut(SwampHutGenerator),
// #[serde(rename = "minecraft:woodland_mansion")]
// WoodlandMansion,
// }
#[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub enum StructureKeys {
#[serde(rename = "minecraft:pillager_outpost")]
PillagerOutpost,
#[serde(rename = "minecraft:mineshaft")]
Mineshaft,
#[serde(rename = "minecraft:mineshaft_mesa")]
MineshaftMesa,
#[serde(rename = "minecraft:mansion")]
Mansion,
#[serde(rename = "minecraft:jungle_pyramid")]
JunglePyramid,
#[serde(rename = "minecraft:desert_pyramid")]
DesertPyramid,
#[serde(rename = "minecraft:igloo")]
Igloo,
#[serde(rename = "minecraft:shipwreck")]
Shipwreck,
#[serde(rename = "minecraft:shipwreck_beached")]
ShipwreckBeached,
#[serde(rename = "minecraft:swamp_hut")]
SwampHut,
#[serde(rename = "minecraft:stronghold")]
Stronghold,
#[serde(rename = "minecraft:monument")]
Monument,
#[serde(rename = "minecraft:ocean_ruin_cold")]
OceanRuinCold,
#[serde(rename = "minecraft:ocean_ruin_warm")]
OceanRuinWarm,
#[serde(rename = "minecraft:fortress")]
Fortress,
#[serde(rename = "minecraft:nether_fossil")]
NetherFossil,
#[serde(rename = "minecraft:end_city")]
EndCity,
#[serde(rename = "minecraft:buried_treasure")]
BuriedTreasure,
#[serde(rename = "minecraft:bastion_remnant")]
BastionRemnant,
#[serde(rename = "minecraft:village_plains")]
VillagePlains,
#[serde(rename = "minecraft:village_desert")]
VillageDesert,
#[serde(rename = "minecraft:village_savanna")]
VillageSavanna,
#[serde(rename = "minecraft:village_snowy")]
VillageSnowy,
#[serde(rename = "minecraft:village_taiga")]
VillageTaiga,
#[serde(rename = "minecraft:ruined_portal")]
RuinedPortal,
#[serde(rename = "minecraft:ruined_portal_desert")]
RuinedPortalDesert,
#[serde(rename = "minecraft:ruined_portal_jungle")]
RuinedPortalJungle,
#[serde(rename = "minecraft:ruined_portal_swamp")]
RuinedPortalSwamp,
#[serde(rename = "minecraft:ruined_portal_mountain")]
RuinedPortalMountain,
#[serde(rename = "minecraft:ruined_portal_ocean")]
RuinedPortalOcean,
#[serde(rename = "minecraft:ruined_portal_nether")]
RuinedPortalNether,
#[serde(rename = "minecraft:ancient_city")]
AncientCity,
#[serde(rename = "minecraft:trail_ruins")]
TrailRuins,
#[serde(rename = "minecraft:trial_chambers")]
TrialChambers,
}
impl StructureKeys {
#[must_use]
pub fn try_generate(
&self,
structure: &Structure,
seed: i64,
chunk: &ProtoChunk,
sea_level: i32,
) -> Option<StructurePosition> {
let random = create_chunk_random(seed, chunk.x, chunk.z);
let context = StructureGeneratorContext {
seed,
chunk_x: chunk.x,
chunk_z: chunk.z,
random,
sea_level,
min_y: chunk.bottom_y() as i32,
};
let structure_pos = match self {
Self::BuriedTreasure => {
BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context)
}
// StructureKeys::Fortress => {
// NetherFortressGenerator::get_structure_position(&NetherFortressGenerator, context)
// }
Self::SwampHut => {
SwampHutGenerator::get_structure_position(&SwampHutGenerator, context)
}
Self::Stronghold => {
StrongholdGenerator::get_structure_position(&StrongholdGenerator, context)
}
// StructureKeys::DesertPyramid => DesertTempleGenerator::get_structure_position(
// &DesertTempleGenerator,
// context
// ),
// TODO
_ => None,
};
if let Some(pos) = structure_pos {
// Get the biome at the structure's starting position
let current_biome = chunk.get_biome_id(
biome_coords::from_block(pos.start_pos.0.x),
biome_coords::from_block(pos.start_pos.0.y),
biome_coords::from_block(pos.start_pos.0.z),
) as u16;
let biomes = get_tag_ids(
RegistryKey::WorldgenBiome,
structure
.biomes
.strip_prefix("#")
.unwrap_or(&structure.biomes),
)
.unwrap();
// Check if the biome is allowed for this structure
if biomes.contains(&current_biome) {
return Some(pos);
}
let structure_pos = match key {
StructureKeys::BuriedTreasure => {
BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context)
}
StructureKeys::SwampHut => {
SwampHutGenerator::get_structure_position(&SwampHutGenerator, context)
}
StructureKeys::Stronghold => {
StrongholdGenerator::get_structure_position(&StrongholdGenerator, context)
}
// TODO: Implement other structure types
_ => None,
};
None
if let Some(pos) = structure_pos {
// Get the biome at the structure's starting position
let current_biome = chunk.get_biome_id(
biome_coords::from_block(pos.start_pos.0.x),
biome_coords::from_block(pos.start_pos.0.y),
biome_coords::from_block(pos.start_pos.0.z),
) as u16;
let biomes = get_tag_ids(
RegistryKey::WorldgenBiome,
structure
.biomes
.strip_prefix("#")
.unwrap_or(structure.biomes),
)
.unwrap();
// Check if the biome is allowed for this structure
if biomes.contains(&current_biome) {
return Some(pos);
}
}
None
}
#[derive(Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Structure {
pub biomes: String,
pub step: GenerationStep,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GenerationStep {
RawGeneration,
Lakes,
LocalModifications,
UndergroundStructures,
SurfaceStructures,
Strongholds,
UndergroundOres,
UndergroundDecoration,
FluidSprings,
VegetalDecoration,
TopLayerModification,
}
impl GenerationStep {
#[must_use]
pub const fn ordinal(&self) -> usize {
*self as usize
}
}
pub static STRUCTURES: LazyLock<HashMap<StructureKeys, Structure>> = LazyLock::new(
|| include_json_static!("../../../../assets/structures.json", HashMap<StructureKeys, Structure>),
);
pub static STRUCTURE_SETS: LazyLock<HashMap<String, StructureSet>> = LazyLock::new(
|| include_json_static!("../../../../assets/structure_set.json", HashMap<String, StructureSet>),
);

View File

@@ -1,3 +1,7 @@
use pumpkin_data::structures::{
ConcentricRingsStructurePlacement, FrequencyReductionMethod, RandomSpreadStructurePlacement,
SpreadType, StructurePlacement, StructurePlacementCalculator, StructurePlacementType,
};
use pumpkin_util::{
math::floor_div,
random::{
@@ -5,238 +9,149 @@ use pumpkin_util::{
xoroshiro128::Xoroshiro,
},
};
use serde::Deserialize;
#[must_use]
pub fn should_generate_structure(
placement: &StructurePlacement,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
) -> bool {
is_start_chunk(
&placement.placement_type,
calculator,
chunk_x,
chunk_z,
placement.salt,
) && apply_frequency_reduction(
placement.frequency_reduction_method,
calculator.seed,
chunk_x,
chunk_z,
placement.salt,
placement.frequency.unwrap_or(1.0),
)
}
#[derive(Deserialize)]
pub struct StructurePlacement {
frequency_reduction_method: Option<FrequencyReductionMethod>,
frequency: Option<f32>,
fn apply_frequency_reduction(
method: Option<FrequencyReductionMethod>,
seed: i64,
chunk_x: i32,
chunk_z: i32,
salt: u32,
#[serde(flatten)]
r#type: StructurePlacementType,
}
impl StructurePlacement {
#[must_use]
pub fn should_generate(
&self,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
) -> bool {
self.r#type
.is_start_chunk(calculator, chunk_x, chunk_z, self.salt)
&& self.apply_frequency_reduction(calculator.seed, chunk_x, chunk_z)
// TODO: add exclusion_zone, only used for pillager_outposts
frequency: f32,
) -> bool {
if frequency >= 1.0 {
return true;
}
fn apply_frequency_reduction(&self, seed: i64, chunk_x: i32, chunk_z: i32) -> bool {
let frequency = self.frequency.unwrap_or(1.0);
frequency >= 1.0
|| self
.frequency_reduction_method
.as_ref()
.unwrap_or(&FrequencyReductionMethod::Default)
.should_generate(seed, chunk_x, chunk_z, self.salt, frequency)
}
let method = method.unwrap_or(FrequencyReductionMethod::Default);
should_generate_frequency(method, seed, chunk_x, chunk_z, salt, frequency)
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FrequencyReductionMethod {
Default,
#[serde(rename = "legacy_type_1")]
LegacyType1,
#[serde(rename = "legacy_type_2")]
LegacyType2,
#[serde(rename = "legacy_type_3")]
LegacyType3,
}
impl FrequencyReductionMethod {
#[must_use]
pub fn should_generate(
&self,
seed: i64,
chunk_x: i32,
chunk_z: i32,
salt: u32,
frequency: f32,
) -> bool {
match self {
Self::Default => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, salt);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed));
random.next_f32() < frequency
}
Self::LegacyType1 => {
let x = chunk_x >> 4;
let z = chunk_z >> 4;
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(
(x ^ z << 4) as u64 ^ seed as u64,
));
random.next_i32(); // yeah mojang just does that and does not use the value
random.next_bounded_i32((1.0 / frequency) as i32) == 0
}
Self::LegacyType2 => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, 10387320);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed));
random.next_f32() < frequency
}
Self::LegacyType3 => {
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
let carver_seed = get_carver_seed(&mut random, seed as u64, chunk_x, chunk_z);
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
random.next_f64() < frequency as f64
}
fn should_generate_frequency(
method: FrequencyReductionMethod,
seed: i64,
chunk_x: i32,
chunk_z: i32,
salt: u32,
frequency: f32,
) -> bool {
match method {
FrequencyReductionMethod::Default => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, salt);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed));
random.next_f32() < frequency
}
FrequencyReductionMethod::LegacyType1 => {
let x = chunk_x >> 4;
let z = chunk_z >> 4;
let mut random =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed((x ^ z << 4) as u64 ^ seed as u64));
random.next_i32();
random.next_bounded_i32((1.0 / frequency) as i32) == 0
}
FrequencyReductionMethod::LegacyType2 => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, 10387320);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed));
random.next_f32() < frequency
}
FrequencyReductionMethod::LegacyType3 => {
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
let carver_seed = get_carver_seed(&mut random, seed as u64, chunk_x, chunk_z);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
random.next_f64() < frequency as f64
}
}
}
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum StructurePlacementType {
#[serde(rename = "minecraft:random_spread")]
RandomSpread(RandomSpreadStructurePlacement),
#[serde(rename = "minecraft:concentric_rings")]
ConcentricRings(ConcentricRingsStructurePlacement),
}
impl StructurePlacementType {
#[must_use]
pub fn is_start_chunk(
&self,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> bool {
match self {
Self::RandomSpread(placement) => {
placement.is_start_chunk(calculator, chunk_x, chunk_z, salt)
}
// This is needed for Stronghold, since it is placed in rings
Self::ConcentricRings(placement) => {
placement.is_start_chunk(calculator, chunk_x, chunk_z, salt)
}
fn is_start_chunk(
placement_type: &StructurePlacementType,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> bool {
match placement_type {
StructurePlacementType::RandomSpread(placement) => {
is_start_chunk_random_spread(placement, calculator, chunk_x, chunk_z, salt)
}
StructurePlacementType::ConcentricRings(placement) => {
is_start_chunk_concentric_rings(placement, calculator, chunk_x, chunk_z, salt)
}
}
}
#[derive(Deserialize)]
pub struct RandomSpreadStructurePlacement {
spacing: i32,
separation: i32,
spread_type: Option<SpreadType>,
fn get_start_chunk_random_spread(
placement: &RandomSpreadStructurePlacement,
seed: i64,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> (i32, i32) {
let x = floor_div(chunk_x, placement.spacing);
let z = floor_div(chunk_z, placement.spacing);
let region_seed = get_region_seed(seed as u64, x, z, salt);
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(region_seed));
let bound = placement.spacing - placement.separation;
let spread_type = placement.spread_type.unwrap_or(SpreadType::Linear);
let rand_x = spread_type.get(&mut random, bound);
let rand_z = spread_type.get(&mut random, bound);
(
x * placement.spacing + rand_x,
z * placement.spacing + rand_z,
)
}
#[derive(Deserialize)]
pub struct ConcentricRingsStructurePlacement {
spread: i32,
distance: i32,
count: i32,
preferred_biomes: String,
fn is_start_chunk_random_spread(
placement: &RandomSpreadStructurePlacement,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> bool {
let pos = get_start_chunk_random_spread(placement, calculator.seed, chunk_x, chunk_z, salt);
(chunk_x == pos.0) && (chunk_z == pos.1)
}
impl ConcentricRingsStructurePlacement {
#[must_use]
pub fn is_start_chunk(
&self,
_calculator: &StructurePlacementCalculator,
_chunk_x: i32,
_chunk_z: i32,
_salt: u32,
) -> bool {
// TODO: this is entirely wrong, but the original logic is too complex for now
// let x = (chunk_x << 4) as f64;
// let z = (chunk_z << 4) as f64;
// let distance_sq = x * x + z * z;
// let distance = distance_sq.sqrt();
// let ring_width = 1000.0;
// let ring_gap = 2000.0;
// let in_ring = (distance % (ring_width + ring_gap)) < ring_width;
// if !in_ring {
// return false;
// }
// let seed = (chunk_x as i64).wrapping_mul(341873128712)
// ^ (chunk_z as i64).wrapping_mul(132897987541)
// ^ (salt as i64);
// (seed.abs() % 400) == 0
rand::random_bool(1.0 / 100.0)
}
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SpreadType {
Linear,
Triangular,
}
impl SpreadType {
pub fn get(&self, random: &mut RandomGenerator, bound: i32) -> i32 {
match self {
Self::Linear => random.next_bounded_i32(bound),
Self::Triangular => i32::midpoint(
random.next_bounded_i32(bound),
random.next_bounded_i32(bound),
),
}
}
}
impl RandomSpreadStructurePlacement {
fn get_start_chunk(&self, seed: i64, chunk_x: i32, chunk_z: i32, salt: u32) -> (i32, i32) {
let x = floor_div(chunk_x, self.spacing);
let z = floor_div(chunk_z, self.spacing);
let region_seed = get_region_seed(seed as u64, x, z, salt);
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(region_seed));
let bound = self.spacing - self.separation;
let spread_type = self.spread_type.as_ref().unwrap_or(&SpreadType::Linear);
let rand_x = spread_type.get(&mut random, bound);
let rand_z = spread_type.get(&mut random, bound);
(x * self.spacing + rand_x, z * self.spacing + rand_z)
}
#[must_use]
pub fn is_start_chunk(
&self,
calculator: &StructurePlacementCalculator,
chunk_x: i32,
chunk_z: i32,
salt: u32,
) -> bool {
let pos = self.get_start_chunk(calculator.seed, chunk_x, chunk_z, salt);
(chunk_x == pos.0) && (chunk_z == pos.1)
}
}
pub struct StructurePlacementCalculator {
pub seed: i64,
}
impl StructurePlacementCalculator {
#[must_use]
pub const fn new(seed: i64) -> Self {
Self { seed }
}
fn is_start_chunk_concentric_rings(
_placement: &ConcentricRingsStructurePlacement,
_calculator: &StructurePlacementCalculator,
_chunk_x: i32,
_chunk_z: i32,
_salt: u32,
) -> bool {
// TODO: Implement proper concentric rings logic
rand::random_bool(1.0 / 100.0)
}
#[cfg(test)]
mod tests {
use pumpkin_data::structures::RandomSpreadStructurePlacement;
use pumpkin_util::random::{
RandomGenerator, RandomImpl, get_region_seed, legacy_rand::LegacyRand,
};
use crate::generation::structure::placement::RandomSpreadStructurePlacement;
use crate::generation::structure::placement::get_start_chunk_random_spread;
#[test]
fn get_start_chunk_random() {
@@ -252,7 +167,7 @@ mod tests {
separation: 8,
spread_type: None,
};
let (x, z) = random.get_start_chunk(123, 1, 1, 14357620);
let (x, z) = get_start_chunk_random_spread(&random, 123, 1, 1, 14357620);
assert_eq!(x, 5);
assert_eq!(z, 4);
}