897 lines
30 KiB
Rust
897 lines
30 KiB
Rust
use heck::ToPascalCase;
|
|
use std::{collections::BTreeMap, fs};
|
|
|
|
use heck::ToShoutySnakeCase;
|
|
use proc_macro2::{Span, TokenStream};
|
|
use quote::{format_ident, quote};
|
|
use serde::Deserialize;
|
|
use syn::LitInt;
|
|
|
|
#[derive(Deserialize)]
|
|
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 {
|
|
/// Whether this biome has precipitation (rain or snow).
|
|
has_precipitation: bool,
|
|
/// Base temperature of the biome affecting weather and foliage color.
|
|
temperature: f32,
|
|
/// Downfall value affecting how wet the biome is (influences snow/rain).
|
|
downfall: f32,
|
|
/// Optional modifier that changes how temperature is applied.
|
|
temperature_modifier: Option<TemperatureModifier>,
|
|
#[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.
|
|
creature_spawn_probability: Option<f32>,
|
|
/// Spawn group data for each entity category in this biome.
|
|
spawners: SpawnGroups,
|
|
/// Per-entity spawn cost budget entries, keyed by namespaced entity ID.
|
|
spawn_costs: BTreeMap<String, SpawnCosts>,
|
|
/// Numeric registry ID assigned to this biome.
|
|
#[serde(default)]
|
|
pub id: u8,
|
|
}
|
|
|
|
/// Spawn group data for all entity categories within a biome.
|
|
#[derive(Deserialize, PartialEq, Eq, Hash)]
|
|
struct SpawnGroups {
|
|
/// Hostile mob spawners for this biome.
|
|
monster: Vec<Spawner>,
|
|
/// Ambient creature spawners (e.g. bats) for this biome.
|
|
ambient: Vec<Spawner>,
|
|
/// Axolotl spawners for this biome.
|
|
axolotls: Vec<Spawner>,
|
|
/// Passive creature spawners (e.g. cows, sheep) for this biome.
|
|
creature: Vec<Spawner>,
|
|
/// Miscellaneous entity spawners for this biome.
|
|
misc: Vec<Spawner>,
|
|
/// Underground water creature spawners (e.g. glow squid) for this biome.
|
|
underground_water_creature: Vec<Spawner>,
|
|
/// Water ambient spawners (e.g. fish) for this biome.
|
|
water_ambient: Vec<Spawner>,
|
|
/// Water creature spawners (e.g. dolphins) for this biome.
|
|
water_creature: Vec<Spawner>,
|
|
}
|
|
|
|
/// A single entity spawner entry within a spawn group, as defined in `biome.json`.
|
|
#[derive(Deserialize, Hash, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct Spawner {
|
|
/// Namespaced entity type ID (e.g. `"minecraft:zombie"`).
|
|
r#type: String,
|
|
/// Minimum number of entities in a spawn group.
|
|
min_count: i32,
|
|
/// Maximum number of entities in a spawn group.
|
|
max_count: i32,
|
|
}
|
|
|
|
impl Spawner {
|
|
/// Converts this spawner entry into a `TokenStream` for use in generated code.
|
|
pub fn to_tokens(&self) -> TokenStream {
|
|
let r#type = &self.r#type;
|
|
let min_count = &self.min_count;
|
|
let max_count = &self.max_count;
|
|
quote! {
|
|
Spawner {
|
|
r#type: #r#type,
|
|
min_count: #min_count,
|
|
max_count: #max_count,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mob spawn cost data controlling how this entity affects biome spawn budgets.
|
|
#[derive(Deserialize, PartialEq)]
|
|
struct SpawnCosts {
|
|
/// Maximum energy this entity type may consume from the spawn budget.
|
|
energy_budget: f64,
|
|
/// Energy cost charged to the budget each time this entity spawns.
|
|
charge: f64,
|
|
}
|
|
|
|
impl SpawnCosts {
|
|
/// Converts these spawn costs into a `TokenStream` for use in generated code.
|
|
pub fn to_tokens(&self) -> TokenStream {
|
|
let energy_budget = &self.energy_budget;
|
|
let charge = &self.charge;
|
|
quote! {
|
|
SpawnCosts {
|
|
energy_budget: #energy_budget,
|
|
charge: #charge,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Optional modifier that adjusts how biome temperature behaves (e.g. frozen biomes).
|
|
#[derive(Deserialize, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
enum TemperatureModifier {
|
|
/// No modification; temperature is used as-is.
|
|
None,
|
|
/// Temperature is forced below freezing regardless of the base value.
|
|
Frozen,
|
|
}
|
|
|
|
/// An inclusive range used as one dimension in the multi-noise biome source tree.
|
|
#[derive(Deserialize)]
|
|
struct ParameterRange {
|
|
/// Lower bound of the noise range (inclusive).
|
|
min: i64,
|
|
/// Upper bound of the noise range (inclusive).
|
|
max: i64,
|
|
}
|
|
|
|
impl ParameterRange {
|
|
/// Converts this parameter range into a `TokenStream` for use in generated code.
|
|
fn into_token_stream(self) -> TokenStream {
|
|
let min = self.min;
|
|
let max = self.max;
|
|
|
|
quote! {
|
|
ParameterRange {
|
|
min: #min,
|
|
max: #max
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A node in the multi-noise biome source k-d tree, either a leaf (resolved biome)
|
|
/// or a branch (spatial partition with child nodes).
|
|
#[derive(Deserialize)]
|
|
#[serde(tag = "_type", rename_all = "lowercase")]
|
|
enum BiomeTree {
|
|
/// A terminal node resolving to a specific biome.
|
|
Leaf {
|
|
/// The seven noise-parameter ranges defining this leaf's position in parameter space.
|
|
parameters: [ParameterRange; 7],
|
|
/// Namespaced biome resource location (e.g. `"minecraft:plains"`).
|
|
biome: String,
|
|
},
|
|
/// An interior node that partitions parameter space and delegates to child nodes.
|
|
Branch {
|
|
/// The seven noise-parameter ranges bounding this branch's region.
|
|
parameters: [ParameterRange; 7],
|
|
/// Child nodes of this branch (leaves or further branches).
|
|
#[serde(rename = "subTree")]
|
|
nodes: Box<[Self]>,
|
|
},
|
|
}
|
|
|
|
impl BiomeTree {
|
|
/// Converts this biome tree node into a `TokenStream` for use in generated code.
|
|
fn into_token_stream(self) -> TokenStream {
|
|
match self {
|
|
Self::Leaf { parameters, biome } => {
|
|
let biome = format_ident!(
|
|
"{}",
|
|
biome
|
|
.strip_prefix("minecraft:")
|
|
.unwrap()
|
|
.to_shouty_snake_case()
|
|
);
|
|
let parameters = parameters.map(ParameterRange::into_token_stream);
|
|
quote! {
|
|
BiomeTree::Leaf {
|
|
parameters: [#(#parameters),*],
|
|
biome: &Biome::#biome
|
|
}
|
|
}
|
|
}
|
|
Self::Branch { parameters, nodes } => {
|
|
let nodes = nodes
|
|
.into_iter()
|
|
.map(Self::into_token_stream)
|
|
.collect::<Vec<_>>();
|
|
let parameters = parameters.map(ParameterRange::into_token_stream);
|
|
quote! {
|
|
BiomeTree::Branch {
|
|
parameters: [#(#parameters),*],
|
|
nodes: &[#(#nodes),*]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Root container holding the overworld and nether multi-noise biome source trees.
|
|
#[derive(Deserialize)]
|
|
struct MultiNoiseBiomeSuppliers {
|
|
/// Multi-noise k-d tree used to resolve biomes in the overworld dimension.
|
|
overworld: BiomeTree,
|
|
/// Multi-noise k-d tree used to resolve biomes in the nether dimension.
|
|
nether: BiomeTree,
|
|
}
|
|
|
|
/// Generates the `TokenStream` for the `Biome` struct, its constants, lookup methods,
|
|
/// the multi-noise biome source trees, and the `BiomeTree` search implementation.
|
|
pub fn build() -> TokenStream {
|
|
let dir = std::path::Path::new("../../assets/datapacks/26_2/data/minecraft/worldgen/biome");
|
|
let mut biomes: BTreeMap<String, Biome> = BTreeMap::new();
|
|
let mut entries: Vec<_> = fs::read_dir(dir)
|
|
.expect("Missing worldgen/biome directory")
|
|
.flatten()
|
|
.filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
|
|
.collect();
|
|
entries.sort_by_key(|e| e.path());
|
|
|
|
for (i, entry) in entries.iter().enumerate() {
|
|
let stem = entry
|
|
.path()
|
|
.file_stem()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.into_owned();
|
|
let content = fs::read_to_string(entry.path()).expect("Failed to read biome file");
|
|
let mut biome: Biome = serde_json::from_str(&content).expect("Failed to parse biome JSON");
|
|
biome.id = i as u8;
|
|
biomes.insert(stem, biome);
|
|
}
|
|
|
|
let biome_trees: MultiNoiseBiomeSuppliers = serde_json::from_str(
|
|
&fs::read_to_string("../../assets/multi_noise_biome_tree.json").unwrap(),
|
|
)
|
|
.expect("Failed to parse multi_noise_biome_tree.json");
|
|
let bedrock_biomes: BTreeMap<String, GeyserBiomeMapping> =
|
|
serde_json::from_str(&fs::read_to_string("../../assets/bedrock/biomes.json").unwrap())
|
|
.expect("Failed to parse biomes.json");
|
|
|
|
let mut variants = TokenStream::new();
|
|
let mut name_to_type = TokenStream::new();
|
|
let mut id_to_type = TokenStream::new();
|
|
let mut all_variants = TokenStream::new();
|
|
|
|
for (name, biome) in biomes {
|
|
let full_name = format!("minecraft:{name}");
|
|
let be_network_id = bedrock_biomes
|
|
.get(&full_name)
|
|
.map(|mapping| mapping.bedrock_id)
|
|
.unwrap_or(1);
|
|
|
|
// let full_name = format!("minecraft:{name}");
|
|
let format_name = format_ident!("{}", name.to_shouty_snake_case());
|
|
let has_precipitation = biome.has_precipitation;
|
|
let temperature = biome.temperature;
|
|
let downfall = biome.downfall;
|
|
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()
|
|
.map(|step| {
|
|
let step_features: Vec<TokenStream> = step
|
|
.iter()
|
|
.map(|f| {
|
|
let name = f.strip_prefix("minecraft:").unwrap_or(f);
|
|
let variant_name = format_ident!("{}", name.to_pascal_case());
|
|
quote! { crate::placed_feature::PlacedFeature::#variant_name }
|
|
})
|
|
.collect();
|
|
quote! { &[#(#step_features),*] }
|
|
})
|
|
.collect();
|
|
|
|
let creature_spawn_probability = &biome.creature_spawn_probability.unwrap_or(0.1);
|
|
|
|
let temperature_modifier = biome
|
|
.temperature_modifier
|
|
.unwrap_or(TemperatureModifier::None);
|
|
|
|
let monster: Vec<_> = biome
|
|
.spawners
|
|
.monster
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let ambient: Vec<_> = biome
|
|
.spawners
|
|
.ambient
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let axolotls: Vec<_> = biome
|
|
.spawners
|
|
.axolotls
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let creature: Vec<_> = biome
|
|
.spawners
|
|
.creature
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let misc: Vec<_> = biome.spawners.misc.iter().map(Spawner::to_tokens).collect();
|
|
let underground_water_creature: Vec<_> = biome
|
|
.spawners
|
|
.underground_water_creature
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let water_ambient: Vec<_> = biome
|
|
.spawners
|
|
.water_ambient
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
let water_creature: Vec<_> = biome
|
|
.spawners
|
|
.water_creature
|
|
.iter()
|
|
.map(Spawner::to_tokens)
|
|
.collect();
|
|
|
|
let spawners = quote! {
|
|
SpawnGroups {
|
|
monster: &[#(#monster),*],
|
|
ambient: &[#(#ambient),*],
|
|
axolotls: &[#(#axolotls),*],
|
|
creature: &[#(#creature),*],
|
|
misc: &[#(#misc),*],
|
|
underground_water_creature: &[#(#underground_water_creature),*],
|
|
water_ambient: &[#(#water_ambient),*],
|
|
water_creature: &[#(#water_creature),*],
|
|
}
|
|
};
|
|
|
|
let spawn_costs: Vec<_> = biome
|
|
.spawn_costs
|
|
.iter()
|
|
.map(|(name, cost)| {
|
|
let cost_token = cost.to_tokens();
|
|
let entity_type = name.strip_prefix("minecraft:").unwrap();
|
|
quote! {
|
|
#entity_type => #cost_token
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let temperature_modifier = match temperature_modifier {
|
|
TemperatureModifier::Frozen => quote! { TemperatureModifier::Frozen },
|
|
TemperatureModifier::None => quote! { TemperatureModifier::None },
|
|
};
|
|
let index = LitInt::new(&biome.id.to_string(), Span::call_site());
|
|
|
|
variants.extend([quote! {
|
|
pub const #format_name: Biome = Biome {
|
|
id: #index,
|
|
registry_id: #name,
|
|
be_network_id: #be_network_id,
|
|
weather: Weather::new(
|
|
#has_precipitation,
|
|
#temperature,
|
|
#temperature_modifier,
|
|
#downfall
|
|
),
|
|
features: &[#(#features),*],
|
|
carvers: &[#(#carvers),*],
|
|
creature_spawn_probability: #creature_spawn_probability,
|
|
spawners: #spawners,
|
|
spawn_costs: phf::phf_map! {
|
|
#(#spawn_costs),*
|
|
},
|
|
};
|
|
}]);
|
|
|
|
name_to_type.extend(quote! { #name => Some(&Self::#format_name), });
|
|
id_to_type.extend(quote! { #index => Some(&Self::#format_name), });
|
|
all_variants.extend(quote! { &Self::#format_name, });
|
|
}
|
|
|
|
let overworld_tree = biome_trees.overworld.into_token_stream();
|
|
let nether_tree = biome_trees.nether.into_token_stream();
|
|
quote! {
|
|
use crate::biome::de::Deserialize;
|
|
use crate::entity_type::EntityType;
|
|
use crate::tag::Taggable;
|
|
use crate::tag::RegistryKey;
|
|
use pumpkin_util::biome::{TemperatureModifier, Weather};
|
|
use serde::{Deserializer, de};
|
|
use std::{fmt, hash::{Hasher, Hash}};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Biome {
|
|
pub id: u8,
|
|
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,
|
|
pub spawn_costs: phf::Map<&'static str, SpawnCosts>,
|
|
}
|
|
|
|
impl PartialEq<u8> for Biome {
|
|
fn eq(&self, other: &u8) -> bool {
|
|
self.id == *other
|
|
}
|
|
}
|
|
|
|
impl PartialEq<Biome> for u8 {
|
|
fn eq(&self, other: &Biome) -> bool {
|
|
*self == other.id
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SpawnGroups {
|
|
pub monster: &'static [Spawner],
|
|
pub ambient: &'static [Spawner],
|
|
pub axolotls: &'static [Spawner],
|
|
pub creature: &'static [Spawner],
|
|
pub misc: &'static [Spawner],
|
|
pub underground_water_creature: &'static [Spawner],
|
|
pub water_ambient: &'static [Spawner],
|
|
pub water_creature: &'static [Spawner],
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Spawner {
|
|
pub r#type: &'static str,
|
|
pub min_count: i32,
|
|
pub max_count: i32,
|
|
}
|
|
|
|
impl PartialEq for Biome {
|
|
fn eq(&self, other: &Biome) -> bool {
|
|
self.id == other.id
|
|
}
|
|
}
|
|
|
|
impl Eq for Biome {}
|
|
|
|
impl Hash for Biome {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.id.hash(state);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SpawnCosts {
|
|
pub energy_budget: f64,
|
|
pub charge: f64,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for &'static Biome {
|
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
struct BiomeVisitor;
|
|
|
|
impl de::Visitor<'_> for BiomeVisitor {
|
|
type Value = &'static Biome;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a biome name as a string")
|
|
}
|
|
|
|
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
|
|
self.visit_str(&v)
|
|
}
|
|
|
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
|
let biome = Biome::from_name(value.strip_prefix("minecraft:").unwrap_or(value));
|
|
biome.ok_or_else(|| E::unknown_variant(value, &["unknown biome"]))
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_str(BiomeVisitor)
|
|
}
|
|
}
|
|
|
|
impl Biome {
|
|
#variants
|
|
|
|
pub const ALL: &'static [&'static Self] = &[#all_variants];
|
|
|
|
pub fn from_name(name: &str) -> Option<&'static Self> {
|
|
match name {
|
|
#name_to_type
|
|
_ => None
|
|
}
|
|
}
|
|
|
|
pub const fn from_id(id: u8) -> Option<&'static Self> {
|
|
match id {
|
|
#id_to_type
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Taggable for Biome {
|
|
#[inline]
|
|
fn registry_id(&self) -> u16 {
|
|
self.id as u16
|
|
}
|
|
#[inline]
|
|
fn tag_key() -> RegistryKey {
|
|
RegistryKey::WorldgenBiome
|
|
}
|
|
#[inline]
|
|
fn registry_key(&self) -> &str {
|
|
self.registry_id
|
|
}
|
|
}
|
|
|
|
pub const QUANTIZATION_FACTOR: f32 = 10000.0;
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn quantize_coord(coord: f32) -> i64 {
|
|
(coord * QUANTIZATION_FACTOR) as i64
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn unquantize_coord(coord: i64) -> f32 {
|
|
coord as f32 / QUANTIZATION_FACTOR
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub struct Parameter {
|
|
pub min: i64,
|
|
pub max: i64,
|
|
}
|
|
|
|
pub type ParameterRange = Parameter;
|
|
|
|
impl Parameter {
|
|
#[must_use]
|
|
pub const fn new(min: i64, max: i64) -> Self {
|
|
Self { min, max }
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn point(min: f32) -> Self {
|
|
Self::span(min, min)
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn span(min: f32, max: f32) -> Self {
|
|
assert!(min <= max, "min > max");
|
|
Self {
|
|
min: quantize_coord(min),
|
|
max: quantize_coord(max),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn span_quantized(min: i64, max: i64) -> Self {
|
|
assert!(min <= max, "min > max");
|
|
Self { min, max }
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn calc_distance(&self, noise: i64) -> i64 {
|
|
self.distance(noise)
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn distance(&self, target: i64) -> i64 {
|
|
let above = target - self.max;
|
|
let below = self.min - target;
|
|
if above > 0 {
|
|
above
|
|
} else if below > 0 {
|
|
below
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn distance_parameter(&self, target: &Self) -> i64 {
|
|
let above = target.min - self.max;
|
|
let below = self.min - target.max;
|
|
if above > 0 {
|
|
above
|
|
} else if below > 0 {
|
|
below
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn span_with(&self, other: Option<&Self>) -> Self {
|
|
match other {
|
|
None => *self,
|
|
Some(other) => Self {
|
|
min: if self.min < other.min { self.min } else { other.min },
|
|
max: if self.max > other.max { self.max } else { other.max },
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Parameter {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if self.min == self.max {
|
|
write!(f, "{}", self.min)
|
|
} else {
|
|
write!(f, "[{}-{}]", self.min, self.max)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub struct TargetPoint {
|
|
pub temperature: i64,
|
|
pub humidity: i64,
|
|
pub continentalness: i64,
|
|
pub erosion: i64,
|
|
pub depth: i64,
|
|
pub weirdness: i64,
|
|
}
|
|
|
|
impl TargetPoint {
|
|
#[must_use]
|
|
pub const fn new(
|
|
temperature: i64,
|
|
humidity: i64,
|
|
continentalness: i64,
|
|
erosion: i64,
|
|
depth: i64,
|
|
weirdness: i64,
|
|
) -> Self {
|
|
Self {
|
|
temperature,
|
|
humidity,
|
|
continentalness,
|
|
erosion,
|
|
depth,
|
|
weirdness,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn to_parameter_array(&self) -> [i64; 7] {
|
|
[
|
|
self.temperature,
|
|
self.humidity,
|
|
self.continentalness,
|
|
self.erosion,
|
|
self.depth,
|
|
self.weirdness,
|
|
0,
|
|
]
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn convert_to_list(&self) -> [i64; 7] {
|
|
self.to_parameter_array()
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
pub struct ParameterPoint {
|
|
pub temperature: Parameter,
|
|
pub humidity: Parameter,
|
|
pub continentalness: Parameter,
|
|
pub erosion: Parameter,
|
|
pub depth: Parameter,
|
|
pub weirdness: Parameter,
|
|
pub offset: i64,
|
|
}
|
|
|
|
impl ParameterPoint {
|
|
#[must_use]
|
|
pub const fn new(
|
|
temperature: Parameter,
|
|
humidity: Parameter,
|
|
continentalness: Parameter,
|
|
erosion: Parameter,
|
|
depth: Parameter,
|
|
weirdness: Parameter,
|
|
offset: i64,
|
|
) -> Self {
|
|
Self {
|
|
temperature,
|
|
humidity,
|
|
continentalness,
|
|
erosion,
|
|
depth,
|
|
weirdness,
|
|
offset,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
pub const fn fitness(&self, target: &TargetPoint) -> i64 {
|
|
let temp_dist = self.temperature.distance(target.temperature);
|
|
let hum_dist = self.humidity.distance(target.humidity);
|
|
let cont_dist = self.continentalness.distance(target.continentalness);
|
|
let ero_dist = self.erosion.distance(target.erosion);
|
|
let dep_dist = self.depth.distance(target.depth);
|
|
let wei_dist = self.weirdness.distance(target.weirdness);
|
|
|
|
temp_dist * temp_dist
|
|
+ hum_dist * hum_dist
|
|
+ cont_dist * cont_dist
|
|
+ ero_dist * ero_dist
|
|
+ dep_dist * dep_dist
|
|
+ wei_dist * wei_dist
|
|
+ self.offset * self.offset
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn parameter_space(&self) -> [Parameter; 7] {
|
|
[
|
|
self.temperature,
|
|
self.humidity,
|
|
self.continentalness,
|
|
self.erosion,
|
|
self.depth,
|
|
self.weirdness,
|
|
Parameter::new(self.offset, self.offset),
|
|
]
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn target(
|
|
temperature: f32,
|
|
humidity: f32,
|
|
continentalness: f32,
|
|
erosion: f32,
|
|
depth: f32,
|
|
weirdness: f32,
|
|
) -> TargetPoint {
|
|
TargetPoint::new(
|
|
quantize_coord(temperature),
|
|
quantize_coord(humidity),
|
|
quantize_coord(continentalness),
|
|
quantize_coord(erosion),
|
|
quantize_coord(depth),
|
|
quantize_coord(weirdness),
|
|
)
|
|
}
|
|
|
|
#[must_use]
|
|
pub const fn parameters(
|
|
temperature: f32,
|
|
humidity: f32,
|
|
continentalness: f32,
|
|
erosion: f32,
|
|
depth: f32,
|
|
weirdness: f32,
|
|
offset: f32,
|
|
) -> ParameterPoint {
|
|
ParameterPoint::new(
|
|
Parameter::point(temperature),
|
|
Parameter::point(humidity),
|
|
Parameter::point(continentalness),
|
|
Parameter::point(erosion),
|
|
Parameter::point(depth),
|
|
Parameter::point(weirdness),
|
|
quantize_coord(offset),
|
|
)
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
pub enum BiomeTree {
|
|
Leaf {
|
|
parameters: [ParameterRange; 7],
|
|
biome: &'static Biome,
|
|
},
|
|
Branch {
|
|
parameters: [ParameterRange; 7],
|
|
nodes: &'static [BiomeTree],
|
|
},
|
|
}
|
|
|
|
|
|
impl BiomeTree {
|
|
pub fn get(
|
|
&'static self,
|
|
point_list: &[i64; 7],
|
|
previous_result_node: &mut Option<&'static BiomeTree>,
|
|
) -> &'static Biome {
|
|
// Initialize best distance from the previous result for spatial coherence
|
|
let mut best_dist = previous_result_node
|
|
.map(|node| node.get_squared_distance(point_list))
|
|
.unwrap_or(i64::MAX);
|
|
|
|
let mut best_node = previous_result_node.unwrap_or(self);
|
|
|
|
self.search(point_list, &mut best_dist, &mut best_node);
|
|
|
|
match best_node {
|
|
BiomeTree::Leaf { biome, .. } => {
|
|
*previous_result_node = Some(best_node);
|
|
biome
|
|
}
|
|
// Should not happen with valid tree data
|
|
_ => unreachable!("Biome search failed to find a leaf"),
|
|
}
|
|
}
|
|
|
|
fn search(
|
|
&'static self,
|
|
point: &[i64; 7],
|
|
best_dist: &mut i64,
|
|
best_node: &mut &'static BiomeTree,
|
|
) {
|
|
let dist = self.get_squared_distance(point);
|
|
|
|
// PRUNING: If this branch/leaf is further than our best, skip it and all children
|
|
if dist >= *best_dist {
|
|
return;
|
|
}
|
|
|
|
match self {
|
|
Self::Leaf { .. } => {
|
|
*best_dist = dist;
|
|
*best_node = self;
|
|
}
|
|
Self::Branch { nodes, .. } => {
|
|
for node in *nodes {
|
|
node.search(point, best_dist, best_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn get_squared_distance(&self, p: &[i64; 7]) -> i64 {
|
|
let params = match self {
|
|
Self::Leaf { parameters, .. } => parameters,
|
|
Self::Branch { parameters, .. } => parameters,
|
|
};
|
|
|
|
let d0 = params[0].calc_distance(p[0]);
|
|
let d1 = params[1].calc_distance(p[1]);
|
|
let d2 = params[2].calc_distance(p[2]);
|
|
let d3 = params[3].calc_distance(p[3]);
|
|
let d4 = params[4].calc_distance(p[4]);
|
|
let d5 = params[5].calc_distance(p[5]);
|
|
let d6 = params[6].calc_distance(p[6]);
|
|
|
|
d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3 + d4 * d4 + d5 * d5 + d6 * d6
|
|
}
|
|
}
|
|
|
|
pub const OVERWORLD_BIOME_SOURCE: BiomeTree = #overworld_tree;
|
|
pub const NETHER_BIOME_SOURCE: BiomeTree = #nether_tree;
|
|
}
|
|
}
|