Prepare chunk entity generation

Also switch to strip_prefix instead of replace, much more efficient
This commit is contained in:
Alexander Medvedev 2025-06-09 16:04:08 +02:00
parent 25e3286476
commit 1a736267ba
17 changed files with 1056 additions and 265 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,39 @@ struct Biome {
temperature_modifier: Option<TemperatureModifier>, temperature_modifier: Option<TemperatureModifier>,
//carvers: Vec<String>, //carvers: Vec<String>,
features: Vec<Vec<String>>, features: Vec<Vec<String>>,
creature_spawn_probability: Option<f32>,
spawners: SpawnGroups,
id: u8, id: u8,
} }
#[derive(Deserialize, PartialEq, Eq, Hash)]
struct SpawnGroups {
monster: Vec<Spawner>,
ambient: Vec<Spawner>,
axolotls: Vec<Spawner>,
creature: Vec<Spawner>,
misc: Vec<Spawner>,
underground_water_creature: Vec<Spawner>,
water_ambient: Vec<Spawner>,
water_creature: Vec<Spawner>,
}
#[derive(Deserialize, Hash, PartialEq, Eq)]
struct Spawner {
r#type: String,
}
impl Spawner {
pub fn to_tokens(&self) -> TokenStream {
let r#type = &self.r#type;
quote! {
Spawner {
r#type: #r#type,
}
}
}
}
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
enum TemperatureModifier { enum TemperatureModifier {
@ -123,11 +153,70 @@ pub(crate) fn build() -> TokenStream {
let downfall = biome.downfall; let downfall = biome.downfall;
// let carvers = &biome.carvers; // let carvers = &biome.carvers;
let features = &biome.features; let features = &biome.features;
let creature_spawn_probability = &biome.creature_spawn_probability.unwrap_or(0.1);
let temperature_modifier = biome let temperature_modifier = biome
.temperature_modifier .temperature_modifier
.clone() .clone()
.unwrap_or(TemperatureModifier::None); .unwrap_or(TemperatureModifier::None);
let monster: Vec<_> = biome
.spawners
.monster
.iter()
.map(|s| s.to_tokens())
.collect();
let ambient: Vec<_> = biome
.spawners
.ambient
.iter()
.map(|s| s.to_tokens())
.collect();
let axolotls: Vec<_> = biome
.spawners
.axolotls
.iter()
.map(|s| s.to_tokens())
.collect();
let creature: Vec<_> = biome
.spawners
.creature
.iter()
.map(|s| s.to_tokens())
.collect();
let misc: Vec<_> = biome.spawners.misc.iter().map(|s| s.to_tokens()).collect();
let underground_water_creature: Vec<_> = biome
.spawners
.underground_water_creature
.iter()
.map(|s| s.to_tokens())
.collect();
let water_ambient: Vec<_> = biome
.spawners
.water_ambient
.iter()
.map(|s| s.to_tokens())
.collect();
let water_creature: Vec<_> = biome
.spawners
.water_creature
.iter()
.map(|s| s.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 temperature_modifier = match temperature_modifier { let temperature_modifier = match temperature_modifier {
TemperatureModifier::Frozen => quote! { TemperatureModifier::Frozen }, TemperatureModifier::Frozen => quote! { TemperatureModifier::Frozen },
TemperatureModifier::None => quote! { TemperatureModifier::None }, TemperatureModifier::None => quote! { TemperatureModifier::None },
@ -144,7 +233,9 @@ pub(crate) fn build() -> TokenStream {
#temperature_modifier, #temperature_modifier,
#downfall #downfall
), ),
features: &[#(&[#(#features),*]),*] features: &[#(&[#(#features),*]),*],
creature_spawn_probability: #creature_spawn_probability,
spawners: #spawners,
}; };
}]); }]);
@ -154,8 +245,8 @@ pub(crate) fn build() -> TokenStream {
let overworld_tree = biome_trees.overworld.into_token_stream(); let overworld_tree = biome_trees.overworld.into_token_stream();
let nether_tree = biome_trees.nether.into_token_stream(); let nether_tree = biome_trees.nether.into_token_stream();
quote! { quote! {
use pumpkin_util::biome::{TemperatureModifier, Weather}; use pumpkin_util::biome::{TemperatureModifier, Weather};
use serde::{Deserializer, de}; use serde::{Deserializer, de};
use crate::biome::de::Deserialize; use crate::biome::de::Deserialize;
@ -167,7 +258,26 @@ pub(crate) fn build() -> TokenStream {
pub registry_id: &'static str, pub registry_id: &'static str,
pub weather: Weather, pub weather: Weather,
// carvers: &'static [&str], // carvers: &'static [&str],
pub features: &'static [&'static [&'static str]] pub features: &'static [&'static [&'static str]],
pub creature_spawn_probability: f32,
pub spawners: SpawnGroups,
}
#[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,
} }
impl PartialEq for Biome { impl PartialEq for Biome {

View File

@ -1368,8 +1368,8 @@ pub(crate) fn build() -> TokenStream {
]; ];
pub fn get_block(registry_id: &str) -> Option<Block> { pub fn get_block(registry_id: &str) -> Option<Block> {
let key = registry_id.replace("minecraft:", ""); let key = registry_id.strip_prefix("minecraft:").unwrap_or(registry_id);
Block::from_registry_key(key.as_str()) Block::from_registry_key(key)
} }
pub fn get_block_by_id(id: u16) -> Option<Block> { pub fn get_block_by_id(id: u16) -> Option<Block> {

View File

@ -1,6 +1,7 @@
use std::{collections::HashMap, fs}; use std::{collections::HashMap, fs};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use pumpkin_util::HeightMap;
use quote::{ToTokens, format_ident, quote}; use quote::{ToTokens, format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use syn::LitInt; use syn::LitInt;
@ -14,6 +15,22 @@ pub struct EntityType {
pub fire_immune: bool, pub fire_immune: bool,
pub dimension: [f32; 2], pub dimension: [f32; 2],
pub eye_height: f32, pub eye_height: f32,
pub spawn_restriction: SpawnRestriction,
}
#[derive(Deserialize)]
pub struct SpawnRestriction {
location: SpawnLocation,
heightmap: HeightMap,
}
#[derive(Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SpawnLocation {
InLava,
InWater,
OnGround,
Unrestricted,
} }
pub struct NamedEntityType<'a>(&'a str, &'a EntityType); pub struct NamedEntityType<'a>(&'a str, &'a EntityType);
@ -34,6 +51,27 @@ impl ToTokens for NamedEntityType<'_> {
None => quote! { None }, None => quote! { None },
}; };
let spawn_restriction_location = match entity.spawn_restriction.location {
SpawnLocation::InLava => quote! {SpawnLocation::InLava},
SpawnLocation::InWater => quote! {SpawnLocation::InWater},
SpawnLocation::OnGround => quote! {SpawnLocation::OnGround},
SpawnLocation::Unrestricted => quote! {SpawnLocation::Unrestricted},
};
let spawn_restriction_heightmap = match entity.spawn_restriction.heightmap {
HeightMap::WorldSurfaceWg => quote! { HeightMap::WorldSurfaceWg },
HeightMap::WorldSurface => quote! { HeightMap::WorldSurface },
HeightMap::OceanFloorWg => quote! { HeightMap::OceanFloorWg },
HeightMap::OceanFloor => quote! { HeightMap::OceanFloor },
HeightMap::MotionBlocking => quote! { HeightMap::MotionBlocking },
HeightMap::MotionBlockingNoLeaves => quote! { HeightMap::MotionBlockingNoLeaves },
};
let spawn_restriction = quote! { SpawnRestriction {
location: #spawn_restriction_location,
heightmap: #spawn_restriction_heightmap,
}};
let summonable = entity.summonable; let summonable = entity.summonable;
let fire_immune = entity.fire_immune; let fire_immune = entity.fire_immune;
let eye_height = entity.eye_height; let eye_height = entity.eye_height;
@ -50,6 +88,7 @@ impl ToTokens for NamedEntityType<'_> {
fire_immune: #fire_immune, fire_immune: #fire_immune,
dimension: [#dimension0, #dimension1], // Correctly construct the array dimension: [#dimension0, #dimension1], // Correctly construct the array
eye_height: #eye_height, eye_height: #eye_height,
spawn_restriction: #spawn_restriction,
resource_name: #name, resource_name: #name,
} }
}); });
@ -87,6 +126,8 @@ pub(crate) fn build() -> TokenStream {
}); });
} }
quote! { quote! {
use pumpkin_util::HeightMap;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct EntityType { pub struct EntityType {
pub id: u16, pub id: u16,
@ -96,9 +137,24 @@ pub(crate) fn build() -> TokenStream {
pub fire_immune: bool, pub fire_immune: bool,
pub dimension: [f32; 2], pub dimension: [f32; 2],
pub eye_height: f32, pub eye_height: f32,
pub spawn_restriction: SpawnRestriction,
pub resource_name: &'static str, pub resource_name: &'static str,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpawnRestriction {
location: SpawnLocation,
heightmap: HeightMap,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SpawnLocation {
InLava,
InWater,
OnGround,
Unrestricted
}
impl EntityType { impl EntityType {
#consts #consts

View File

@ -18,6 +18,17 @@ pub mod serde_enum_as_integer;
pub mod text; pub mod text;
pub mod translation; pub mod translation;
#[derive(Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HeightMap {
WorldSurfaceWg,
WorldSurface,
OceanFloorWg,
OceanFloor,
MotionBlocking,
MotionBlockingNoLeaves,
}
#[macro_export] #[macro_export]
macro_rules! global_path { macro_rules! global_path {
($path:expr) => {{ ($path:expr) => {{

View File

@ -144,7 +144,9 @@ impl ChunkData {
delay: tick.delay as u16, delay: tick.delay as u16,
priority: TickPriority::from(tick.priority), priority: TickPriority::from(tick.priority),
target_block_id: Block::from_registry_key( target_block_id: Block::from_registry_key(
&tick.target_block.replace("minecraft:", ""), tick.target_block
.strip_prefix("minecraft:")
.unwrap_or(&tick.target_block),
) )
.unwrap_or(Block::AIR) .unwrap_or(Block::AIR)
.id, .id,
@ -158,7 +160,9 @@ impl ChunkData {
delay: tick.delay as u16, delay: tick.delay as u16,
priority: TickPriority::from(tick.priority), priority: TickPriority::from(tick.priority),
target_block_id: Block::from_registry_key( target_block_id: Block::from_registry_key(
&tick.target_block.replace("minecraft:", ""), tick.target_block
.strip_prefix("minecraft:")
.unwrap_or(&tick.target_block),
) )
.unwrap_or(Block::AIR) .unwrap_or(Block::AIR)
.id, .id,

View File

@ -84,11 +84,11 @@ impl MatchingBlocksBlockPredicate {
let block = self.offset.get_block(chunk, pos); let block = self.offset.get_block(chunk, pos);
match &self.blocks { match &self.blocks {
MatchingBlocksWrapper::Single(single_block) => { MatchingBlocksWrapper::Single(single_block) => {
single_block.replace("minecraft:", "") == block.name single_block.strip_prefix("minecraft:").unwrap() == block.name
} }
MatchingBlocksWrapper::Multiple(blocks) => blocks MatchingBlocksWrapper::Multiple(blocks) => blocks
.iter() .iter()
.map(|s| s.replace("minecraft:", "")) .map(|s| s.strip_prefix("minecraft:").unwrap())
.contains(block.name), .contains(block.name),
} }
} }

View File

@ -1,5 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
use pumpkin_util::HeightMap;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::iter; use std::iter;
@ -13,12 +14,12 @@ use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::{RandomGenerator, RandomImpl}; use pumpkin_util::random::{RandomGenerator, RandomImpl};
use crate::ProtoChunk;
use crate::generation::block_predicate::BlockPredicate; use crate::generation::block_predicate::BlockPredicate;
use crate::generation::height_limit::HeightLimitView; use crate::generation::height_limit::HeightLimitView;
use crate::generation::height_provider::HeightProvider; use crate::generation::height_provider::HeightProvider;
use crate::level::Level; use crate::level::Level;
use crate::world::BlockRegistryExt; use crate::world::BlockRegistryExt;
use crate::{HeightMap, ProtoChunk};
use super::configured_features::{CONFIGURED_FEATURES, ConfiguredFeature}; use super::configured_features::{CONFIGURED_FEATURES, ConfiguredFeature};

View File

@ -12,12 +12,12 @@ use pumpkin_data::{
}; };
use pumpkin_macros::default_block_state; use pumpkin_macros::default_block_state;
use pumpkin_util::{ use pumpkin_util::{
HeightMap,
math::{position::BlockPos, vector2::Vector2, vector3::Vector3}, math::{position::BlockPos, vector2::Vector2, vector3::Vector3},
random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro}, random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro},
}; };
use crate::{ use crate::{
HeightMap,
biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier, hash_seed}, biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier, hash_seed},
block::RawBlockState, block::RawBlockState,
chunk::CHUNK_AREA, chunk::CHUNK_AREA,
@ -547,6 +547,24 @@ impl<'a> ProtoChunk<'a> {
} }
} }
pub fn generate_entities(&self) {
let start_x = self.start_block_x();
let start_z = self.start_block_z();
let population_seed =
Xoroshiro::get_population_seed(self.random_config.seed, start_x, start_z);
let _random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(population_seed));
let _biome = self.get_biome(&Vector3::new(
start_x,
self.bottom_section_coord() as i32 + self.height() as i32 - 1,
start_z,
));
// while random.next_f32() < biome.creature_spawn_probability {
// }
todo!()
}
pub fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3<i32>) -> &'static Biome { pub fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3<i32>) -> &'static Biome {
let seed_biome_pos = biome::get_biome_blend( let seed_biome_pos = biome::get_biome_blend(
self.bottom_y(), self.bottom_y(),

View File

@ -327,7 +327,7 @@ impl NoiseThresholdMaterialCondition {
pub fn test(&self, context: &mut MaterialRuleContext) -> bool { pub fn test(&self, context: &mut MaterialRuleContext) -> bool {
let sampler = context let sampler = context
.noise_builder .noise_builder
.get_noise_sampler_for_id(&self.noise.replace("minecraft:", "")); .get_noise_sampler_for_id(self.noise.strip_prefix("minecraft:").unwrap());
let value = sampler.sample(context.block_pos.x as f64, 0.0, context.block_pos.z as f64); let value = sampler.sample(context.block_pos.x as f64, 0.0, context.block_pos.z as f64);
value >= self.min_threshold && value <= self.max_threshold value >= self.min_threshold && value <= self.max_threshold
} }

View File

@ -20,17 +20,6 @@ pub mod world_info;
pub type BlockId = u16; pub type BlockId = u16;
pub type BlockStateId = u16; pub type BlockStateId = u16;
#[derive(Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HeightMap {
WorldSurfaceWg,
WorldSurface,
OceanFloorWg,
OceanFloor,
MotionBlocking,
MotionBlockingNoLeaves,
}
#[macro_export] #[macro_export]
macro_rules! global_path { macro_rules! global_path {
($path:expr) => {{ ($path:expr) => {{
@ -60,8 +49,6 @@ pub use generation::{
GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters, GlobalRandomConfig, noise_router::proto_noise_router::ProtoNoiseRouters,
proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting, proto_chunk::ProtoChunk, settings::GENERATION_SETTINGS, settings::GeneratorSetting,
}; };
use serde::Deserialize;
pub fn bench_create_and_populate_noise( pub fn bench_create_and_populate_noise(
base_router: &ProtoNoiseRouters, base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig, random_config: &GlobalRandomConfig,

View File

@ -114,7 +114,7 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes {
match self { match self {
Self::Empty => Vec::new(), Self::Empty => Vec::new(),
Self::Item(item_entry) => { Self::Item(item_entry) => {
let key = &item_entry.name.replace("minecraft:", ""); let key = &item_entry.name.strip_prefix("minecraft:").unwrap();
vec![ItemStack::new(1, Item::from_registry_key(key).unwrap())] vec![ItemStack::new(1, Item::from_registry_key(key).unwrap())]
} }
Self::LootTable => todo!(), Self::LootTable => todo!(),

View File

@ -38,7 +38,7 @@ impl ArgumentConsumer for EffectTypeArgumentConsumer {
let name = args.pop()?; let name = args.pop()?;
// Create a static damage type first // Create a static damage type first
let damage_type = EffectType::from_name(&name.replace("minecraft:", ""))?; let damage_type = EffectType::from_name(name.strip_prefix("minecraft:").unwrap_or(name))?;
// Find matching static damage type from values array // Find matching static damage type from values array
Some(Arg::Effect(damage_type)) Some(Arg::Effect(damage_type))
} }

View File

@ -58,7 +58,8 @@ impl<'a> FindArg<'a> for ItemArgumentConsumer {
fn find_arg(args: &'a ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> { fn find_arg(args: &'a ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
match args.get(name) { match args.get(name) {
Some(Arg::Item(name)) => Item::from_registry_key(&name.replace("minecraft:", "")) Some(Arg::Item(name)) => {
Item::from_registry_key(name.strip_prefix("minecraft:").unwrap_or(name))
.map_or_else( .map_or_else(
|| { || {
Err(CommandError::GeneralCommandIssue(format!( Err(CommandError::GeneralCommandIssue(format!(
@ -66,7 +67,8 @@ impl<'a> FindArg<'a> for ItemArgumentConsumer {
))) )))
}, },
|item| Ok((*name, item)), |item| Ok((*name, item)),
), )
}
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
} }
} }

View File

@ -38,7 +38,7 @@ impl ArgumentConsumer for ParticleArgumentConsumer {
let name = args.pop()?; let name = args.pop()?;
// Create a static damage type first // Create a static damage type first
let particle = Particle::from_name(&name.replace("minecraft:", ""))?; let particle = Particle::from_name(name.strip_prefix("minecraft:").unwrap_or(name))?;
// Find matching static damage type from values array // Find matching static damage type from values array
Some(Arg::Particle(particle)) Some(Arg::Particle(particle))
} }

View File

@ -57,15 +57,16 @@ impl<'a> FindArg<'a> for SoundArgumentConsumer {
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> { fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
match args.get(name) { match args.get(name) {
Some(Arg::Block(name)) => Sound::from_name(&name.replace("minecraft:", "")) Some(Arg::Block(name)) => {
.map_or_else( Sound::from_name(name.strip_prefix("minecraft:").unwrap_or(name)).map_or_else(
|| { || {
Err(CommandError::GeneralCommandIssue(format!( Err(CommandError::GeneralCommandIssue(format!(
"Sound {name} does not exist." "Sound {name} does not exist."
))) )))
}, },
Result::Ok, Result::Ok,
), )
}
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
} }
} }

View File

@ -57,15 +57,16 @@ impl<'a> FindArg<'a> for SummonableEntitiesArgumentConsumer {
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> { fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
match args.get(name) { match args.get(name) {
Some(Arg::Block(name)) => EntityType::from_name(&name.replace("minecraft:", "")) Some(Arg::Block(name)) => {
.map_or_else( EntityType::from_name(name.strip_prefix("minecraft:").unwrap_or(name)).map_or_else(
|| { || {
Err(CommandError::GeneralCommandIssue(format!( Err(CommandError::GeneralCommandIssue(format!(
"Entity {name} does not exist." "Entity {name} does not exist."
))) )))
}, },
Result::Ok, Result::Ok,
), )
}
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
} }
} }