feat: inital mob spawn rules

This commit is contained in:
Alexander Medvedev 2026-05-10 11:09:38 +02:00
parent c23c7c429d
commit 2bfe2335f0
55 changed files with 595 additions and 244 deletions

View File

@ -23,9 +23,7 @@ pub fn build() -> TokenStream {
let replaceable_str = config["replaceable"].as_str().unwrap_or(""); let replaceable_str = config["replaceable"].as_str().unwrap_or("");
let replaceable = if let Some(tag_name) = replaceable_str.strip_prefix('#') { let replaceable = if let Some(tag_name) = replaceable_str.strip_prefix('#') {
let tag_name = tag_name let tag_name = tag_name.to_uppercase().replace([':', '.', '-'], "_");
.to_uppercase()
.replace([':', '.', '-'], "_");
let tag_ident = format_ident!("{}", tag_name); let tag_ident = format_ident!("{}", tag_name);
quote! { crate::tag::Block::#tag_ident } quote! { crate::tag::Block::#tag_ident }
} else { } else {
@ -70,10 +68,12 @@ pub fn build() -> TokenStream {
let width_smoothness = shape["width_smoothness"].as_i64().unwrap_or(0) as i32; let width_smoothness = shape["width_smoothness"].as_i64().unwrap_or(0) as i32;
let horizontal_radius_factor = let horizontal_radius_factor =
value_to_float_provider(&shape["horizontal_radius_factor"]); value_to_float_provider(&shape["horizontal_radius_factor"]);
let vertical_radius_default_factor = let vertical_radius_default_factor = shape["vertical_radius_default_factor"]
shape["vertical_radius_default_factor"].as_f64().unwrap_or(0.0) as f32; .as_f64()
let vertical_radius_center_factor = .unwrap_or(0.0) as f32;
shape["vertical_radius_center_factor"].as_f64().unwrap_or(0.0) as f32; let vertical_radius_center_factor = shape["vertical_radius_center_factor"]
.as_f64()
.unwrap_or(0.0) as f32;
quote! { quote! {
CarverAdditionalConfig::Canyon(CanyonCarverConfig { CarverAdditionalConfig::Canyon(CanyonCarverConfig {

View File

@ -641,10 +641,10 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
let root_radius = config["root_radius"].as_i64().unwrap_or(0) as i32; let root_radius = config["root_radius"].as_i64().unwrap_or(0) as i32;
let root_replaceable = value_to_block_predicate(&config["root_replaceable"]); let root_replaceable = value_to_block_predicate(&config["root_replaceable"]);
let root_state_provider = value_to_block_state_provider(&config["root_state_provider"]); let root_state_provider = value_to_block_state_provider(&config["root_state_provider"]);
let root_placement_attempts = config["root_placement_attempts"].as_i64().unwrap_or(0) let root_placement_attempts =
as i32; config["root_placement_attempts"].as_i64().unwrap_or(0) as i32;
let root_column_max_height = config["root_column_max_height"].as_i64().unwrap_or(0) let root_column_max_height =
as i32; config["root_column_max_height"].as_i64().unwrap_or(0) as i32;
let hanging_root_radius = config["hanging_root_radius"].as_i64().unwrap_or(0) as i32; let hanging_root_radius = config["hanging_root_radius"].as_i64().unwrap_or(0) as i32;
let hanging_roots_vertical_span = config["hanging_roots_vertical_span"] let hanging_roots_vertical_span = config["hanging_roots_vertical_span"]
.as_i64() .as_i64()
@ -1108,8 +1108,9 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream {
} }
"minecraft:upwards_branching_trunk_placer" => { "minecraft:upwards_branching_trunk_placer" => {
let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]); let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]);
let place_branch_per_log_probability = let place_branch_per_log_probability = v["place_branch_per_log_probability"]
v["place_branch_per_log_probability"].as_f64().unwrap_or(0.0) as f32; .as_f64()
.unwrap_or(0.0) as f32;
let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]); let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]);
let can_grow_through = value_to_block_list(&v["can_grow_through"]); let can_grow_through = value_to_block_list(&v["can_grow_through"]);
quote! { quote! {
@ -1127,7 +1128,8 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream {
let branch_start_offset_v = &v["branch_start_offset_from_top"]; let branch_start_offset_v = &v["branch_start_offset_from_top"];
let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32; let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32;
let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32; let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32;
let branch_end_offset_from_top = value_to_int_provider(&v["branch_end_offset_from_top"]); let branch_end_offset_from_top =
value_to_int_provider(&v["branch_end_offset_from_top"]);
quote! { quote! {
TrunkType::Cherry(CherryTrunkPlacer { TrunkType::Cherry(CherryTrunkPlacer {
branch_count: #branch_count, branch_count: #branch_count,

View File

@ -1,5 +1,6 @@
use std::{collections::BTreeMap, fs}; use std::{collections::BTreeMap, fs};
use crate::placed_feature::value_to_int_provider;
use heck::ToShoutySnakeCase; use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -36,6 +37,8 @@ pub struct Dimension {
pub logical_height: i32, pub logical_height: i32,
/// Tag key for blocks that act as infinite burn sources (e.g. `"minecraft:infiniburn_overworld"`). /// Tag key for blocks that act as infinite burn sources (e.g. `"minecraft:infiniburn_overworld"`).
pub infiniburn: String, pub infiniburn: String,
pub monster_spawn_light_level: serde_json::Value,
pub monster_spawn_block_light_limit: u8,
/// Fixed day-time value in this dimension, or `None` if time progresses normally. /// Fixed day-time value in this dimension, or `None` if time progresses normally.
#[serde(rename = "fixed_time")] #[serde(rename = "fixed_time")]
pub fixed_time: Option<i64>, pub fixed_time: Option<i64>,
@ -47,26 +50,6 @@ pub struct Dimension {
pub timelines: Option<String>, pub timelines: Option<String>,
} }
// #[derive(Clone, PartialEq, Deserialize)]
// #[serde(untagged)]
// pub enum MonsterSpawnLightLevel {
// Int(i32),
// Tagged(MonsterSpawnLightLevelTagged),
// }
// #[derive(Clone, PartialEq, Deserialize)]
// pub struct MonsterSpawnLightLevelTagged {
// min_inclusive: i32,
// max_inclusive: i32,
// r#type: String,
// }
// impl From<i32> for MonsterSpawnLightLevel {
// fn from(value: i32) -> Self {
// Self::Int(value)
// }
// }
/// Generates the `TokenStream` for the `Dimension` struct, its constants, and `from_name` lookup. /// Generates the `TokenStream` for the `Dimension` struct, its constants, and `from_name` lookup.
pub fn build() -> TokenStream { pub fn build() -> TokenStream {
let dimensions: BTreeMap<String, Dimension> = serde_json::from_str( let dimensions: BTreeMap<String, Dimension> = serde_json::from_str(
@ -113,6 +96,9 @@ pub fn build() -> TokenStream {
quote! { None } quote! { None }
}; };
let monster_spawn_light_level = value_to_int_provider(&dim.monster_spawn_light_level);
let monster_spawn_block_light_limit = dim.monster_spawn_block_light_limit;
let ambient_light = dim.ambient_light; let ambient_light = dim.ambient_light;
let coordinate_scale = dim.coordinate_scale; let coordinate_scale = dim.coordinate_scale;
let height = dim.height; let height = dim.height;
@ -174,6 +160,8 @@ pub fn build() -> TokenStream {
logical_height: #logical_height, logical_height: #logical_height,
infiniburn: #infiniburn, infiniburn: #infiniburn,
ambient_light: #ambient_light, ambient_light: #ambient_light,
monster_spawn_light_level: #monster_spawn_light_level,
monster_spawn_block_light_limit: #monster_spawn_block_light_limit,
sky_color: #sky_color_literal, sky_color: #sky_color_literal,
fog_color: #fog_color_literal, fog_color: #fog_color_literal,
cloud_color: #cloud_color_literal, cloud_color: #cloud_color_literal,
@ -187,7 +175,13 @@ pub fn build() -> TokenStream {
} }
quote!( quote!(
#[derive(Debug, Clone, Copy)] use pumpkin_util::math::int_provider::{
BiasedToBottomIntProvider, ClampedIntProvider, TrapezoidIntProvider, ClampedNormalIntProvider,
ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
WeightedEntry, WeightedListIntProvider,
};
#[derive(Debug, Clone)]
pub struct Dimension { pub struct Dimension {
pub id: u8, pub id: u8,
pub minecraft_name: &'static str, pub minecraft_name: &'static str,
@ -200,6 +194,8 @@ pub fn build() -> TokenStream {
pub logical_height: i32, pub logical_height: i32,
pub infiniburn: &'static str, pub infiniburn: &'static str,
pub ambient_light: f32, pub ambient_light: f32,
pub monster_spawn_light_level: IntProvider,
pub monster_spawn_block_light_limit: u8,
pub sky_color: Option<i32>, pub sky_color: Option<i32>,
pub fog_color: Option<i32>, pub fog_color: Option<i32>,
pub cloud_color: Option<i32>, pub cloud_color: Option<i32>,

View File

@ -121,7 +121,12 @@ pub fn build() -> TokenStream {
let slots = enchantment.slots; let slots = enchantment.slots;
let slots = slots.iter().map(AttributeModifierSlot::to_tokens); let slots = slots.iter().map(AttributeModifierSlot::to_tokens);
let Translate { translate, bedrock_translate: _, with: _ } = &*enchantment.description.0.content else { let Translate {
translate,
bedrock_translate: _,
with: _,
} = &*enchantment.description.0.content
else {
panic!() panic!()
}; };
let translate = translate.to_string(); let translate = translate.to_string();

View File

@ -13,7 +13,10 @@ pub fn build() -> TokenStream {
let mut match_variants = TokenStream::new(); let mut match_variants = TokenStream::new();
for (i, (raw_name, parameter)) in json.iter().enumerate() { for (i, (raw_name, parameter)) in json.iter().enumerate() {
let name = raw_name.strip_prefix("minecraft:").unwrap().replace("/", "_"); let name = raw_name
.strip_prefix("minecraft:")
.unwrap()
.replace("/", "_");
let name_ident = format_ident!("{}", name.to_uppercase()); let name_ident = format_ident!("{}", name.to_uppercase());
// 1. MD5 Pre-calculation // 1. MD5 Pre-calculation

View File

@ -148,7 +148,6 @@ impl ParsedMappings {
for (mapped_id, id) in (shifts_to[index]..).zip(*from..to) { for (mapped_id, id) in (shifts_to[index]..).zip(*from..to) {
result[id as usize] = mapped_id; result[id as usize] = mapped_id;
} }
} }
result result

View File

@ -22,7 +22,8 @@ pub fn build() -> TokenStream {
}); });
} }
let bedrock_content = fs::read_to_string("../assets/en_us_bedrock.lang").expect("en_us_bedrock is missing"); let bedrock_content =
fs::read_to_string("../assets/en_us_bedrock.lang").expect("en_us_bedrock is missing");
let mut bedrock_constants = TokenStream::new(); let mut bedrock_constants = TokenStream::new();
for line in bedrock_content.lines() { for line in bedrock_content.lines() {

View File

@ -70,7 +70,12 @@ pub fn build() -> TokenStream {
let format_trade = |trade: &TradeJson| { let format_trade = |trade: &TradeJson| {
let wants_item = format_ident!( let wants_item = format_ident!(
"{}", "{}",
trade.wants.id.strip_prefix("minecraft:").unwrap_or(&trade.wants.id).to_shouty_snake_case() trade
.wants
.id
.strip_prefix("minecraft:")
.unwrap_or(&trade.wants.id)
.to_shouty_snake_case()
); );
let wants_count = trade.wants.count.unwrap_or(1.0) as i32; let wants_count = trade.wants.count.unwrap_or(1.0) as i32;
let wants = quote! { VillagerTradeItem { item: &crate::item::Item::#wants_item, count: #wants_count } }; let wants = quote! { VillagerTradeItem { item: &crate::item::Item::#wants_item, count: #wants_count } };
@ -78,7 +83,9 @@ pub fn build() -> TokenStream {
let wants_b = if let Some(b) = &trade.wants_b { let wants_b = if let Some(b) = &trade.wants_b {
let item = format_ident!( let item = format_ident!(
"{}", "{}",
b.id.strip_prefix("minecraft:").unwrap_or(&b.id).to_shouty_snake_case() b.id.strip_prefix("minecraft:")
.unwrap_or(&b.id)
.to_shouty_snake_case()
); );
let count = b.count.unwrap_or(1.0) as i32; let count = b.count.unwrap_or(1.0) as i32;
quote! { Some(VillagerTradeItem { item: &crate::item::Item::#item, count: #count }) } quote! { Some(VillagerTradeItem { item: &crate::item::Item::#item, count: #count }) }
@ -88,7 +95,12 @@ pub fn build() -> TokenStream {
let gives_item = format_ident!( let gives_item = format_ident!(
"{}", "{}",
trade.gives.id.strip_prefix("minecraft:").unwrap_or(&trade.gives.id).to_shouty_snake_case() trade
.gives
.id
.strip_prefix("minecraft:")
.unwrap_or(&trade.gives.id)
.to_shouty_snake_case()
); );
let gives_count = trade.gives.count.unwrap_or(1.0) as i32; let gives_count = trade.gives.count.unwrap_or(1.0) as i32;
let gives = quote! { VillagerTradeItem { item: &crate::item::Item::#gives_item, count: #gives_count } }; let gives = quote! { VillagerTradeItem { item: &crate::item::Item::#gives_item, count: #gives_count } };
@ -132,7 +144,9 @@ pub fn build() -> TokenStream {
} }
// Fallback for smiths // Fallback for smiths
if matching_trades.is_empty() && (prof == "armorer" || prof == "toolsmith" || prof == "weaponsmith") { if matching_trades.is_empty()
&& (prof == "armorer" || prof == "toolsmith" || prof == "weaponsmith")
{
let smith_prefix = format!("smith/{level_str}/"); let smith_prefix = format!("smith/{level_str}/");
for (key, trade) in &data.villager_trades { for (key, trade) in &data.villager_trades {
if key.starts_with(&smith_prefix) { if key.starts_with(&smith_prefix) {
@ -199,7 +213,11 @@ pub fn build() -> TokenStream {
for (level_str, set_key) in &prof_data.trade_sets { for (level_str, set_key) in &prof_data.trade_sets {
let level = level_str.parse::<i32>().unwrap(); let level = level_str.parse::<i32>().unwrap();
let set_key_clean = set_key.strip_prefix("minecraft:").unwrap_or(set_key); let set_key_clean = set_key.strip_prefix("minecraft:").unwrap_or(set_key);
if let Some(trades_ident) = data.trade_sets.get(set_key_clean).and_then(|set| generated_trade_sets.get(&set.trades)) { if let Some(trades_ident) = data
.trade_sets
.get(set_key_clean)
.and_then(|set| generated_trade_sets.get(&set.trades))
{
let set = data.trade_sets.get(set_key_clean).unwrap(); let set = data.trade_sets.get(set_key_clean).unwrap();
let amount = set.amount as i32; let amount = set.amount as i32;
level_matches.push(quote! { #level => Some(VillagerTradeSet { trades: #trades_ident, amount: #amount }) }); level_matches.push(quote! { #level => Some(VillagerTradeSet { trades: #trades_ident, amount: #amount }) });

View File

@ -1,24 +1,45 @@
use crate::wit::utils::map_type; use crate::wit::utils::map_type;
use heck::ToKebabCase;
use semver::Version;
use std::{fs, path::Path}; use std::{fs, path::Path};
use syn::{Fields, Item}; use syn::{Fields, Item};
use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase}; use wit_encoder::{
use semver::Version; Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant,
use heck::ToKebabCase; VariantCase,
};
pub fn build() -> String { pub fn build() -> String {
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0)))); let mut package = Package::new(PackageName::new(
"pumpkin",
"plugin",
Some(Version::new(0, 1, 0)),
));
let mut interface = Interface::new("bedrock-packets"); let mut interface = Interface::new("bedrock-packets");
let mut serverbound_variant = Variant::empty(); let mut serverbound_variant = Variant::empty();
let mut clientbound_variant = Variant::empty(); let mut clientbound_variant = Variant::empty();
// Process serverbound packets // Process serverbound packets
process_packets("../pumpkin-protocol/src/bedrock/server", &mut interface, &mut serverbound_variant); process_packets(
"../pumpkin-protocol/src/bedrock/server",
&mut interface,
&mut serverbound_variant,
);
// Process clientbound packets // Process clientbound packets
process_packets("../pumpkin-protocol/src/bedrock/client", &mut interface, &mut clientbound_variant); process_packets(
"../pumpkin-protocol/src/bedrock/client",
&mut interface,
&mut clientbound_variant,
);
interface.type_def(TypeDef::new("serverbound-packet", TypeDefKind::Variant(serverbound_variant))); interface.type_def(TypeDef::new(
interface.type_def(TypeDef::new("clientbound-packet", TypeDefKind::Variant(clientbound_variant))); "serverbound-packet",
TypeDefKind::Variant(serverbound_variant),
));
interface.type_def(TypeDef::new(
"clientbound-packet",
TypeDefKind::Variant(clientbound_variant),
));
package.interface(interface); package.interface(interface);
package.to_string() package.to_string()
@ -26,7 +47,9 @@ pub fn build() -> String {
fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) { fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory"); let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect(); let mut sorted_paths: Vec<_> = paths
.map(|e| e.expect("Failed to read entry").path())
.collect();
sorted_paths.sort(); sorted_paths.sort();
for path in sorted_paths { for path in sorted_paths {
@ -34,7 +57,9 @@ fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant)
process_packets(path.to_str().unwrap(), interface, variant); process_packets(path.to_str().unwrap(), interface, variant);
continue; continue;
} }
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") { if path.extension().is_some_and(|ext| ext == "rs")
&& path.file_name().is_some_and(|name| name != "mod.rs")
{
parse_packet_file(&path, interface, variant); parse_packet_file(&path, interface, variant);
} }
} }
@ -63,8 +88,14 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia
} }
let wit_name = struct_name.to_kebab_case(); let wit_name = struct_name.to_kebab_case();
interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list)))); interface.type_def(TypeDef::new(
variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name))); wit_name.clone(),
TypeDefKind::Record(Record::new(fields_list)),
));
variant.case(VariantCase::value(
wit_name.clone(),
WitType::named(wit_name),
));
} }
} }
} }

View File

@ -1,6 +1,6 @@
use semver::Version;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs; use std::fs;
use semver::Version;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String { pub fn build() -> String {
@ -8,7 +8,11 @@ pub fn build() -> String {
serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap()) serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap())
.expect("Failed to parse entities.json"); .expect("Failed to parse entities.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0)))); let mut package = Package::new(PackageName::new(
"pumpkin",
"plugin",
Some(Version::new(0, 1, 0)),
));
let mut interface = Interface::new("entity-types"); let mut interface = Interface::new("entity-types");
let mut entity_type_enum = Enum::empty(); let mut entity_type_enum = Enum::empty();
@ -16,7 +20,10 @@ pub fn build() -> String {
entity_type_enum.case(name.replace('_', "-")); entity_type_enum.case(name.replace('_', "-"));
} }
interface.type_def(TypeDef::new("entity-type", TypeDefKind::Enum(entity_type_enum))); interface.type_def(TypeDef::new(
"entity-type",
TypeDefKind::Enum(entity_type_enum),
));
package.interface(interface); package.interface(interface);
package.to_string() package.to_string()

View File

@ -1,24 +1,45 @@
use crate::wit::utils::map_type; use crate::wit::utils::map_type;
use heck::ToKebabCase;
use semver::Version;
use std::{fs, path::Path}; use std::{fs, path::Path};
use syn::{Fields, Item}; use syn::{Fields, Item};
use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase}; use wit_encoder::{
use semver::Version; Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant,
use heck::ToKebabCase; VariantCase,
};
pub fn build() -> String { pub fn build() -> String {
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0)))); let mut package = Package::new(PackageName::new(
"pumpkin",
"plugin",
Some(Version::new(0, 1, 0)),
));
let mut interface = Interface::new("java-packets"); let mut interface = Interface::new("java-packets");
let mut serverbound_variant = Variant::empty(); let mut serverbound_variant = Variant::empty();
let mut clientbound_variant = Variant::empty(); let mut clientbound_variant = Variant::empty();
// Process serverbound packets // Process serverbound packets
process_packets("../pumpkin-protocol/src/java/server/play", &mut interface, &mut serverbound_variant); process_packets(
"../pumpkin-protocol/src/java/server/play",
&mut interface,
&mut serverbound_variant,
);
// Process clientbound packets // Process clientbound packets
process_packets("../pumpkin-protocol/src/java/client/play", &mut interface, &mut clientbound_variant); process_packets(
"../pumpkin-protocol/src/java/client/play",
&mut interface,
&mut clientbound_variant,
);
interface.type_def(TypeDef::new("serverbound-packet", TypeDefKind::Variant(serverbound_variant))); interface.type_def(TypeDef::new(
interface.type_def(TypeDef::new("clientbound-packet", TypeDefKind::Variant(clientbound_variant))); "serverbound-packet",
TypeDefKind::Variant(serverbound_variant),
));
interface.type_def(TypeDef::new(
"clientbound-packet",
TypeDefKind::Variant(clientbound_variant),
));
package.interface(interface); package.interface(interface);
package.to_string() package.to_string()
@ -26,11 +47,15 @@ pub fn build() -> String {
fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) { fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory"); let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect(); let mut sorted_paths: Vec<_> = paths
.map(|e| e.expect("Failed to read entry").path())
.collect();
sorted_paths.sort(); sorted_paths.sort();
for path in sorted_paths { for path in sorted_paths {
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") { if path.extension().is_some_and(|ext| ext == "rs")
&& path.file_name().is_some_and(|name| name != "mod.rs")
{
parse_packet_file(&path, interface, variant); parse_packet_file(&path, interface, variant);
} }
} }
@ -43,7 +68,11 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia
for item in file.items { for item in file.items {
if let Item::Struct(s) = item { if let Item::Struct(s) = item {
// Only process structs with #[java_packet] attribute // Only process structs with #[java_packet] attribute
if !s.attrs.iter().any(|attr| attr.path().is_ident("java_packet")) { if !s
.attrs
.iter()
.any(|attr| attr.path().is_ident("java_packet"))
{
continue; continue;
} }
@ -59,9 +88,14 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia
} }
let wit_name = struct_name.to_kebab_case(); let wit_name = struct_name.to_kebab_case();
interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list)))); interface.type_def(TypeDef::new(
variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name))); wit_name.clone(),
TypeDefKind::Record(Record::new(fields_list)),
));
variant.case(VariantCase::value(
wit_name.clone(),
WitType::named(wit_name),
));
} }
} }
} }

View File

@ -1,6 +1,6 @@
use heck::{ToPascalCase, ToSnakeCase};
use std::{fs, path::Path}; use std::{fs, path::Path};
use syn::{Fields, Item}; use syn::{Fields, Item};
use heck::{ToPascalCase, ToSnakeCase};
pub fn build_java_mapping() -> String { pub fn build_java_mapping() -> String {
let mut output = String::new(); let mut output = String::new();
@ -17,7 +17,14 @@ pub fn build_java_mapping() -> String {
output.push_str("pub fn serialize_java_packet(packet: &ClientboundPacket, version: MinecraftVersion) -> Option<Bytes> {\n"); output.push_str("pub fn serialize_java_packet(packet: &ClientboundPacket, version: MinecraftVersion) -> Option<Bytes> {\n");
output.push_str(" match packet {\n"); output.push_str(" match packet {\n");
process_packets("../pumpkin-protocol/src/java/client/play", &mut output, "java_packet", "ClientboundPacket", "pumpkin_protocol::java::client::play", false); process_packets(
"../pumpkin-protocol/src/java/client/play",
&mut output,
"java_packet",
"ClientboundPacket",
"pumpkin_protocol::java::client::play",
false,
);
output.push_str(" _ => None,\n"); output.push_str(" _ => None,\n");
output.push_str(" }\n"); output.push_str(" }\n");
@ -29,10 +36,19 @@ pub fn build_bedrock_mapping() -> String {
let mut output = String::new(); let mut output = String::new();
output.push_str("\nuse crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::bedrock_packets::ClientboundPacket as BClientboundPacket;\n\n"); output.push_str("\nuse crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::bedrock_packets::ClientboundPacket as BClientboundPacket;\n\n");
output.push_str("#[must_use]\n"); output.push_str("#[must_use]\n");
output.push_str("pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {\n"); output.push_str(
"pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {\n",
);
output.push_str(" match packet {\n"); output.push_str(" match packet {\n");
process_packets("../pumpkin-protocol/src/bedrock/client", &mut output, "packet", "BClientboundPacket", "pumpkin_protocol::bedrock::client", true); process_packets(
"../pumpkin-protocol/src/bedrock/client",
&mut output,
"packet",
"BClientboundPacket",
"pumpkin_protocol::bedrock::client",
true,
);
output.push_str(" _ => None,\n"); output.push_str(" _ => None,\n");
output.push_str(" }\n"); output.push_str(" }\n");
@ -40,9 +56,18 @@ pub fn build_bedrock_mapping() -> String {
output output
} }
fn process_packets(dir: &str, output: &mut String, attr_name: &str, variant_prefix: &str, rust_path_prefix: &str, skip_raknet: bool) { fn process_packets(
dir: &str,
output: &mut String,
attr_name: &str,
variant_prefix: &str,
rust_path_prefix: &str,
skip_raknet: bool,
) {
let paths = fs::read_dir(dir).expect("Failed to read packet directory"); let paths = fs::read_dir(dir).expect("Failed to read packet directory");
let mut sorted_paths: Vec<_> = paths.map(|e| e.expect("Failed to read entry").path()).collect(); let mut sorted_paths: Vec<_> = paths
.map(|e| e.expect("Failed to read entry").path())
.collect();
sorted_paths.sort(); sorted_paths.sort();
for path in sorted_paths { for path in sorted_paths {
@ -50,16 +75,31 @@ fn process_packets(dir: &str, output: &mut String, attr_name: &str, variant_pref
if skip_raknet && path.file_name().unwrap() == "raknet" { if skip_raknet && path.file_name().unwrap() == "raknet" {
continue; continue;
} }
process_packets(path.to_str().unwrap(), output, attr_name, variant_prefix, rust_path_prefix, skip_raknet); process_packets(
path.to_str().unwrap(),
output,
attr_name,
variant_prefix,
rust_path_prefix,
skip_raknet,
);
continue; continue;
} }
if path.extension().is_some_and(|ext| ext == "rs") && path.file_name().is_some_and(|name| name != "mod.rs") { if path.extension().is_some_and(|ext| ext == "rs")
&& path.file_name().is_some_and(|name| name != "mod.rs")
{
parse_packet_file(&path, output, attr_name, variant_prefix, rust_path_prefix); parse_packet_file(&path, output, attr_name, variant_prefix, rust_path_prefix);
} }
} }
} }
fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_prefix: &str, rust_path_prefix: &str) { fn parse_packet_file(
path: &Path,
output: &mut String,
attr_name: &str,
variant_prefix: &str,
rust_path_prefix: &str,
) {
let content = fs::read_to_string(path).expect("Failed to read file"); let content = fs::read_to_string(path).expect("Failed to read file");
let file = syn::parse_file(&content).expect("Failed to parse file"); let file = syn::parse_file(&content).expect("Failed to parse file");
@ -70,7 +110,9 @@ fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_
} }
let struct_name = s.ident.to_string(); let struct_name = s.ident.to_string();
if struct_name == "CHandshake" { continue; } // Skip CHandshake (RakNet) if struct_name == "CHandshake" {
continue;
} // Skip CHandshake (RakNet)
let wit_case = struct_name.to_pascal_case(); let wit_case = struct_name.to_pascal_case();
let mut prep_code = String::new(); let mut prep_code = String::new();
@ -137,9 +179,15 @@ fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_
} }
if possible { if possible {
output.push_str(&format!(" {}::{}(data) => {{\n", variant_prefix, wit_case)); output.push_str(&format!(
" {}::{}(data) => {{\n",
variant_prefix, wit_case
));
output.push_str(&prep_code); output.push_str(&prep_code);
output.push_str(&format!(" let p = {}::{} {{\n", rust_path_prefix, struct_name)); output.push_str(&format!(
" let p = {}::{} {{\n",
rust_path_prefix, struct_name
));
output.push_str(&field_inits); output.push_str(&field_inits);
output.push_str(" };\n"); output.push_str(" };\n");
output.push_str(" let mut buf = Vec::new();\n"); output.push_str(" let mut buf = Vec::new();\n");

View File

@ -1,5 +1,5 @@
use std::fs;
use semver::Version; use semver::Version;
use std::fs;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String { pub fn build() -> String {
@ -7,7 +7,11 @@ pub fn build() -> String {
serde_json::from_str(&fs::read_to_string("../assets/particles.json").unwrap()) serde_json::from_str(&fs::read_to_string("../assets/particles.json").unwrap())
.expect("Failed to parse particles.json"); .expect("Failed to parse particles.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0)))); let mut package = Package::new(PackageName::new(
"pumpkin",
"plugin",
Some(Version::new(0, 1, 0)),
));
let mut interface = Interface::new("particles"); let mut interface = Interface::new("particles");
let mut particle_enum = Enum::empty(); let mut particle_enum = Enum::empty();

View File

@ -1,5 +1,5 @@
use std::fs;
use semver::Version; use semver::Version;
use std::fs;
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
pub fn build() -> String { pub fn build() -> String {
@ -7,7 +7,11 @@ pub fn build() -> String {
serde_json::from_str(&fs::read_to_string("../assets/sounds.json").unwrap()) serde_json::from_str(&fs::read_to_string("../assets/sounds.json").unwrap())
.expect("Failed to parse sounds.json"); .expect("Failed to parse sounds.json");
let mut package = Package::new(PackageName::new("pumpkin", "plugin", Some(Version::new(0, 1, 0)))); let mut package = Package::new(PackageName::new(
"pumpkin",
"plugin",
Some(Version::new(0, 1, 0)),
));
let mut interface = Interface::new("sounds"); let mut interface = Interface::new("sounds");
let mut sound_enum = Enum::empty(); let mut sound_enum = Enum::empty();

View File

@ -1,5 +1,5 @@
use wit_encoder::Type as WitType;
use syn::{Type, TypePath}; use syn::{Type, TypePath};
use wit_encoder::Type as WitType;
pub fn map_type(ty: &Type) -> WitType { pub fn map_type(ty: &Type) -> WitType {
match ty { match ty {
@ -17,14 +17,16 @@ pub fn map_type(ty: &Type) -> WitType {
"u16" | "i16" => WitType::S32, "u16" | "i16" => WitType::S32,
"Option" => { "Option" => {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
{
return WitType::option(map_type(inner_ty)); return WitType::option(map_type(inner_ty));
} }
WitType::String WitType::String
} }
"Vec" | "Box" => { "Vec" | "Box" => {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
{
return WitType::list(map_type(inner_ty)); return WitType::list(map_type(inner_ty));
} }
WitType::String WitType::String

View File

@ -1,5 +1,10 @@
/* This file is generated. Do not edit manually. */ /* This file is generated. Do not edit manually. */
#[derive(Debug, Clone, Copy)] use pumpkin_util::math::int_provider::{
BiasedToBottomIntProvider, ClampedIntProvider, ClampedNormalIntProvider, ConstantIntProvider,
IntProvider, NormalIntProvider, TrapezoidIntProvider, UniformIntProvider, WeightedEntry,
WeightedListIntProvider,
};
#[derive(Debug, Clone)]
pub struct Dimension { pub struct Dimension {
pub id: u8, pub id: u8,
pub minecraft_name: &'static str, pub minecraft_name: &'static str,
@ -12,6 +17,8 @@ pub struct Dimension {
pub logical_height: i32, pub logical_height: i32,
pub infiniburn: &'static str, pub infiniburn: &'static str,
pub ambient_light: f32, pub ambient_light: f32,
pub monster_spawn_light_level: IntProvider,
pub monster_spawn_block_light_limit: u8,
pub sky_color: Option<i32>, pub sky_color: Option<i32>,
pub fog_color: Option<i32>, pub fog_color: Option<i32>,
pub cloud_color: Option<i32>, pub cloud_color: Option<i32>,
@ -30,6 +37,13 @@ impl Dimension {
logical_height: 384i32, logical_height: 384i32,
infiniburn: "#minecraft:infiniburn_overworld", infiniburn: "#minecraft:infiniburn_overworld",
ambient_light: 0f32, ambient_light: 0f32,
monster_spawn_light_level: IntProvider::Object(NormalIntProvider::Uniform(
UniformIntProvider {
min_inclusive: 0i32,
max_inclusive: 7i32,
},
)),
monster_spawn_block_light_limit: 0u8,
sky_color: Some(7907327i32), sky_color: Some(7907327i32),
fog_color: Some(12638463i32), fog_color: Some(12638463i32),
cloud_color: None, cloud_color: None,
@ -47,6 +61,13 @@ impl Dimension {
logical_height: 384i32, logical_height: 384i32,
infiniburn: "#minecraft:infiniburn_overworld", infiniburn: "#minecraft:infiniburn_overworld",
ambient_light: 0f32, ambient_light: 0f32,
monster_spawn_light_level: IntProvider::Object(NormalIntProvider::Uniform(
UniformIntProvider {
min_inclusive: 0i32,
max_inclusive: 7i32,
},
)),
monster_spawn_block_light_limit: 0u8,
sky_color: Some(7907327i32), sky_color: Some(7907327i32),
fog_color: Some(12638463i32), fog_color: Some(12638463i32),
cloud_color: None, cloud_color: None,
@ -64,6 +85,8 @@ impl Dimension {
logical_height: 256i32, logical_height: 256i32,
infiniburn: "#minecraft:infiniburn_end", infiniburn: "#minecraft:infiniburn_end",
ambient_light: 0.25f32, ambient_light: 0.25f32,
monster_spawn_light_level: IntProvider::Constant(15i32),
monster_spawn_block_light_limit: 0u8,
sky_color: Some(0i32), sky_color: Some(0i32),
fog_color: Some(1577752i32), fog_color: Some(1577752i32),
cloud_color: None, cloud_color: None,
@ -81,6 +104,8 @@ impl Dimension {
logical_height: 128i32, logical_height: 128i32,
infiniburn: "#minecraft:infiniburn_nether", infiniburn: "#minecraft:infiniburn_nether",
ambient_light: 0.1f32, ambient_light: 0.1f32,
monster_spawn_light_level: IntProvider::Constant(7i32),
monster_spawn_block_light_limit: 15u8,
sky_color: None, sky_color: None,
fog_color: None, fog_color: None,
cloud_color: None, cloud_color: None,

View File

@ -12,7 +12,7 @@ fn bench_full_chunk_generation(c: &mut Criterion) {
let dimension = Dimension::OVERWORLD; let dimension = Dimension::OVERWORLD;
let seed = Seed(42); let seed = Seed(42);
let block_registry = Arc::new(BlockRegistry); let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension); let world_gen = get_world_gen(seed, dimension.clone());
let biome_mixer_seed = hash_seed(world_gen.random_config.seed); let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
c.bench_function("full_chunk_generation", |b| { c.bench_function("full_chunk_generation", |b| {

View File

@ -110,11 +110,11 @@ struct ChunkBitmap([u8; 128]);
impl ChunkBitmap { impl ChunkBitmap {
const SIZE: usize = 128; const SIZE: usize = 128;
fn new() -> Self { const fn new() -> Self {
Self([0u8; 128]) Self([0u8; 128])
} }
fn set(&mut self, index: usize, exists: bool) { const fn set(&mut self, index: usize, exists: bool) {
let byte = index / 8; let byte = index / 8;
let bit = index % 8; let bit = index % 8;
if exists { if exists {
@ -125,7 +125,7 @@ impl ChunkBitmap {
} }
#[allow(dead_code)] #[allow(dead_code)]
fn get(&self, index: usize) -> bool { const fn get(&self, index: usize) -> bool {
let byte = index / 8; let byte = index / 8;
let bit = index % 8; let bit = index % 8;
(self.0[byte] >> bit) & 1 == 1 (self.0[byte] >> bit) & 1 == 1
@ -315,8 +315,8 @@ impl<S: SingleChunkDataSerializer> LinearV2File<S> {
AnvilChunkFile::<S>::get_chunk_index(x, z) AnvilChunkFile::<S>::get_chunk_index(x, z)
} }
/// Number of buckets in the grid (grid_size²). /// Number of buckets in the grid (`grid_size²`).
fn bucket_count(grid_size: u8) -> usize { const fn bucket_count(grid_size: u8) -> usize {
(grid_size as usize) * (grid_size as usize) (grid_size as usize) * (grid_size as usize)
} }
@ -344,7 +344,7 @@ impl<S: SingleChunkDataSerializer> LinearV2File<S> {
} }
/// Inverse of `chunk_bucket_index` + `chunk_local_index`. /// Inverse of `chunk_bucket_index` + `chunk_local_index`.
fn global_chunk_index(bucket_idx: usize, local: usize, grid_size: u8) -> usize { const fn global_chunk_index(bucket_idx: usize, local: usize, grid_size: u8) -> usize {
let stride = 32 / grid_size as usize; let stride = 32 / grid_size as usize;
let bucket_row = bucket_idx / grid_size as usize; let bucket_row = bucket_idx / grid_size as usize;
let bucket_col = bucket_idx % grid_size as usize; let bucket_col = bucket_idx % grid_size as usize;
@ -510,8 +510,8 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
.map_err(|_| ChunkReadingError::RegionIsInvalid)?; .map_err(|_| ChunkReadingError::RegionIsInvalid)?;
decoder decoder
.read_to_end(&mut decompressed) .read_to_end(&mut decompressed)
.map_err(|e| ChunkReadingError::IoError(e.kind()))?; .map_err(|e| ChunkReadingError::IoError(e.kind()))?
} };
buf.advance(compressed_size); buf.advance(compressed_size);
let mut bucket_buf: Bytes = decompressed.into(); let mut bucket_buf: Bytes = decompressed.into();
@ -548,8 +548,7 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
self.timestamps[index] = SystemTime::now() self.timestamps[index] = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.map(|d| d.as_secs()) .map_or(0, |d| d.as_secs());
.unwrap_or(0);
self.chunks_data[index] = Some(chunk_raw); self.chunks_data[index] = Some(chunk_raw);
Ok(()) Ok(())
} }

View File

@ -377,9 +377,9 @@ where
serializer serializer
.write(&path) .write(&path)
.await .await
.map_err(|e| ChunkWritingError::IoError(e.kind()))?; .map_err(|e| ChunkWritingError::IoError(e.kind()))?
// Read-lock released here. // Read-lock released here.
} };
// Drop our handle so `can_remove` may succeed. // Drop our handle so `can_remove` may succeed.
drop(chunk_serializer); drop(chunk_serializer);

View File

@ -174,6 +174,7 @@ impl TryFrom<usize> for ChunkHeightmapType {
} }
impl ChunkHeightmapType { impl ChunkHeightmapType {
#[must_use]
pub fn is_opaque(&self, block_state: &BlockState) -> bool { pub fn is_opaque(&self, block_state: &BlockState) -> bool {
let block = Block::get_raw_id_from_state_id(block_state.id); let block = Block::get_raw_id_from_state_id(block_state.id);
match self { match self {

View File

@ -694,6 +694,7 @@ impl BlockPalette {
) )
} }
#[must_use]
pub fn to_disk_nbt(&self) -> ChunkSectionBlockStates { pub fn to_disk_nbt(&self) -> ChunkSectionBlockStates {
let bits_per_entry = self.bits_per_entry().max(BLOCK_DISK_MIN_BITS); let bits_per_entry = self.bits_per_entry().max(BLOCK_DISK_MIN_BITS);
let (palette, packed_data) = self.to_palette_and_packed_data(bits_per_entry); let (palette, packed_data) = self.to_palette_and_packed_data(bits_per_entry);

View File

@ -124,7 +124,10 @@ impl LevelChannel {
ret ret
} }
pub fn notify(&self) { pub fn notify(&self) {
let val = self.value.lock().unwrap_or_else(|e| e.into_inner()); let val = self
.value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
drop(val); drop(val);
self.notify.notify_one(); self.notify.notify_one();
} }

View File

@ -330,7 +330,7 @@ impl Chunk {
fluid_ticks: Default::default(), fluid_ticks: Default::default(),
pending_block_entities: Mutex::new(pending_block_entities), pending_block_entities: Mutex::new(pending_block_entities),
status: proto_chunk.stage.into(), status: proto_chunk.stage.into(),
blending_data: proto_chunk.blending_data.clone(), blending_data: proto_chunk.blending_data,
}; };
chunk.heightmap = Mutex::new(chunk.calculate_heightmap()); chunk.heightmap = Mutex::new(chunk.calculate_heightmap());

View File

@ -97,7 +97,7 @@ mod tests {
let dimension = Dimension::OVERWORLD; let dimension = Dimension::OVERWORLD;
let seed = Seed(42); let seed = Seed(42);
let block_registry = Arc::new(BlockRegistry); let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension); let world_gen = get_world_gen(seed, dimension.clone());
let biome_mixer_seed = hash_seed(world_gen.random_config.seed); let biome_mixer_seed = hash_seed(world_gen.random_config.seed);
let _ = generate_single_chunk( let _ = generate_single_chunk(

View File

@ -137,10 +137,7 @@ impl GenerationSchedule {
} }
let max_in_flight = if gen_pool.is_some() { let max_in_flight = if gen_pool.is_some() {
(thread::available_parallelism() (thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16
.map(|n| n.get())
.unwrap_or(1)
* 4) as u16
} else { } else {
gen_thread_count as u16 gen_thread_count as u16
}; };

View File

@ -5,6 +5,7 @@ use pumpkin_data::dimension::Dimension;
use crate::level::Level; use crate::level::Level;
#[must_use]
pub fn into_level( pub fn into_level(
dimension: Dimension, dimension: Dimension,
level_config: &LevelConfig, level_config: &LevelConfig,

View File

@ -14,6 +14,7 @@ pub struct BlendingData {
} }
impl BlendingData { impl BlendingData {
#[must_use]
pub fn get_height(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 { pub fn get_height(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 {
if !(0..16).contains(&cell_x) || !(0..16).contains(&cell_z) { if !(0..16).contains(&cell_x) || !(0..16).contains(&cell_z) {
return f64::MAX; return f64::MAX;
@ -21,6 +22,7 @@ impl BlendingData {
self.heights[(cell_z * 16 + cell_x) as usize] self.heights[(cell_z * 16 + cell_x) as usize]
} }
#[must_use]
pub fn get_density(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 { pub fn get_density(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 {
// cell_y is block_y / 8 in Blender.java // cell_y is block_y / 8 in Blender.java
// We'll need a better storage for this if we want full fidelity // We'll need a better storage for this if we want full fidelity

View File

@ -30,6 +30,7 @@ const DENSITY_BLENDING_RANGE_CELLS: i32 = 2;
const DENSITY_BLENDING_RANGE_CHUNKS: i32 = 5 >> 2; // QuartPos.toSection(5) const DENSITY_BLENDING_RANGE_CHUNKS: i32 = 5 >> 2; // QuartPos.toSection(5)
impl Blender { impl Blender {
#[must_use]
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
height_and_biome_blending_data: FxHashMap::default(), height_and_biome_blending_data: FxHashMap::default(),
@ -79,16 +80,22 @@ impl Blender {
} }
} }
#[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.height_and_biome_blending_data.is_empty() && self.density_blending_data.is_empty() self.height_and_biome_blending_data.is_empty() && self.density_blending_data.is_empty()
} }
#[must_use]
pub fn blend_offset_and_factor(&self, block_x: i32, block_z: i32) -> BlendingOutput { pub fn blend_offset_and_factor(&self, block_x: i32, block_z: i32) -> BlendingOutput {
let cell_x = biome_coords::from_block(block_x); let cell_x = biome_coords::from_block(block_x);
let cell_z = biome_coords::from_block(block_z); let cell_z = biome_coords::from_block(block_z);
let fixed_height = self let fixed_height = self.get_blending_data_value(
.get_blending_data_value(cell_x, 0, cell_z, |data, x, y, z| data.get_height(x, y, z)); cell_x,
0,
cell_z,
blending_data::BlendingData::get_height,
);
if fixed_height != f64::MAX { if fixed_height != f64::MAX {
return BlendingOutput { return BlendingOutput {
@ -111,7 +118,7 @@ impl Blender {
|test_cell_x, test_cell_z, height| { |test_cell_x, test_cell_z, height| {
let dx = (cell_x - test_cell_x) as f64; let dx = (cell_x - test_cell_x) as f64;
let dz = (cell_z - test_cell_z) as f64; let dz = (cell_z - test_cell_z) as f64;
let distance = (dx * dx + dz * dz).sqrt(); let distance = dx.hypot(dz);
if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 { if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 {
if distance < closest_distance { if distance < closest_distance {
@ -151,6 +158,7 @@ impl Blender {
/ (128.0 * (32.0 - 3.0 * target_y_mod)) / (128.0 * (32.0 - 3.0 * target_y_mod))
} }
#[must_use]
pub fn blend_density(&self, block_x: i32, block_y: i32, block_z: i32, noise_value: f64) -> f64 { pub fn blend_density(&self, block_x: i32, block_y: i32, block_z: i32, noise_value: f64) -> f64 {
let cell_x = biome_coords::from_block(block_x); let cell_x = biome_coords::from_block(block_x);
let cell_y = block_y / 8; let cell_y = block_y / 8;
@ -263,6 +271,7 @@ impl Blender {
} }
} }
#[must_use]
pub fn blend_biome( pub fn blend_biome(
&self, &self,
quart_x: i32, quart_x: i32,
@ -284,7 +293,7 @@ impl Blender {
|test_cell_x, test_cell_z, biome| { |test_cell_x, test_cell_z, biome| {
let dx = (quart_x - test_cell_x) as f64; let dx = (quart_x - test_cell_x) as f64;
let dz = (quart_z - test_cell_z) as f64; let dz = (quart_z - test_cell_z) as f64;
let distance = (dx * dx + dz * dz).sqrt(); let distance = dx.hypot(dz);
if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 && distance < closest_distance if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 && distance < closest_distance
{ {

View File

@ -428,7 +428,7 @@ pub fn get_height(p: &HeightProvider, random: &mut RandomGenerator, min_y: i8, h
HeightProvider::VeryBiasedToBottom(p) => { HeightProvider::VeryBiasedToBottom(p) => {
let min = p.min_inclusive.get_y(min_y as i16, height); let min = p.min_inclusive.get_y(min_y as i16, height);
let max = p.max_inclusive.get_y(min_y as i16, height); let max = p.max_inclusive.get_y(min_y as i16, height);
let inner = p.inner.map_or(1, |n| n.get()) as i32; let inner = p.inner.map_or(1, std::num::NonZero::get) as i32;
let min_rnd = random.next_inbetween_i32(min + inner, max); let min_rnd = random.next_inbetween_i32(min + inner, max);
let max_rnd = random.next_inbetween_i32(min, min_rnd - 1); let max_rnd = random.next_inbetween_i32(min, min_rnd - 1);
random.next_inbetween_i32(min, max_rnd - 1 + inner) random.next_inbetween_i32(min, max_rnd - 1 + inner)

View File

@ -16,7 +16,7 @@ impl CarvingMask {
} }
} }
fn get_bit_index(&self, x: i32, y: i32, z: i32) -> usize { const fn get_bit_index(&self, x: i32, y: i32, z: i32) -> usize {
// x: 0..16, z: 0..16, y: min_y..min_y+height // x: 0..16, z: 0..16, y: min_y..min_y+height
let rel_x = x & 15; let rel_x = x & 15;
let rel_z = z & 15; let rel_z = z & 15;

View File

@ -413,7 +413,7 @@ impl IcebergFeature {
} }
} }
fn get_ellipse_c(&self, y_off: i32, height: i32, shape_ellipse_c: i32) -> i32 { const fn get_ellipse_c(&self, y_off: i32, height: i32, shape_ellipse_c: i32) -> i32 {
let mut c = shape_ellipse_c; let mut c = shape_ellipse_c;
if y_off > 0 && height - y_off <= 3 { if y_off > 0 && height - y_off <= 3 {
c = shape_ellipse_c - (4 - (height - y_off)); c = shape_ellipse_c - (4 - (height - y_off));

View File

@ -130,7 +130,7 @@ struct SculkSpreader {
} }
impl SculkSpreader { impl SculkSpreader {
fn new() -> Self { const fn new() -> Self {
Self { Self {
cursors: Vec::new(), cursors: Vec::new(),
} }

View File

@ -143,8 +143,8 @@ impl CherryTrunkPlacer {
tree_height as i32 - 1 + self.branch_end_offset_from_top.get(random); tree_height as i32 - 1 + self.branch_end_offset_from_top.get(random);
let extend_branch_away_from_trunk = let extend_branch_away_from_trunk =
middle_continues_upwards || branch_end_pos_offset < offset_from_origin; middle_continues_upwards || branch_end_pos_offset < offset_from_origin;
let distance_to_trunk = self.branch_horizontal_length.get(random) let distance_to_trunk =
+ if extend_branch_away_from_trunk { 1 } else { 0 }; self.branch_horizontal_length.get(random) + i32::from(extend_branch_away_from_trunk);
let branch_end_pos = origin let branch_end_pos = origin
.add( .add(

View File

@ -85,9 +85,8 @@ impl TwistingVinesFeature {
// TODO: Set age property // TODO: Set age property
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES.default_state); chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES.default_state);
break; break;
} else {
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES_PLANT.default_state);
} }
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES_PLANT.default_state);
current_pos = current_pos.up(); current_pos = current_pos.up();
} }
} }

View File

@ -33,6 +33,7 @@ impl DoublePerlinNoiseSampler {
) )
} }
#[must_use]
pub fn get_amplitude(amplitudes: &[f64]) -> f64 { pub fn get_amplitude(amplitudes: &[f64]) -> f64 {
let mut j = i32::MAX; let mut j = i32::MAX;
let mut k = i32::MIN; let mut k = i32::MIN;

View File

@ -657,11 +657,11 @@ impl ProtoChunk {
generator: &super::generator::VanillaGenerator, generator: &super::generator::VanillaGenerator,
multi_noise_sampler: &mut MultiNoiseSampler, multi_noise_sampler: &mut MultiNoiseSampler,
) { ) {
let dimension = generator.dimension; let dimension = &generator.dimension;
// Instantiate ONLY the supplier we actually need // Instantiate ONLY the supplier we actually need
let active_supplier = if dimension == Dimension::THE_END { let active_supplier = if dimension == &Dimension::THE_END {
ActiveSupplier::End(TheEndBiomeSupplier) ActiveSupplier::End(TheEndBiomeSupplier)
} else if dimension == Dimension::THE_NETHER { } else if dimension == &Dimension::THE_NETHER {
ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER) ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER)
} else { } else {
ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD) ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD)
@ -1114,6 +1114,7 @@ impl ProtoChunk {
} }
} }
#[must_use]
pub fn get_allowed_biomes(set: &StructureSet) -> Vec<u16> { pub fn get_allowed_biomes(set: &StructureSet) -> Vec<u16> {
let mut allowed_biomes = Vec::new(); let mut allowed_biomes = Vec::new();
for entry in set.structures { for entry in set.structures {

View File

@ -392,8 +392,8 @@ impl StructurePieceBase for JungleTemplePiece {
}; };
self.piece.add_block(chunk, r_state, 9, -3, 5, bb); self.piece.add_block(chunk, r_state, 9, -3, 5, bb);
self.piece self.piece
.add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 4, bb); .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 4, bb)
} };
self.piece.add_block(chunk, redstone_wire_ns, 9, -2, 4, bb); self.piece.add_block(chunk, redstone_wire_ns, 9, -2, 4, bb);
/*if !self.placed_trap_2 { /*if !self.placed_trap_2 {
self.placed_trap_2 = this.createDispenser( self.placed_trap_2 = this.createDispenser(
@ -527,7 +527,7 @@ impl StructurePieceBase for JungleTemplePiece {
}*/ }*/
} }
} }
fn set_side_dir(props: &mut RedstoneWireLikeProperties, facing: HorizontalFacing) { const fn set_side_dir(props: &mut RedstoneWireLikeProperties, facing: HorizontalFacing) {
match facing { match facing {
HorizontalFacing::North => props.north = NorthRedstone::Side, HorizontalFacing::North => props.north = NorthRedstone::Side,
HorizontalFacing::South => props.south = SouthRedstone::Side, HorizontalFacing::South => props.south = SouthRedstone::Side,

View File

@ -9,6 +9,7 @@ pub struct SimpleInventory {
} }
impl SimpleInventory { impl SimpleInventory {
#[must_use]
pub fn new(size: usize) -> Self { pub fn new(size: usize) -> Self {
Self { Self {
stacks: (0..size) stacks: (0..size)

View File

@ -138,6 +138,7 @@ pub async fn dump() {
} }
impl Level { impl Level {
#[must_use]
pub fn from_root_folder( pub fn from_root_folder(
level_config: &LevelConfig, level_config: &LevelConfig,
root_folder: PathBuf, root_folder: PathBuf,
@ -204,7 +205,7 @@ impl Level {
should_save: AtomicBool::new(false), should_save: AtomicBool::new(false),
should_unload: AtomicBool::new(false), should_unload: AtomicBool::new(false),
autosave_ticks: level_config.autosave_ticks, autosave_ticks: level_config.autosave_ticks,
pending_entity_generations: pending_entity_generations.clone(), pending_entity_generations,
level_channel: level_channel.clone(), level_channel: level_channel.clone(),
thread_tracker, thread_tracker,
chunk_listener: listener.clone(), chunk_listener: listener.clone(),
@ -213,8 +214,7 @@ impl Level {
// TODO // TODO
let total_cores = thread::available_parallelism() let total_cores = thread::available_parallelism()
.map(|n| n.get()) .map_or(1, std::num::NonZero::get)
.unwrap_or(1)
.saturating_sub(2) .saturating_sub(2)
.max(1); .max(1);
let threads_per_dimension = (total_cores / 2).max(1); let threads_per_dimension = (total_cores / 2).max(1);
@ -253,9 +253,9 @@ impl Level {
}); });
} else { } else {
// Fallback to spawning a new thread if no pool is available (should not happen in production) // Fallback to spawning a new thread if no pool is available (should not happen in production)
let level_clone = level.clone(); let level_clone = level;
thread::Builder::new() thread::Builder::new()
.name(format!("Entity Gen {:?}", pos)) .name(format!("Entity Gen {pos:?}"))
.spawn(move || { .spawn(move || {
let arc_chunk = Arc::new(ChunkEntityData { let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x, x: pos.x,

View File

@ -369,8 +369,10 @@ impl SkyLightPropagator {
} }
} }
Chunk::Level(c) => { Chunk::Level(c) => {
let mut light_engine = let mut light_engine = c
c.light_engine.lock().unwrap_or_else(|e| e.into_inner()); .light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_idx < light_engine.sky_light.len() { if section_idx < light_engine.sky_light.len() {
light_engine.sky_light[section_idx] light_engine.sky_light[section_idx]
.set(local_x, local_y, local_z, 15); .set(local_x, local_y, local_z, 15);
@ -407,8 +409,10 @@ impl SkyLightPropagator {
} }
} }
Chunk::Level(c) => { Chunk::Level(c) => {
let mut light_engine = let mut light_engine = c
c.light_engine.lock().unwrap_or_else(|e| e.into_inner()); .light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_idx < light_engine.sky_light.len() { if section_idx < light_engine.sky_light.len() {
light_engine.sky_light[section_idx] light_engine.sky_light[section_idx]
.set(local_x, local_y, local_z, light_val); .set(local_x, local_y, local_z, light_val);

View File

@ -482,7 +482,10 @@ impl DynamicLightEngine {
let section_index = let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
// Bounds check for section index // Bounds check for section index
let mut light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner()); let mut light_engine = chunk
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_index >= light_engine.block_light.len() { if section_index >= light_engine.block_light.len() {
return Err("Invalid section index".to_string()); return Err("Invalid section index".to_string());
} }
@ -509,7 +512,10 @@ impl DynamicLightEngine {
let section_index = let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
let light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner()); let light_engine = chunk
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
// Bounds check for section index (lock the light engine) // Bounds check for section index (lock the light engine)
if section_index >= light_engine.sky_light.len() { if section_index >= light_engine.sky_light.len() {
return 15; return 15;
@ -536,7 +542,10 @@ impl DynamicLightEngine {
let section_index = let section_index =
(relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
// Bounds check for section index // Bounds check for section index
let mut light_engine = chunk.light_engine.lock().unwrap_or_else(|e| e.into_inner()); let mut light_engine = chunk
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_index >= light_engine.sky_light.len() { if section_index >= light_engine.sky_light.len() {
return Err("Invalid section index".to_string()); return Err("Invalid section index".to_string());
} }

View File

@ -13,7 +13,6 @@ const fn get_chunk_index(cache: &Cache, chunk_x: i32, chunk_z: i32) -> Option<us
Some((rel_x * cache.size + rel_z) as usize) Some((rel_x * cache.size + rel_z) as usize)
} }
#[inline(always)]
fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> { fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> {
let bottom = cache.bottom_y() as i32; let bottom = cache.bottom_y() as i32;
if pos_y < bottom { if pos_y < bottom {
@ -58,7 +57,6 @@ pub fn get_block_light(cache: &Cache, pos: BlockPos) -> u8 {
} }
} }
#[inline(always)]
pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) { pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
let chunk_x = pos.0.x >> 4; let chunk_x = pos.0.x >> 4;
let chunk_z = pos.0.z >> 4; let chunk_z = pos.0.z >> 4;
@ -76,7 +74,10 @@ pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
match &mut cache.chunks[idx] { match &mut cache.chunks[idx] {
Chunk::Level(c) => { Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap_or_else(|e| e.into_inner()); let mut light_engine = c
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_y < light_engine.block_light.len() { if section_y < light_engine.block_light.len() {
light_engine.block_light[section_y].set(x, y, z, level); light_engine.block_light[section_y].set(x, y, z, level);
c.dirty.store(true, std::sync::atomic::Ordering::Relaxed); c.dirty.store(true, std::sync::atomic::Ordering::Relaxed);
@ -90,7 +91,6 @@ pub fn set_block_light(cache: &mut Cache, pos: BlockPos, level: u8) {
} }
} }
#[inline(always)]
#[must_use] #[must_use]
pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 { pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 {
let chunk_x = pos.0.x >> 4; let chunk_x = pos.0.x >> 4;
@ -125,7 +125,6 @@ pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 {
} }
} }
#[inline(always)]
pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) { pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) {
let chunk_x = pos.0.x >> 4; let chunk_x = pos.0.x >> 4;
let chunk_z = pos.0.z >> 4; let chunk_z = pos.0.z >> 4;
@ -143,7 +142,10 @@ pub fn set_sky_light(cache: &mut Cache, pos: BlockPos, level: u8) {
match &mut cache.chunks[idx] { match &mut cache.chunks[idx] {
Chunk::Level(c) => { Chunk::Level(c) => {
let mut light_engine = c.light_engine.lock().unwrap_or_else(|e| e.into_inner()); let mut light_engine = c
.light_engine
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if section_y < light_engine.sky_light.len() { if section_y < light_engine.sky_light.len() {
light_engine.sky_light[section_y].set(x, y, z, level); light_engine.sky_light[section_y].set(x, y, z, level);
c.dirty.store(true, std::sync::atomic::Ordering::Relaxed); c.dirty.store(true, std::sync::atomic::Ordering::Relaxed);

View File

@ -25,7 +25,10 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
let next_offset = (current_offset + 1) % MAX_TICK_DELAY; let next_offset = (current_offset + 1) % MAX_TICK_DELAY;
self.offset.store(next_offset, Ordering::SeqCst); self.offset.store(next_offset, Ordering::SeqCst);
let mut inner_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner()); let mut inner_guard = self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(inner) = inner_guard.as_mut() else { let Some(inner) = inner_guard.as_mut() else {
return Vec::new(); return Vec::new();
}; };
@ -43,7 +46,10 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
} }
pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) { pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) {
let mut inner_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner()); let mut inner_guard = self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let inner = inner_guard.get_or_insert_with(|| { let inner = inner_guard.get_or_insert_with(|| {
Box::new(ChunkTickSchedulerInner { Box::new(ChunkTickSchedulerInner {
tick_queue: std::array::from_fn(|_| Vec::new()), tick_queue: std::array::from_fn(|_| Vec::new()),
@ -67,7 +73,7 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool { pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool {
self.inner self.inner
.lock() .lock()
.unwrap_or_else(|e| e.into_inner()) .unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref() .as_ref()
.is_some_and(|inner| inner.queued_ticks.contains(&(pos, value))) .is_some_and(|inner| inner.queued_ticks.contains(&(pos, value)))
} }
@ -75,7 +81,7 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
pub fn has_ticks(&self) -> bool { pub fn has_ticks(&self) -> bool {
self.inner self.inner
.lock() .lock()
.unwrap_or_else(|e| e.into_inner()) .unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref() .as_ref()
.is_some_and(|inner| !inner.queued_ticks.is_empty()) .is_some_and(|inner| !inner.queued_ticks.is_empty())
} }
@ -83,7 +89,10 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
#[must_use] #[must_use]
pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> { pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> {
let offset = self.offset.load(Ordering::SeqCst); let offset = self.offset.load(Ordering::SeqCst);
let inner_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner()); let inner_guard = self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(inner) = inner_guard.as_ref() else { let Some(inner) = inner_guard.as_ref() else {
return Vec::new(); return Vec::new();
}; };
@ -112,7 +121,10 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
let (lower, _) = iter.size_hint(); let (lower, _) = iter.size_hint();
if lower > 0 { if lower > 0 {
let mut inner_guard = scheduler.inner.lock().unwrap_or_else(|e| e.into_inner()); let mut inner_guard = scheduler
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let inner = inner_guard.get_or_insert_with(|| { let inner = inner_guard.get_or_insert_with(|| {
Box::new(ChunkTickSchedulerInner { Box::new(ChunkTickSchedulerInner {
tick_queue: std::array::from_fn(|_| Vec::new()), tick_queue: std::array::from_fn(|_| Vec::new()),

View File

@ -314,10 +314,11 @@ impl BlockBehaviour for BedBlock {
if args if args
.player .player
.set_respawn_point( .set_respawn_point(
args.world.dimension, args.world.dimension.clone(),
bed_head_pos, bed_head_pos,
args.player.get_entity().yaw.load(), args.player.get_entity().yaw.load(),
args.player.get_entity().pitch.load(), args.player.get_entity().pitch.load(),
false,
) )
.await .await
{ {

View File

@ -195,9 +195,9 @@ impl BlockBehaviour for FireBlock {
return; return;
} }
let dimension = args.world.dimension; let dimension = &args.world.dimension;
// First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla // First lets check if we are in OverWorld or Nether, its not possible to place an Nether portal in other dimensions in Vanilla
if (dimension == Dimension::OVERWORLD || dimension == Dimension::THE_NETHER) if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER)
&& let Some(portal) = && let Some(portal) =
NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X).await NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X).await
{ {

View File

@ -90,8 +90,8 @@ impl FireBlockBase {
block_pos: &BlockPos, block_pos: &BlockPos,
direction: BlockDirection, direction: BlockDirection,
) -> bool { ) -> bool {
let dimension = world.dimension; let dimension = &world.dimension;
if dimension != Dimension::OVERWORLD && dimension != Dimension::THE_NETHER { if dimension != &Dimension::OVERWORLD && dimension != &Dimension::THE_NETHER {
return false; return false;
} }
let mut found = false; let mut found = false;

View File

@ -124,10 +124,10 @@ impl CommandExecutor for TargetsPosAngleExecutor {
} }
async fn set_spawnpoint(sender: &CommandSender, target: &Arc<Player>, pos: BlockPos, yaw: f32) { async fn set_spawnpoint(sender: &CommandSender, target: &Arc<Player>, pos: BlockPos, yaw: f32) {
let dimension = target.world().dimension; let dimension = &target.world().dimension;
target target
.set_respawn_point_forced(dimension, pos, yaw, 0.0) .set_respawn_point(dimension.clone(), pos, yaw, 0.0, true)
.await; .await;
sender sender

View File

@ -3,13 +3,16 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering::Relaxed};
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_data::tag::{self, Taggable};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::chunk::ChunkHeightmapType;
use rand::RngExt; use rand::RngExt;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::entity::mob::{Mob, MobEntity}; use crate::entity::mob::{Mob, MobEntity};
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture}; use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture};
use crate::world::World;
const ROOSTING_FLAG: u8 = 1; const ROOSTING_FLAG: u8 = 1;
const CLOSE_PLAYER_DISTANCE: f64 = 4.0; const CLOSE_PLAYER_DISTANCE: f64 = 4.0;
@ -39,6 +42,31 @@ impl BatEntity {
mob_arc mob_arc
} }
pub async fn check_bat_spawn_rules(world: &World, pos: &BlockPos) -> bool {
if pos.0.y
>= world
.get_heightmap_height(ChunkHeightmapType::WorldSurface, pos.0.x, pos.0.z)
.await
{
return false;
}
if rand::random_bool(1.0) {
return false;
}
if world.get_max_local_raw_brightness(pos).await > rand::random_range(0..4) {
return false;
}
if world
.get_block(pos)
.await
.has_tag(&tag::Block::MINECRAFT_BATS_SPAWNABLE_ON)
{
return false;
}
//TODO:check_mob_spawn_rules(entity_type, world, spawn_reason, pos).await
true
}
fn is_roosting(&self) -> bool { fn is_roosting(&self) -> bool {
self.roosting.load(Relaxed) self.roosting.load(Relaxed)
} }

View File

@ -13,10 +13,13 @@ use pumpkin_data::meta_data_type::MetaDataType;
use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::tag::{self, Taggable};
use pumpkin_data::tracked_data::TrackedData; use pumpkin_data::tracked_data::TrackedData;
use pumpkin_protocol::java::client::play::{CHeadRot, CUpdateEntityRot, Metadata}; use pumpkin_protocol::java::client::play::{CHeadRot, CUpdateEntityRot, Metadata};
use pumpkin_util::Difficulty;
use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::xoroshiro128::Xoroshiro;
use pumpkin_util::random::{RandomGenerator, get_seed};
use rand::RngExt; use rand::RngExt;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
@ -184,6 +187,44 @@ impl MobEntity {
true true
} }
pub async fn is_dark_enough_to_spawn(world: &World, pos: &BlockPos) -> bool {
let sky_light = world.get_sky_light_level(pos).await;
if sky_light > rand::random_range(0..32) {
return false;
}
let dimension = &world.dimension;
let block_light_limit = dimension.monster_spawn_block_light_limit;
let block_light = world.get_block_light_level(pos).await.unwrap();
if block_light_limit < 15 && block_light > block_light_limit {
return false;
}
let current_brightness = if world.is_thundering().await {
(sky_light - 10).max(block_light)
} else {
sky_light.max(block_light)
};
// TODO
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(get_seed()));
current_brightness <= dimension.monster_spawn_light_level.get(&mut random) as u8
}
pub async fn check_monster_spawn_rules(world: &World, pos: &BlockPos) -> bool {
if world.level_info.load().difficulty == Difficulty::Peaceful {
return false;
}
if !Self::is_dark_enough_to_spawn(world, pos).await {
return false;
}
//TODO:check_mob_spawn_rules(entity_type, world, spawn_reason, pos).await
true
}
pub async fn try_attack(&self, caller: &dyn EntityBase, target: &dyn EntityBase) { pub async fn try_attack(&self, caller: &dyn EntityBase, target: &dyn EntityBase) {
if self.living_entity.dead.load(Relaxed) { if self.living_entity.dead.load(Relaxed) {
return; return;

View File

@ -410,7 +410,7 @@ pub struct Player {
/// The player's previous gamemode /// The player's previous gamemode
pub previous_gamemode: AtomicCell<Option<GameMode>>, pub previous_gamemode: AtomicCell<Option<GameMode>>,
/// The player's spawnpoint /// The player's spawnpoint
pub respawn_point: AtomicCell<Option<RespawnPoint>>, pub respawn_point: Mutex<Option<RespawnPoint>>,
/// The player's sleep status /// The player's sleep status
pub sleeping_since: AtomicCell<Option<u8>>, pub sleeping_since: AtomicCell<Option<u8>>,
/// Manages the player's breath level /// Manages the player's breath level
@ -566,7 +566,7 @@ impl Player {
gamemode: AtomicCell::new(gamemode), gamemode: AtomicCell::new(gamemode),
previous_gamemode: AtomicCell::new(None), previous_gamemode: AtomicCell::new(None),
// TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values // TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values
respawn_point: AtomicCell::new(None), respawn_point: Mutex::new(None),
sleeping_since: AtomicCell::new(None), sleeping_since: AtomicCell::new(None),
// We want this to be an impossible watched section so that `chunker::update_position` // We want this to be an impossible watched section so that `chunker::update_position`
// will mark chunks as watched for a new join rather than a respawn. // will mark chunks as watched for a new join rather than a respawn.
@ -1072,21 +1072,16 @@ impl Player {
block_pos: BlockPos, block_pos: BlockPos,
yaw: f32, yaw: f32,
pitch: f32, pitch: f32,
forced: bool,
) -> bool { ) -> bool {
if let Some(respawn_point) = self.respawn_point.load() if !forced
&& let Some(respawn_point) = self.respawn_point.lock().await.as_ref()
&& dimension == respawn_point.dimension && dimension == respawn_point.dimension
&& block_pos == respawn_point.position && block_pos == respawn_point.position
{ {
return false; return false;
} }
self.respawn_point.store(Some(RespawnPoint {
dimension,
position: block_pos,
yaw,
force: false,
}));
self.client self.client
.send_packet_now(&CPlayerSpawnPosition::new( .send_packet_now(&CPlayerSpawnPosition::new(
block_pos, block_pos,
@ -1095,35 +1090,16 @@ impl Player {
dimension.minecraft_name.to_owned(), dimension.minecraft_name.to_owned(),
)) ))
.await; .await;
*self.respawn_point.lock().await = Some(RespawnPoint {
dimension,
position: block_pos,
yaw,
force: forced,
});
true true
} }
/// Sets the respawn point with force=true, bypassing bed/anchor checks.
/// Used by /spawnpoint command.
pub async fn set_respawn_point_forced(
&self,
dimension: Dimension,
block_pos: BlockPos,
yaw: f32,
pitch: f32,
) {
self.respawn_point.store(Some(RespawnPoint {
dimension,
position: block_pos,
yaw,
force: true,
}));
self.client
.send_packet_now(&CPlayerSpawnPosition::new(
block_pos,
yaw,
pitch,
dimension.minecraft_name.to_owned(),
))
.await;
}
/// Calculates the player's respawn point based on stored spawn data. /// Calculates the player's respawn point based on stored spawn data.
/// ///
/// Returns `Some(CalculatedRespawnPoint)` if a valid respawn point exists, `None` otherwise. /// Returns `Some(CalculatedRespawnPoint)` if a valid respawn point exists, `None` otherwise.
@ -1143,7 +1119,8 @@ impl Player {
type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties; type BedProperties = pumpkin_data::block_properties::WhiteBedLikeProperties;
type AnchorProperties = pumpkin_data::block_properties::RespawnAnchorLikeProperties; type AnchorProperties = pumpkin_data::block_properties::RespawnAnchorLikeProperties;
let respawn_point = self.respawn_point.load()?; let respawn_guard = self.respawn_point.lock().await;
let respawn_point = respawn_guard.as_ref()?;
let world = self.world(); let world = self.world();
let pos = &respawn_point.position; let pos = &respawn_point.position;
let (block, state_id) = world.get_block_and_state_id(pos).await; let (block, state_id) = world.get_block_and_state_id(pos).await;
@ -1172,7 +1149,7 @@ impl Player {
position, position,
yaw: respawn_point.yaw, yaw: respawn_point.yaw,
pitch: 0.0, pitch: 0.0,
dimension: respawn_point.dimension, dimension: respawn_point.dimension.clone(),
}); });
} }
return None; return None;
@ -1192,7 +1169,7 @@ impl Player {
position: spawn_pos, position: spawn_pos,
yaw: respawn_point.yaw, yaw: respawn_point.yaw,
pitch: 0.0, pitch: 0.0,
dimension: respawn_point.dimension, dimension: respawn_point.dimension.clone(),
}); });
} }
return None; return None;
@ -1226,7 +1203,7 @@ impl Player {
position: spawn_pos, position: spawn_pos,
yaw: respawn_point.yaw, yaw: respawn_point.yaw,
pitch: 0.0, pitch: 0.0,
dimension: respawn_point.dimension, dimension: respawn_point.dimension.clone(),
}); });
} }
return None; return None;
@ -1512,10 +1489,11 @@ impl Player {
pub async fn wake_up(&self) { pub async fn wake_up(&self) {
let world = self.world(); let world = self.world();
let respawn_point = self let respawn_point = self.respawn_point.lock().await;
.respawn_point let Some(respawn_point) = respawn_point.as_ref() else {
.load() warn!("Player waking up should have it's respawn point set on the bed");
.expect("Player waking up should have it's respawn point set on the bed."); return;
};
let (bed, bed_state) = world.get_block_and_state_id(&respawn_point.position).await; let (bed, bed_state) = world.get_block_and_state_id(&respawn_point.position).await;
BedBlock::set_occupied(false, &world, bed, &respawn_point.position, bed_state).await; BedBlock::set_occupied(false, &world, bed, &respawn_point.position, bed_state).await;
@ -3744,7 +3722,7 @@ impl NBTStorage for Player {
nbt.put_bool(self.has_played_before.load(Ordering::Relaxed)); nbt.put_bool(self.has_played_before.load(Ordering::Relaxed));
self.hunger_manager.write_nbt(nbt).await; self.hunger_manager.write_nbt(nbt).await;
if let Some(respawn) = self.respawn_point.load().as_ref() { if let Some(respawn) = self.respawn_point.lock().await.as_ref() {
nbt.put_bool(true); nbt.put_bool(true);
nbt.put_int(respawn.position.0.x); nbt.put_int(respawn.position.0.x);
nbt.put_int(respawn.position.0.y); nbt.put_int(respawn.position.0.y);
@ -3796,15 +3774,15 @@ impl NBTStorage for Player {
let dim = nbt let dim = nbt
.get_string() .get_string()
.ok() .ok()
.and_then(|s| Dimension::from_name(s.as_str()).copied()) .and_then(|s| Dimension::from_name(s.as_str()).cloned())
.unwrap_or(self.world().dimension); .unwrap_or(self.world().dimension.clone());
let force = nbt.get_bool().unwrap_or(false); let force = nbt.get_bool().unwrap_or(false);
self.respawn_point.store(Some(RespawnPoint { *self.respawn_point.lock().await = Some(RespawnPoint {
dimension: dim, dimension: dim,
position: BlockPos(Vector3::new(x, y, z)), position: BlockPos(Vector3::new(x, y, z)),
yaw: 0.0, yaw: 0.0,
force, force,
})); });
} }
self.enchantment_seed self.enchantment_seed
.store(nbt.get_int().unwrap_or(rand::random()), Ordering::Relaxed); .store(nbt.get_int().unwrap_or(rand::random()), Ordering::Relaxed);
@ -4168,7 +4146,7 @@ impl Abilities {
} }
/// Represents the player's stored respawn point (bed/anchor/forced). /// Represents the player's stored respawn point (bed/anchor/forced).
#[derive(Copy, Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RespawnPoint { pub struct RespawnPoint {
pub dimension: Dimension, pub dimension: Dimension,
pub position: BlockPos, pub position: BlockPos,
@ -4176,9 +4154,6 @@ pub struct RespawnPoint {
pub force: bool, pub force: bool,
} }
/// Calculated respawn position ready for use.
/// Returned by `calculate_respawn_point()`.
#[derive(Debug, Clone)]
pub struct CalculatedRespawnPoint { pub struct CalculatedRespawnPoint {
/// The exact position to spawn at (centered in block). /// The exact position to spawn at (centered in block).
pub position: Vector3<f64>, pub position: Vector3<f64>,

View File

@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid; use uuid::Uuid;
@ -13,7 +14,7 @@ use crate::entity::experience_orb::ExperienceOrbEntity;
use crate::entity::falling::FallingEntity; use crate::entity::falling::FallingEntity;
use crate::entity::item::ItemEntity; use crate::entity::item::ItemEntity;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::mob::bat::BatEntity; use crate::entity::mob::bat::{self, BatEntity};
use crate::entity::mob::blaze::BlazeEntity; use crate::entity::mob::blaze::BlazeEntity;
use crate::entity::mob::breeze::BreezeEntity; use crate::entity::mob::breeze::BreezeEntity;
use crate::entity::mob::cave_spider::CaveSpiderEntity; use crate::entity::mob::cave_spider::CaveSpiderEntity;
@ -107,7 +108,7 @@ use crate::entity::projectile::splash_potion::SplashPotionEntity;
use crate::entity::projectile::wind_charge::WindChargeEntity; use crate::entity::projectile::wind_charge::WindChargeEntity;
use crate::entity::tnt::TNTEntity; use crate::entity::tnt::TNTEntity;
use crate::entity::vehicle::boat::BoatEntity; use crate::entity::vehicle::boat::BoatEntity;
use crate::entity::{Entity, EntityBase}; use crate::entity::{Entity, EntityBase, mob};
use crate::world::World; use crate::world::World;
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
@ -304,3 +305,41 @@ pub async fn from_type(
mob mob
} }
pub async fn check_spawn_rules(
entity_type: &'static EntityType,
world: &World,
pos: &BlockPos,
) -> bool {
let id = entity_type.id;
if id == EntityType::BOGGED.id
|| id == EntityType::CAVE_SPIDER.id
|| id == EntityType::CREEPER.id
|| id == EntityType::ENDERMAN.id
|| id == EntityType::GIANT.id
|| id == EntityType::RAVAGER.id
|| id == EntityType::SKELETON.id
|| id == EntityType::SPIDER.id
|| id == EntityType::WITCH.id
|| id == EntityType::WITHER.id
|| id == EntityType::WITHER_SKELETON.id
|| id == EntityType::ZOMBIE.id
|| id == EntityType::ZOMBIE_HORSE.id
|| id == EntityType::ZOMBIE_VILLAGER.id
|| id == EntityType::CREAKING.id
|| id == EntityType::EVOKER.id
|| id == EntityType::ILLUSIONER.id
|| id == EntityType::VEX.id
|| id == EntityType::VINDICATOR.id
|| id == EntityType::WARDEN.id
{
return mob::MobEntity::check_monster_spawn_rules(world, pos).await;
}
if id == EntityType::BAT.id {
return bat::BatEntity::check_bat_spawn_rules(world, pos).await;
}
// TODO
true
}

View File

@ -45,7 +45,7 @@ impl ItemBehaviour for MapItem {
let map_id = server.next_map_id(); let map_id = server.next_map_id();
let _ = server.map_manager.create_map( let _ = server.map_manager.create_map(
map_id, map_id,
player.world().dimension, player.world().dimension.clone(),
player.position().x as i32, player.position().x as i32,
player.position().z as i32, player.position().z as i32,
0, // Default scale 0, // Default scale

View File

@ -286,7 +286,7 @@ impl Server {
.color_named(NamedColor::DarkGreen) .color_named(NamedColor::DarkGreen)
.to_pretty_console() .to_pretty_console()
); );
let level = into_level(dim, &config, path, seed, Some(pool)); let level = into_level(dim.clone(), &config, path, seed, Some(pool));
let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak)); let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak));
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal( let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(
world.clone(), world.clone(),
@ -383,8 +383,13 @@ impl Server {
let seed = server.level_info.load().world_gen_settings.seed; let seed = server.level_info.load().world_gen_settings.seed;
// TODO: gen_pool should be reused // TODO: gen_pool should be reused
let level = let level = pumpkin_world::dimension::into_level(
pumpkin_world::dimension::into_level(dimension, &config, world_path, seed, None); dimension.clone(),
&config,
world_path,
seed,
None,
);
let world: World = World::load(level.clone(), l_info, dimension, registry, weak); let world: World = World::load(level.clone(), l_info, dimension, registry, weak);
let world = Arc::new(world); let world = Arc::new(world);
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal( let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(

View File

@ -150,7 +150,7 @@ pub mod weather;
use crate::world::natural_spawner::{SpawnState, spawn_for_chunk}; use crate::world::natural_spawner::{SpawnState, spawn_for_chunk};
use pumpkin_config::lighting::LightingEngineConfig; use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::effect::StatusEffect; use pumpkin_data::effect::StatusEffect;
use pumpkin_world::chunk::ChunkHeightmapType::MotionBlocking; use pumpkin_world::chunk::ChunkHeightmapType::{self, MotionBlocking};
use uuid::Uuid; use uuid::Uuid;
use weather::Weather; use weather::Weather;
@ -241,7 +241,8 @@ impl World {
// Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist) // Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist)
let portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder); let portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder);
let dragon_fight =
(dimension == Dimension::THE_END).then(|| Mutex::new(dragon_fight::DragonFight::new()));
Self { Self {
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
level, level,
@ -259,8 +260,7 @@ impl World {
synced_block_event_queue: Mutex::new(Vec::new()), synced_block_event_queue: Mutex::new(Vec::new()),
unsent_block_changes: Mutex::new(HashMap::new()), unsent_block_changes: Mutex::new(HashMap::new()),
portal_poi: Mutex::new(portal_poi), portal_poi: Mutex::new(portal_poi),
dragon_fight: (dimension == Dimension::THE_END) dragon_fight,
.then(|| Mutex::new(dragon_fight::DragonFight::new())),
spawn_state: ArcSwap::new(Arc::new(SpawnState::empty())), spawn_state: ArcSwap::new(Arc::new(SpawnState::empty())),
active_chunks: ArcSwap::new(Arc::new(FxHashSet::default())), active_chunks: ArcSwap::new(Arc::new(FxHashSet::default())),
server, server,
@ -1574,6 +1574,24 @@ impl World {
.await .await
} }
pub async fn get_heightmap_height(
&self,
height_map: ChunkHeightmapType,
x: i32,
z: i32,
) -> i32 {
let chunk_pos = Vector2::new(x >> 4, z >> 4);
self.level
.get_or_fetch_chunk(chunk_pos, |chunk| {
chunk
.heightmap
.lock()
.unwrap()
.get(height_map, x, z, self.min_y)
})
.await
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub async fn spawn_bedrock_player( pub async fn spawn_bedrock_player(
&self, &self,
@ -1884,7 +1902,7 @@ impl World {
false, false,
true, true,
false, false,
self.dimension, self.dimension.clone(),
biome::hash_seed(self.level.seed.0), // seed biome::hash_seed(self.level.seed.0), // seed
gamemode as u8, gamemode as u8,
player player
@ -2427,7 +2445,7 @@ impl World {
), ),
spawn_yaw, spawn_yaw,
spawn_pitch, spawn_pitch,
self.dimension, self.dimension.clone(),
) )
}; };
@ -3424,6 +3442,26 @@ impl World {
replaced_block_state_id replaced_block_state_id
} }
pub async fn get_max_local_raw_brightness(&self, pos: &BlockPos) -> u8 {
let sky_light = self.get_sky_light_level(pos).await;
let block_light = self.get_block_light_level(pos).await.unwrap();
sky_light.max(block_light) // TODO: getSkyDarken
}
pub async fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> {
self.level
.light_engine
.get_block_light_level(&self.level, position)
.await
}
pub async fn get_sky_light_level(&self, position: &BlockPos) -> u8 {
self.level
.light_engine
.get_sky_light_level(&self.level, position)
.await
}
pub async fn schedule_block_tick( pub async fn schedule_block_tick(
&self, &self,
block: &Block, block: &Block,

View File

@ -1,5 +1,5 @@
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::r#type::from_type; use crate::entity::r#type::{check_spawn_rules, from_type};
use crate::world::World; use crate::world::World;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use pumpkin_data::biome::Spawner; use pumpkin_data::biome::Spawner;
@ -840,6 +840,9 @@ pub async fn is_valid_spawn_position_for_type(
if !is_spawn_position_ok(world, block_pos, entity_type).await { if !is_spawn_position_ok(world, block_pos, entity_type).await {
return false; return false;
} }
if !check_spawn_rules(entity_type, world, block_pos).await {
return false;
}
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes // TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
if !world if !world
.is_space_empty(BoundingBox::new_from_pos( .is_space_empty(BoundingBox::new_from_pos(