From 2bfe2335f0204e4b2c4f8afeda9fdb839d46de53 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 10 May 2026 11:09:38 +0200 Subject: [PATCH] feat: inital mob spawn rules --- pumpkin-codegen/src/carver.rs | 14 ++-- pumpkin-codegen/src/configured_feature.rs | 18 +++-- pumpkin-codegen/src/dimension.rs | 38 ++++----- pumpkin-codegen/src/enchantments.rs | 7 +- pumpkin-codegen/src/noise_parameter.rs | 19 +++-- pumpkin-codegen/src/remap/mod.rs | 1 - pumpkin-codegen/src/translations.rs | 9 ++- pumpkin-codegen/src/villager.rs | 30 +++++-- pumpkin-codegen/src/wit/bedrock_packet.rs | 55 ++++++++++--- pumpkin-codegen/src/wit/entity_type.rs | 13 ++- pumpkin-codegen/src/wit/java_packet.rs | 62 ++++++++++---- pumpkin-codegen/src/wit/packet_mapping.rs | 76 +++++++++++++---- pumpkin-codegen/src/wit/particle.rs | 8 +- pumpkin-codegen/src/wit/sound.rs | 8 +- pumpkin-codegen/src/wit/utils.rs | 12 +-- pumpkin-data/src/generated/dimension.rs | 27 ++++++- pumpkin-world/benches/chunk_gen.rs | 2 +- pumpkin-world/src/chunk/format/linear.rs | 19 +++-- pumpkin-world/src/chunk/io/file_manager.rs | 4 +- pumpkin-world/src/chunk/mod.rs | 1 + pumpkin-world/src/chunk/palette.rs | 1 + pumpkin-world/src/chunk_system/channel.rs | 5 +- pumpkin-world/src/chunk_system/chunk_state.rs | 2 +- pumpkin-world/src/chunk_system/generation.rs | 2 +- pumpkin-world/src/chunk_system/schedule.rs | 5 +- pumpkin-world/src/dimension.rs | 1 + .../src/generation/blender/blending_data.rs | 2 + pumpkin-world/src/generation/blender/mod.rs | 17 +++- pumpkin-world/src/generation/carver/cave.rs | 2 +- pumpkin-world/src/generation/carver/mask.rs | 2 +- .../generation/feature/features/iceberg.rs | 2 +- .../feature/features/sculk_patch.rs | 2 +- .../feature/features/tree/trunk/cherry.rs | 4 +- .../feature/features/twisting_vines.rs | 3 +- pumpkin-world/src/generation/noise/perlin.rs | 1 + pumpkin-world/src/generation/proto_chunk.rs | 7 +- .../structure/structures/jungle_temple.rs | 6 +- .../src/inventory/simple_inventory.rs | 1 + pumpkin-world/src/level.rs | 10 +-- pumpkin-world/src/lighting/engine.rs | 12 ++- pumpkin-world/src/lighting/runtime.rs | 15 +++- pumpkin-world/src/lighting/storage.rs | 14 ++-- pumpkin-world/src/tick/scheduler.rs | 24 ++++-- pumpkin/src/block/blocks/bed.rs | 3 +- pumpkin/src/block/blocks/fire/fire.rs | 4 +- pumpkin/src/block/blocks/fire/mod.rs | 4 +- pumpkin/src/command/commands/spawnpoint.rs | 4 +- pumpkin/src/entity/mob/bat.rs | 28 +++++++ pumpkin/src/entity/mob/mod.rs | 41 ++++++++++ pumpkin/src/entity/player.rs | 81 +++++++------------ pumpkin/src/entity/type.rs | 43 +++++++++- pumpkin/src/item/items/map.rs | 2 +- pumpkin/src/server/mod.rs | 11 ++- pumpkin/src/world/mod.rs | 50 ++++++++++-- pumpkin/src/world/natural_spawner.rs | 5 +- 55 files changed, 595 insertions(+), 244 deletions(-) diff --git a/pumpkin-codegen/src/carver.rs b/pumpkin-codegen/src/carver.rs index 659eb5f28..fcc246745 100644 --- a/pumpkin-codegen/src/carver.rs +++ b/pumpkin-codegen/src/carver.rs @@ -23,9 +23,7 @@ pub fn build() -> TokenStream { let replaceable_str = config["replaceable"].as_str().unwrap_or(""); let replaceable = if let Some(tag_name) = replaceable_str.strip_prefix('#') { - let tag_name = tag_name - .to_uppercase() - .replace([':', '.', '-'], "_"); + let tag_name = tag_name.to_uppercase().replace([':', '.', '-'], "_"); let tag_ident = format_ident!("{}", tag_name); quote! { crate::tag::Block::#tag_ident } } else { @@ -70,10 +68,12 @@ pub fn build() -> TokenStream { let width_smoothness = shape["width_smoothness"].as_i64().unwrap_or(0) as i32; let horizontal_radius_factor = value_to_float_provider(&shape["horizontal_radius_factor"]); - let vertical_radius_default_factor = - shape["vertical_radius_default_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; + let vertical_radius_default_factor = shape["vertical_radius_default_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! { CarverAdditionalConfig::Canyon(CanyonCarverConfig { diff --git a/pumpkin-codegen/src/configured_feature.rs b/pumpkin-codegen/src/configured_feature.rs index 47ec0f805..3738b515e 100644 --- a/pumpkin-codegen/src/configured_feature.rs +++ b/pumpkin-codegen/src/configured_feature.rs @@ -609,7 +609,7 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { "minecraft:kelp" => { quote! { ConfiguredFeature::Kelp(crate::generation::feature::features::kelp::KelpFeature {}) } } - + // All TODO/empty features "minecraft:fossil" => { quote! { ConfiguredFeature::Fossil(crate::generation::feature::features::fossil::FossilFeature {}) } @@ -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_replaceable = value_to_block_predicate(&config["root_replaceable"]); 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) - as i32; - let root_column_max_height = config["root_column_max_height"].as_i64().unwrap_or(0) - as i32; + let root_placement_attempts = + 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) 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"] .as_i64() @@ -1108,8 +1108,9 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream { } "minecraft:upwards_branching_trunk_placer" => { let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]); - let place_branch_per_log_probability = - v["place_branch_per_log_probability"].as_f64().unwrap_or(0.0) as f32; + let place_branch_per_log_probability = v["place_branch_per_log_probability"] + .as_f64() + .unwrap_or(0.0) as f32; let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]); let can_grow_through = value_to_block_list(&v["can_grow_through"]); 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 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 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! { TrunkType::Cherry(CherryTrunkPlacer { branch_count: #branch_count, diff --git a/pumpkin-codegen/src/dimension.rs b/pumpkin-codegen/src/dimension.rs index 62b0b619a..a8a088943 100644 --- a/pumpkin-codegen/src/dimension.rs +++ b/pumpkin-codegen/src/dimension.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, fs}; +use crate::placed_feature::value_to_int_provider; use heck::ToShoutySnakeCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -36,6 +37,8 @@ pub struct Dimension { pub logical_height: i32, /// Tag key for blocks that act as infinite burn sources (e.g. `"minecraft:infiniburn_overworld"`). 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. #[serde(rename = "fixed_time")] pub fixed_time: Option, @@ -47,26 +50,6 @@ pub struct Dimension { pub timelines: Option, } -// #[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 for MonsterSpawnLightLevel { -// fn from(value: i32) -> Self { -// Self::Int(value) -// } -// } - /// Generates the `TokenStream` for the `Dimension` struct, its constants, and `from_name` lookup. pub fn build() -> TokenStream { let dimensions: BTreeMap = serde_json::from_str( @@ -113,6 +96,9 @@ pub fn build() -> TokenStream { 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 coordinate_scale = dim.coordinate_scale; let height = dim.height; @@ -174,6 +160,8 @@ pub fn build() -> TokenStream { logical_height: #logical_height, infiniburn: #infiniburn, 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, fog_color: #fog_color_literal, cloud_color: #cloud_color_literal, @@ -187,7 +175,13 @@ pub fn build() -> TokenStream { } 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 id: u8, pub minecraft_name: &'static str, @@ -200,6 +194,8 @@ pub fn build() -> TokenStream { pub logical_height: i32, pub infiniburn: &'static str, pub ambient_light: f32, + pub monster_spawn_light_level: IntProvider, + pub monster_spawn_block_light_limit: u8, pub sky_color: Option, pub fog_color: Option, pub cloud_color: Option, diff --git a/pumpkin-codegen/src/enchantments.rs b/pumpkin-codegen/src/enchantments.rs index 826c2aa55..eb19d23b5 100644 --- a/pumpkin-codegen/src/enchantments.rs +++ b/pumpkin-codegen/src/enchantments.rs @@ -121,7 +121,12 @@ pub fn build() -> TokenStream { let slots = enchantment.slots; 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!() }; let translate = translate.to_string(); diff --git a/pumpkin-codegen/src/noise_parameter.rs b/pumpkin-codegen/src/noise_parameter.rs index 7c944531f..b9a547180 100644 --- a/pumpkin-codegen/src/noise_parameter.rs +++ b/pumpkin-codegen/src/noise_parameter.rs @@ -8,19 +8,22 @@ pub fn build() -> TokenStream { let json: BTreeMap = serde_json::from_str(&fs::read_to_string("../assets/noise_parameters.json").unwrap()) .expect("Failed to parse noise_parameters.json"); - + let mut variants = TokenStream::new(); let mut match_variants = TokenStream::new(); 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()); - + // 1. MD5 Pre-calculation let hash = md5::compute(raw_name.as_bytes()); let lo = u64::from_be_bytes(hash[0..8].try_into().unwrap()); let hi = u64::from_be_bytes(hash[8..16].try_into().unwrap()); - + // 2. Amplitude Pre-calculation let amplitudes = ¶meter.amplitudes; let mut min_octave = i32::MAX; @@ -71,9 +74,9 @@ pub fn build() -> TokenStream { pub const COUNT: usize = #count; pub const fn new( - id: usize, - first_octave: i32, - amplitudes: &'static [f64], + id: usize, + first_octave: i32, + amplitudes: &'static [f64], lo: u64, hi: u64, amplitude: f64, @@ -92,4 +95,4 @@ pub fn build() -> TokenStream { #variants } } -} \ No newline at end of file +} diff --git a/pumpkin-codegen/src/remap/mod.rs b/pumpkin-codegen/src/remap/mod.rs index a25a227f2..5b9061681 100644 --- a/pumpkin-codegen/src/remap/mod.rs +++ b/pumpkin-codegen/src/remap/mod.rs @@ -148,7 +148,6 @@ impl ParsedMappings { for (mapped_id, id) in (shifts_to[index]..).zip(*from..to) { result[id as usize] = mapped_id; } - } result diff --git a/pumpkin-codegen/src/translations.rs b/pumpkin-codegen/src/translations.rs index b96e25382..ecf8f7d3d 100644 --- a/pumpkin-codegen/src/translations.rs +++ b/pumpkin-codegen/src/translations.rs @@ -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(); for line in bedrock_content.lines() { @@ -61,10 +62,10 @@ pub fn build() -> TokenStream { fn to_valid_ident(name: &str) -> Ident { let mut clean = name.to_uppercase().replace(['.', ':', '-'], "_"); - + if clean.chars().next().is_some_and(|c| c.is_ascii_digit()) { clean.insert(0, '_'); } - + format_ident!("{}", clean) -} \ No newline at end of file +} diff --git a/pumpkin-codegen/src/villager.rs b/pumpkin-codegen/src/villager.rs index 90a0fdc72..6126632a0 100644 --- a/pumpkin-codegen/src/villager.rs +++ b/pumpkin-codegen/src/villager.rs @@ -70,7 +70,12 @@ pub fn build() -> TokenStream { let format_trade = |trade: &TradeJson| { 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 = 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 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; quote! { Some(VillagerTradeItem { item: &crate::item::Item::#item, count: #count }) } @@ -88,7 +95,12 @@ pub fn build() -> TokenStream { 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 = quote! { VillagerTradeItem { item: &crate::item::Item::#gives_item, count: #gives_count } }; @@ -122,7 +134,7 @@ pub fn build() -> TokenStream { } let prof = parts[0]; let level_str = parts[1].strip_prefix("level_").unwrap_or(parts[1]); - + let mut matching_trades = Vec::new(); let prefix = format!("{prof}/{level_str}/"); for (key, trade) in &data.villager_trades { @@ -132,7 +144,9 @@ pub fn build() -> TokenStream { } // 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}/"); for (key, trade) in &data.villager_trades { if key.starts_with(&smith_prefix) { @@ -199,7 +213,11 @@ pub fn build() -> TokenStream { for (level_str, set_key) in &prof_data.trade_sets { let level = level_str.parse::().unwrap(); 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 amount = set.amount as i32; level_matches.push(quote! { #level => Some(VillagerTradeSet { trades: #trades_ident, amount: #amount }) }); diff --git a/pumpkin-codegen/src/wit/bedrock_packet.rs b/pumpkin-codegen/src/wit/bedrock_packet.rs index 724a9564e..69933fcf8 100644 --- a/pumpkin-codegen/src/wit/bedrock_packet.rs +++ b/pumpkin-codegen/src/wit/bedrock_packet.rs @@ -1,24 +1,45 @@ use crate::wit::utils::map_type; +use heck::ToKebabCase; +use semver::Version; use std::{fs, path::Path}; use syn::{Fields, Item}; -use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase}; -use semver::Version; -use heck::ToKebabCase; +use wit_encoder::{ + Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, + VariantCase, +}; 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 serverbound_variant = Variant::empty(); let mut clientbound_variant = Variant::empty(); // 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_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("clientbound-packet", TypeDefKind::Variant(clientbound_variant))); + interface.type_def(TypeDef::new( + "serverbound-packet", + TypeDefKind::Variant(serverbound_variant), + )); + interface.type_def(TypeDef::new( + "clientbound-packet", + TypeDefKind::Variant(clientbound_variant), + )); package.interface(interface); package.to_string() @@ -26,7 +47,9 @@ pub fn build() -> String { fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) { 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(); 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); 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); } } @@ -63,8 +88,14 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia } let wit_name = struct_name.to_kebab_case(); - interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list)))); - variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name))); + interface.type_def(TypeDef::new( + wit_name.clone(), + TypeDefKind::Record(Record::new(fields_list)), + )); + variant.case(VariantCase::value( + wit_name.clone(), + WitType::named(wit_name), + )); } } } diff --git a/pumpkin-codegen/src/wit/entity_type.rs b/pumpkin-codegen/src/wit/entity_type.rs index 39352e11c..c3a0cda5e 100644 --- a/pumpkin-codegen/src/wit/entity_type.rs +++ b/pumpkin-codegen/src/wit/entity_type.rs @@ -1,6 +1,6 @@ +use semver::Version; use std::collections::BTreeMap; use std::fs; -use semver::Version; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; pub fn build() -> String { @@ -8,7 +8,11 @@ pub fn build() -> String { serde_json::from_str(&fs::read_to_string("../assets/entities.json").unwrap()) .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 entity_type_enum = Enum::empty(); @@ -16,7 +20,10 @@ pub fn build() -> String { 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.to_string() diff --git a/pumpkin-codegen/src/wit/java_packet.rs b/pumpkin-codegen/src/wit/java_packet.rs index ca31e5d38..0adbeb451 100644 --- a/pumpkin-codegen/src/wit/java_packet.rs +++ b/pumpkin-codegen/src/wit/java_packet.rs @@ -1,24 +1,45 @@ use crate::wit::utils::map_type; +use heck::ToKebabCase; +use semver::Version; use std::{fs, path::Path}; use syn::{Fields, Item}; -use wit_encoder::{Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, VariantCase}; -use semver::Version; -use heck::ToKebabCase; +use wit_encoder::{ + Field, Interface, Package, PackageName, Record, Type as WitType, TypeDef, TypeDefKind, Variant, + VariantCase, +}; 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 serverbound_variant = Variant::empty(); let mut clientbound_variant = Variant::empty(); // 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_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("clientbound-packet", TypeDefKind::Variant(clientbound_variant))); + interface.type_def(TypeDef::new( + "serverbound-packet", + TypeDefKind::Variant(serverbound_variant), + )); + interface.type_def(TypeDef::new( + "clientbound-packet", + TypeDefKind::Variant(clientbound_variant), + )); package.interface(interface); package.to_string() @@ -26,11 +47,15 @@ pub fn build() -> String { fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) { 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(); 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); } } @@ -43,7 +68,11 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia for item in file.items { if let Item::Struct(s) = item { // 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; } @@ -59,9 +88,14 @@ fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Varia } let wit_name = struct_name.to_kebab_case(); - interface.type_def(TypeDef::new(wit_name.clone(), TypeDefKind::Record(Record::new(fields_list)))); - variant.case(VariantCase::value(wit_name.clone(), WitType::named(wit_name))); + interface.type_def(TypeDef::new( + wit_name.clone(), + TypeDefKind::Record(Record::new(fields_list)), + )); + variant.case(VariantCase::value( + wit_name.clone(), + WitType::named(wit_name), + )); } } } - diff --git a/pumpkin-codegen/src/wit/packet_mapping.rs b/pumpkin-codegen/src/wit/packet_mapping.rs index a67d2df93..7c8fec03e 100644 --- a/pumpkin-codegen/src/wit/packet_mapping.rs +++ b/pumpkin-codegen/src/wit/packet_mapping.rs @@ -1,6 +1,6 @@ +use heck::{ToPascalCase, ToSnakeCase}; use std::{fs, path::Path}; use syn::{Fields, Item}; -use heck::{ToPascalCase, ToSnakeCase}; pub fn build_java_mapping() -> String { 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 {\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(" }\n"); @@ -29,10 +36,19 @@ pub fn build_bedrock_mapping() -> String { 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("#[must_use]\n"); - output.push_str("pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option {\n"); + output.push_str( + "pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option {\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(" }\n"); @@ -40,9 +56,18 @@ pub fn build_bedrock_mapping() -> String { 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 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(); 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" { 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; } - 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); } } } -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 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(); - 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 mut prep_code = String::new(); @@ -82,7 +124,7 @@ fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_ let mut field_name = field.ident.as_ref().unwrap().to_string(); let wit_field = field_name.to_snake_case(); let (type_ident, is_ref) = get_type_info(&field.ty); - + if field_name == "type" { field_name = "r#type".to_string(); } @@ -135,11 +177,17 @@ fn parse_packet_file(path: &Path, output: &mut String, attr_name: &str, variant_ } else { possible = false; } - + 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(&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(" };\n"); output.push_str(" let mut buf = Vec::new();\n"); diff --git a/pumpkin-codegen/src/wit/particle.rs b/pumpkin-codegen/src/wit/particle.rs index 620869417..5bfacfeae 100644 --- a/pumpkin-codegen/src/wit/particle.rs +++ b/pumpkin-codegen/src/wit/particle.rs @@ -1,5 +1,5 @@ -use std::fs; use semver::Version; +use std::fs; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; pub fn build() -> String { @@ -7,7 +7,11 @@ pub fn build() -> String { serde_json::from_str(&fs::read_to_string("../assets/particles.json").unwrap()) .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 particle_enum = Enum::empty(); diff --git a/pumpkin-codegen/src/wit/sound.rs b/pumpkin-codegen/src/wit/sound.rs index 50cfb5e83..3b373b506 100644 --- a/pumpkin-codegen/src/wit/sound.rs +++ b/pumpkin-codegen/src/wit/sound.rs @@ -1,5 +1,5 @@ -use std::fs; use semver::Version; +use std::fs; use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; pub fn build() -> String { @@ -7,7 +7,11 @@ pub fn build() -> String { serde_json::from_str(&fs::read_to_string("../assets/sounds.json").unwrap()) .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 sound_enum = Enum::empty(); diff --git a/pumpkin-codegen/src/wit/utils.rs b/pumpkin-codegen/src/wit/utils.rs index 97e09312b..fbb162a26 100644 --- a/pumpkin-codegen/src/wit/utils.rs +++ b/pumpkin-codegen/src/wit/utils.rs @@ -1,5 +1,5 @@ -use wit_encoder::Type as WitType; use syn::{Type, TypePath}; +use wit_encoder::Type as WitType; pub fn map_type(ty: &Type) -> WitType { match ty { @@ -17,15 +17,17 @@ pub fn map_type(ty: &Type) -> WitType { "u16" | "i16" => WitType::S32, "Option" => { if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments - && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { - return WitType::option(map_type(inner_ty)); + && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() + { + return WitType::option(map_type(inner_ty)); } WitType::String } "Vec" | "Box" => { if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments - && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { - return WitType::list(map_type(inner_ty)); + && let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() + { + return WitType::list(map_type(inner_ty)); } WitType::String } diff --git a/pumpkin-data/src/generated/dimension.rs b/pumpkin-data/src/generated/dimension.rs index 60b1f70ea..dd2e0ea77 100644 --- a/pumpkin-data/src/generated/dimension.rs +++ b/pumpkin-data/src/generated/dimension.rs @@ -1,5 +1,10 @@ /* 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 id: u8, pub minecraft_name: &'static str, @@ -12,6 +17,8 @@ pub struct Dimension { pub logical_height: i32, pub infiniburn: &'static str, pub ambient_light: f32, + pub monster_spawn_light_level: IntProvider, + pub monster_spawn_block_light_limit: u8, pub sky_color: Option, pub fog_color: Option, pub cloud_color: Option, @@ -30,6 +37,13 @@ impl Dimension { logical_height: 384i32, infiniburn: "#minecraft:infiniburn_overworld", 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), fog_color: Some(12638463i32), cloud_color: None, @@ -47,6 +61,13 @@ impl Dimension { logical_height: 384i32, infiniburn: "#minecraft:infiniburn_overworld", 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), fog_color: Some(12638463i32), cloud_color: None, @@ -64,6 +85,8 @@ impl Dimension { logical_height: 256i32, infiniburn: "#minecraft:infiniburn_end", ambient_light: 0.25f32, + monster_spawn_light_level: IntProvider::Constant(15i32), + monster_spawn_block_light_limit: 0u8, sky_color: Some(0i32), fog_color: Some(1577752i32), cloud_color: None, @@ -81,6 +104,8 @@ impl Dimension { logical_height: 128i32, infiniburn: "#minecraft:infiniburn_nether", ambient_light: 0.1f32, + monster_spawn_light_level: IntProvider::Constant(7i32), + monster_spawn_block_light_limit: 15u8, sky_color: None, fog_color: None, cloud_color: None, diff --git a/pumpkin-world/benches/chunk_gen.rs b/pumpkin-world/benches/chunk_gen.rs index ce36cd7e2..d4aceaaa1 100644 --- a/pumpkin-world/benches/chunk_gen.rs +++ b/pumpkin-world/benches/chunk_gen.rs @@ -12,7 +12,7 @@ fn bench_full_chunk_generation(c: &mut Criterion) { let dimension = Dimension::OVERWORLD; let seed = Seed(42); 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); c.bench_function("full_chunk_generation", |b| { diff --git a/pumpkin-world/src/chunk/format/linear.rs b/pumpkin-world/src/chunk/format/linear.rs index 6dd0a880b..3c5ad0354 100644 --- a/pumpkin-world/src/chunk/format/linear.rs +++ b/pumpkin-world/src/chunk/format/linear.rs @@ -110,11 +110,11 @@ struct ChunkBitmap([u8; 128]); impl ChunkBitmap { const SIZE: usize = 128; - fn new() -> Self { + const fn new() -> Self { 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 bit = index % 8; if exists { @@ -125,7 +125,7 @@ impl ChunkBitmap { } #[allow(dead_code)] - fn get(&self, index: usize) -> bool { + const fn get(&self, index: usize) -> bool { let byte = index / 8; let bit = index % 8; (self.0[byte] >> bit) & 1 == 1 @@ -315,8 +315,8 @@ impl LinearV2File { AnvilChunkFile::::get_chunk_index(x, z) } - /// Number of buckets in the grid (grid_size²). - fn bucket_count(grid_size: u8) -> usize { + /// Number of buckets in the grid (`grid_size²`). + const fn bucket_count(grid_size: u8) -> usize { (grid_size as usize) * (grid_size as usize) } @@ -344,7 +344,7 @@ impl LinearV2File { } /// 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 bucket_row = bucket_idx / grid_size as usize; let bucket_col = bucket_idx % grid_size as usize; @@ -510,8 +510,8 @@ impl ChunkSerializer for LinearV2File { .map_err(|_| ChunkReadingError::RegionIsInvalid)?; decoder .read_to_end(&mut decompressed) - .map_err(|e| ChunkReadingError::IoError(e.kind()))?; - } + .map_err(|e| ChunkReadingError::IoError(e.kind()))? + }; buf.advance(compressed_size); let mut bucket_buf: Bytes = decompressed.into(); @@ -548,8 +548,7 @@ impl ChunkSerializer for LinearV2File { self.timestamps[index] = SystemTime::now() .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or(0); + .map_or(0, |d| d.as_secs()); self.chunks_data[index] = Some(chunk_raw); Ok(()) } diff --git a/pumpkin-world/src/chunk/io/file_manager.rs b/pumpkin-world/src/chunk/io/file_manager.rs index bba5fadc6..cff3a6789 100644 --- a/pumpkin-world/src/chunk/io/file_manager.rs +++ b/pumpkin-world/src/chunk/io/file_manager.rs @@ -377,9 +377,9 @@ where serializer .write(&path) .await - .map_err(|e| ChunkWritingError::IoError(e.kind()))?; + .map_err(|e| ChunkWritingError::IoError(e.kind()))? // Read-lock released here. - } + }; // Drop our handle so `can_remove` may succeed. drop(chunk_serializer); diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 96883a916..bd4478136 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -174,6 +174,7 @@ impl TryFrom for ChunkHeightmapType { } impl ChunkHeightmapType { + #[must_use] pub fn is_opaque(&self, block_state: &BlockState) -> bool { let block = Block::get_raw_id_from_state_id(block_state.id); match self { diff --git a/pumpkin-world/src/chunk/palette.rs b/pumpkin-world/src/chunk/palette.rs index 351ce659d..fe1192860 100644 --- a/pumpkin-world/src/chunk/palette.rs +++ b/pumpkin-world/src/chunk/palette.rs @@ -694,6 +694,7 @@ impl BlockPalette { ) } + #[must_use] pub fn to_disk_nbt(&self) -> ChunkSectionBlockStates { 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); diff --git a/pumpkin-world/src/chunk_system/channel.rs b/pumpkin-world/src/chunk_system/channel.rs index 12b212a07..d7ea86485 100644 --- a/pumpkin-world/src/chunk_system/channel.rs +++ b/pumpkin-world/src/chunk_system/channel.rs @@ -124,7 +124,10 @@ impl LevelChannel { ret } 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); self.notify.notify_one(); } diff --git a/pumpkin-world/src/chunk_system/chunk_state.rs b/pumpkin-world/src/chunk_system/chunk_state.rs index c995554af..5f4c70b21 100644 --- a/pumpkin-world/src/chunk_system/chunk_state.rs +++ b/pumpkin-world/src/chunk_system/chunk_state.rs @@ -330,7 +330,7 @@ impl Chunk { fluid_ticks: Default::default(), pending_block_entities: Mutex::new(pending_block_entities), 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()); diff --git a/pumpkin-world/src/chunk_system/generation.rs b/pumpkin-world/src/chunk_system/generation.rs index af99efe17..9c2dda80f 100644 --- a/pumpkin-world/src/chunk_system/generation.rs +++ b/pumpkin-world/src/chunk_system/generation.rs @@ -97,7 +97,7 @@ mod tests { let dimension = Dimension::OVERWORLD; let seed = Seed(42); 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 _ = generate_single_chunk( diff --git a/pumpkin-world/src/chunk_system/schedule.rs b/pumpkin-world/src/chunk_system/schedule.rs index 446cea71c..e9d8201f3 100644 --- a/pumpkin-world/src/chunk_system/schedule.rs +++ b/pumpkin-world/src/chunk_system/schedule.rs @@ -137,10 +137,7 @@ impl GenerationSchedule { } let max_in_flight = if gen_pool.is_some() { - (thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(1) - * 4) as u16 + (thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16 } else { gen_thread_count as u16 }; diff --git a/pumpkin-world/src/dimension.rs b/pumpkin-world/src/dimension.rs index 23023df89..67d811607 100644 --- a/pumpkin-world/src/dimension.rs +++ b/pumpkin-world/src/dimension.rs @@ -5,6 +5,7 @@ use pumpkin_data::dimension::Dimension; use crate::level::Level; +#[must_use] pub fn into_level( dimension: Dimension, level_config: &LevelConfig, diff --git a/pumpkin-world/src/generation/blender/blending_data.rs b/pumpkin-world/src/generation/blender/blending_data.rs index 1f60b10de..073d9c501 100644 --- a/pumpkin-world/src/generation/blender/blending_data.rs +++ b/pumpkin-world/src/generation/blender/blending_data.rs @@ -14,6 +14,7 @@ pub struct BlendingData { } impl BlendingData { + #[must_use] 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) { return f64::MAX; @@ -21,6 +22,7 @@ impl BlendingData { 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 { // cell_y is block_y / 8 in Blender.java // We'll need a better storage for this if we want full fidelity diff --git a/pumpkin-world/src/generation/blender/mod.rs b/pumpkin-world/src/generation/blender/mod.rs index f99e75379..5a91eb174 100644 --- a/pumpkin-world/src/generation/blender/mod.rs +++ b/pumpkin-world/src/generation/blender/mod.rs @@ -30,6 +30,7 @@ const DENSITY_BLENDING_RANGE_CELLS: i32 = 2; const DENSITY_BLENDING_RANGE_CHUNKS: i32 = 5 >> 2; // QuartPos.toSection(5) impl Blender { + #[must_use] pub fn empty() -> Self { Self { height_and_biome_blending_data: FxHashMap::default(), @@ -79,16 +80,22 @@ impl Blender { } } + #[must_use] pub fn is_empty(&self) -> bool { 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 { let cell_x = biome_coords::from_block(block_x); let cell_z = biome_coords::from_block(block_z); - let fixed_height = self - .get_blending_data_value(cell_x, 0, cell_z, |data, x, y, z| data.get_height(x, y, z)); + let fixed_height = self.get_blending_data_value( + cell_x, + 0, + cell_z, + blending_data::BlendingData::get_height, + ); if fixed_height != f64::MAX { return BlendingOutput { @@ -111,7 +118,7 @@ impl Blender { |test_cell_x, test_cell_z, height| { let dx = (cell_x - test_cell_x) 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 < closest_distance { @@ -151,6 +158,7 @@ impl Blender { / (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 { let cell_x = biome_coords::from_block(block_x); let cell_y = block_y / 8; @@ -263,6 +271,7 @@ impl Blender { } } + #[must_use] pub fn blend_biome( &self, quart_x: i32, @@ -284,7 +293,7 @@ impl Blender { |test_cell_x, test_cell_z, biome| { let dx = (quart_x - test_cell_x) 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 { diff --git a/pumpkin-world/src/generation/carver/cave.rs b/pumpkin-world/src/generation/carver/cave.rs index aec0301f7..986df3ddb 100644 --- a/pumpkin-world/src/generation/carver/cave.rs +++ b/pumpkin-world/src/generation/carver/cave.rs @@ -428,7 +428,7 @@ pub fn get_height(p: &HeightProvider, random: &mut RandomGenerator, min_y: i8, h HeightProvider::VeryBiasedToBottom(p) => { 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 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 max_rnd = random.next_inbetween_i32(min, min_rnd - 1); random.next_inbetween_i32(min, max_rnd - 1 + inner) diff --git a/pumpkin-world/src/generation/carver/mask.rs b/pumpkin-world/src/generation/carver/mask.rs index 4170c3dbb..5c84f0d37 100644 --- a/pumpkin-world/src/generation/carver/mask.rs +++ b/pumpkin-world/src/generation/carver/mask.rs @@ -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 let rel_x = x & 15; let rel_z = z & 15; diff --git a/pumpkin-world/src/generation/feature/features/iceberg.rs b/pumpkin-world/src/generation/feature/features/iceberg.rs index 7ef70d70c..916b528bc 100644 --- a/pumpkin-world/src/generation/feature/features/iceberg.rs +++ b/pumpkin-world/src/generation/feature/features/iceberg.rs @@ -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; if y_off > 0 && height - y_off <= 3 { c = shape_ellipse_c - (4 - (height - y_off)); diff --git a/pumpkin-world/src/generation/feature/features/sculk_patch.rs b/pumpkin-world/src/generation/feature/features/sculk_patch.rs index d05595ab7..803c7c304 100644 --- a/pumpkin-world/src/generation/feature/features/sculk_patch.rs +++ b/pumpkin-world/src/generation/feature/features/sculk_patch.rs @@ -130,7 +130,7 @@ struct SculkSpreader { } impl SculkSpreader { - fn new() -> Self { + const fn new() -> Self { Self { cursors: Vec::new(), } diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs index e93114b2d..b4cffe33c 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs @@ -143,8 +143,8 @@ impl CherryTrunkPlacer { tree_height as i32 - 1 + self.branch_end_offset_from_top.get(random); let extend_branch_away_from_trunk = middle_continues_upwards || branch_end_pos_offset < offset_from_origin; - let distance_to_trunk = self.branch_horizontal_length.get(random) - + if extend_branch_away_from_trunk { 1 } else { 0 }; + let distance_to_trunk = + self.branch_horizontal_length.get(random) + i32::from(extend_branch_away_from_trunk); let branch_end_pos = origin .add( diff --git a/pumpkin-world/src/generation/feature/features/twisting_vines.rs b/pumpkin-world/src/generation/feature/features/twisting_vines.rs index 0d7ba1c6b..de338095b 100644 --- a/pumpkin-world/src/generation/feature/features/twisting_vines.rs +++ b/pumpkin-world/src/generation/feature/features/twisting_vines.rs @@ -85,9 +85,8 @@ impl TwistingVinesFeature { // TODO: Set age property chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES.default_state); break; - } else { - chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES_PLANT.default_state); } + chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES_PLANT.default_state); current_pos = current_pos.up(); } } diff --git a/pumpkin-world/src/generation/noise/perlin.rs b/pumpkin-world/src/generation/noise/perlin.rs index 4e3635eee..eea185697 100644 --- a/pumpkin-world/src/generation/noise/perlin.rs +++ b/pumpkin-world/src/generation/noise/perlin.rs @@ -33,6 +33,7 @@ impl DoublePerlinNoiseSampler { ) } + #[must_use] pub fn get_amplitude(amplitudes: &[f64]) -> f64 { let mut j = i32::MAX; let mut k = i32::MIN; diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 61014c1c1..919a8b344 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -657,11 +657,11 @@ impl ProtoChunk { generator: &super::generator::VanillaGenerator, multi_noise_sampler: &mut MultiNoiseSampler, ) { - let dimension = generator.dimension; + let dimension = &generator.dimension; // 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) - } else if dimension == Dimension::THE_NETHER { + } else if dimension == &Dimension::THE_NETHER { ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER) } else { ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD) @@ -1114,6 +1114,7 @@ impl ProtoChunk { } } + #[must_use] pub fn get_allowed_biomes(set: &StructureSet) -> Vec { let mut allowed_biomes = Vec::new(); for entry in set.structures { diff --git a/pumpkin-world/src/generation/structure/structures/jungle_temple.rs b/pumpkin-world/src/generation/structure/structures/jungle_temple.rs index aea5654eb..77a0a6d7a 100644 --- a/pumpkin-world/src/generation/structure/structures/jungle_temple.rs +++ b/pumpkin-world/src/generation/structure/structures/jungle_temple.rs @@ -392,8 +392,8 @@ impl StructurePieceBase for JungleTemplePiece { }; self.piece.add_block(chunk, r_state, 9, -3, 5, bb); 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); /*if !self.placed_trap_2 { 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 { HorizontalFacing::North => props.north = NorthRedstone::Side, HorizontalFacing::South => props.south = SouthRedstone::Side, diff --git a/pumpkin-world/src/inventory/simple_inventory.rs b/pumpkin-world/src/inventory/simple_inventory.rs index 3292d0746..15923f30c 100644 --- a/pumpkin-world/src/inventory/simple_inventory.rs +++ b/pumpkin-world/src/inventory/simple_inventory.rs @@ -9,6 +9,7 @@ pub struct SimpleInventory { } impl SimpleInventory { + #[must_use] pub fn new(size: usize) -> Self { Self { stacks: (0..size) diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index ca33bffbd..b3d31225a 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -138,6 +138,7 @@ pub async fn dump() { } impl Level { + #[must_use] pub fn from_root_folder( level_config: &LevelConfig, root_folder: PathBuf, @@ -204,7 +205,7 @@ impl Level { should_save: AtomicBool::new(false), should_unload: AtomicBool::new(false), autosave_ticks: level_config.autosave_ticks, - pending_entity_generations: pending_entity_generations.clone(), + pending_entity_generations, level_channel: level_channel.clone(), thread_tracker, chunk_listener: listener.clone(), @@ -213,8 +214,7 @@ impl Level { // TODO let total_cores = thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(1) + .map_or(1, std::num::NonZero::get) .saturating_sub(2) .max(1); let threads_per_dimension = (total_cores / 2).max(1); @@ -253,9 +253,9 @@ impl Level { }); } else { // 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() - .name(format!("Entity Gen {:?}", pos)) + .name(format!("Entity Gen {pos:?}")) .spawn(move || { let arc_chunk = Arc::new(ChunkEntityData { x: pos.x, diff --git a/pumpkin-world/src/lighting/engine.rs b/pumpkin-world/src/lighting/engine.rs index 0b9cc0144..b43e52f1e 100644 --- a/pumpkin-world/src/lighting/engine.rs +++ b/pumpkin-world/src/lighting/engine.rs @@ -369,8 +369,10 @@ impl SkyLightPropagator { } } 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_idx < light_engine.sky_light.len() { light_engine.sky_light[section_idx] .set(local_x, local_y, local_z, 15); @@ -407,8 +409,10 @@ impl SkyLightPropagator { } } 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_idx < light_engine.sky_light.len() { light_engine.sky_light[section_idx] .set(local_x, local_y, local_z, light_val); diff --git a/pumpkin-world/src/lighting/runtime.rs b/pumpkin-world/src/lighting/runtime.rs index d25206a90..5e03d6529 100644 --- a/pumpkin-world/src/lighting/runtime.rs +++ b/pumpkin-world/src/lighting/runtime.rs @@ -482,7 +482,10 @@ impl DynamicLightEngine { let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; // 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() { return Err("Invalid section index".to_string()); } @@ -509,7 +512,10 @@ impl DynamicLightEngine { let section_index = (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) if section_index >= light_engine.sky_light.len() { return 15; @@ -536,7 +542,10 @@ impl DynamicLightEngine { let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; // 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() { return Err("Invalid section index".to_string()); } diff --git a/pumpkin-world/src/lighting/storage.rs b/pumpkin-world/src/lighting/storage.rs index a248f62b5..d178bcca5 100644 --- a/pumpkin-world/src/lighting/storage.rs +++ b/pumpkin-world/src/lighting/storage.rs @@ -13,7 +13,6 @@ const fn get_chunk_index(cache: &Cache, chunk_x: i32, chunk_z: i32) -> Option Option { let bottom = cache.bottom_y() as i32; 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) { let chunk_x = pos.0.x >> 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] { 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() { light_engine.block_light[section_y].set(x, y, z, level); 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] pub fn get_sky_light(cache: &Cache, pos: BlockPos) -> u8 { 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) { let chunk_x = pos.0.x >> 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] { 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() { light_engine.sky_light[section_y].set(x, y, z, level); c.dirty.store(true, std::sync::atomic::Ordering::Relaxed); diff --git a/pumpkin-world/src/tick/scheduler.rs b/pumpkin-world/src/tick/scheduler.rs index 84aaa3b6d..4299ac074 100644 --- a/pumpkin-world/src/tick/scheduler.rs +++ b/pumpkin-world/src/tick/scheduler.rs @@ -25,7 +25,10 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> { let next_offset = (current_offset + 1) % MAX_TICK_DELAY; 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 { 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) { - 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(|| { Box::new(ChunkTickSchedulerInner { 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 { self.inner .lock() - .unwrap_or_else(|e| e.into_inner()) + .unwrap_or_else(std::sync::PoisonError::into_inner) .as_ref() .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 { self.inner .lock() - .unwrap_or_else(|e| e.into_inner()) + .unwrap_or_else(std::sync::PoisonError::into_inner) .as_ref() .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] pub fn to_vec(&self) -> Vec> { 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 { return Vec::new(); }; @@ -112,7 +121,10 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator> let (lower, _) = iter.size_hint(); 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(|| { Box::new(ChunkTickSchedulerInner { tick_queue: std::array::from_fn(|_| Vec::new()), diff --git a/pumpkin/src/block/blocks/bed.rs b/pumpkin/src/block/blocks/bed.rs index a569d42c5..5e5cc6bb3 100644 --- a/pumpkin/src/block/blocks/bed.rs +++ b/pumpkin/src/block/blocks/bed.rs @@ -314,10 +314,11 @@ impl BlockBehaviour for BedBlock { if args .player .set_respawn_point( - args.world.dimension, + args.world.dimension.clone(), bed_head_pos, args.player.get_entity().yaw.load(), args.player.get_entity().pitch.load(), + false, ) .await { diff --git a/pumpkin/src/block/blocks/fire/fire.rs b/pumpkin/src/block/blocks/fire/fire.rs index b1a6bc957..42d6ca294 100644 --- a/pumpkin/src/block/blocks/fire/fire.rs +++ b/pumpkin/src/block/blocks/fire/fire.rs @@ -195,9 +195,9 @@ impl BlockBehaviour for FireBlock { 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 - if (dimension == Dimension::OVERWORLD || dimension == Dimension::THE_NETHER) + if (dimension == &Dimension::OVERWORLD || dimension == &Dimension::THE_NETHER) && let Some(portal) = NetherPortal::get_new_portal(args.world, args.position, HorizontalAxis::X).await { diff --git a/pumpkin/src/block/blocks/fire/mod.rs b/pumpkin/src/block/blocks/fire/mod.rs index 1a6a6c202..113ab8bdc 100644 --- a/pumpkin/src/block/blocks/fire/mod.rs +++ b/pumpkin/src/block/blocks/fire/mod.rs @@ -90,8 +90,8 @@ impl FireBlockBase { block_pos: &BlockPos, direction: BlockDirection, ) -> bool { - let dimension = world.dimension; - if dimension != Dimension::OVERWORLD && dimension != Dimension::THE_NETHER { + let dimension = &world.dimension; + if dimension != &Dimension::OVERWORLD && dimension != &Dimension::THE_NETHER { return false; } let mut found = false; diff --git a/pumpkin/src/command/commands/spawnpoint.rs b/pumpkin/src/command/commands/spawnpoint.rs index a6156ca6b..c222295c6 100644 --- a/pumpkin/src/command/commands/spawnpoint.rs +++ b/pumpkin/src/command/commands/spawnpoint.rs @@ -124,10 +124,10 @@ impl CommandExecutor for TargetsPosAngleExecutor { } async fn set_spawnpoint(sender: &CommandSender, target: &Arc, pos: BlockPos, yaw: f32) { - let dimension = target.world().dimension; + let dimension = &target.world().dimension; target - .set_respawn_point_forced(dimension, pos, yaw, 0.0) + .set_respawn_point(dimension.clone(), pos, yaw, 0.0, true) .await; sender diff --git a/pumpkin/src/entity/mob/bat.rs b/pumpkin/src/entity/mob/bat.rs index 8e8cda8b5..6341c9bcd 100644 --- a/pumpkin/src/entity/mob/bat.rs +++ b/pumpkin/src/entity/mob/bat.rs @@ -3,13 +3,16 @@ use std::sync::atomic::{AtomicBool, AtomicI32, Ordering::Relaxed}; use pumpkin_data::damage::DamageType; use pumpkin_data::sound::Sound; +use pumpkin_data::tag::{self, Taggable}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; +use pumpkin_world::chunk::ChunkHeightmapType; use rand::RngExt; use tokio::sync::Mutex; use crate::entity::mob::{Mob, MobEntity}; use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture}; +use crate::world::World; const ROOSTING_FLAG: u8 = 1; const CLOSE_PLAYER_DISTANCE: f64 = 4.0; @@ -39,6 +42,31 @@ impl BatEntity { 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 { self.roosting.load(Relaxed) } diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 65ebe80b4..ca541bb90 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -13,10 +13,13 @@ use pumpkin_data::meta_data_type::MetaDataType; use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::tracked_data::TrackedData; use pumpkin_protocol::java::client::play::{CHeadRot, CUpdateEntityRot, Metadata}; +use pumpkin_util::Difficulty; use pumpkin_util::math::boundingbox::BoundingBox; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::random::xoroshiro128::Xoroshiro; +use pumpkin_util::random::{RandomGenerator, get_seed}; use rand::RngExt; use std::pin::Pin; use std::sync::Arc; @@ -184,6 +187,44 @@ impl MobEntity { 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) { if self.living_entity.dead.load(Relaxed) { return; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index f282b87b5..6ff99e1dd 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -410,7 +410,7 @@ pub struct Player { /// The player's previous gamemode pub previous_gamemode: AtomicCell>, /// The player's spawnpoint - pub respawn_point: AtomicCell>, + pub respawn_point: Mutex>, /// The player's sleep status pub sleeping_since: AtomicCell>, /// Manages the player's breath level @@ -566,7 +566,7 @@ impl Player { gamemode: AtomicCell::new(gamemode), previous_gamemode: AtomicCell::new(None), // 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), // 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. @@ -1072,21 +1072,16 @@ impl Player { block_pos: BlockPos, yaw: f32, pitch: f32, + forced: 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 && block_pos == respawn_point.position { return false; } - self.respawn_point.store(Some(RespawnPoint { - dimension, - position: block_pos, - yaw, - force: false, - })); - self.client .send_packet_now(&CPlayerSpawnPosition::new( block_pos, @@ -1095,35 +1090,16 @@ impl Player { dimension.minecraft_name.to_owned(), )) .await; + + *self.respawn_point.lock().await = Some(RespawnPoint { + dimension, + position: block_pos, + yaw, + force: forced, + }); 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. /// /// 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 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 pos = &respawn_point.position; let (block, state_id) = world.get_block_and_state_id(pos).await; @@ -1172,7 +1149,7 @@ impl Player { position, yaw: respawn_point.yaw, pitch: 0.0, - dimension: respawn_point.dimension, + dimension: respawn_point.dimension.clone(), }); } return None; @@ -1192,7 +1169,7 @@ impl Player { position: spawn_pos, yaw: respawn_point.yaw, pitch: 0.0, - dimension: respawn_point.dimension, + dimension: respawn_point.dimension.clone(), }); } return None; @@ -1226,7 +1203,7 @@ impl Player { position: spawn_pos, yaw: respawn_point.yaw, pitch: 0.0, - dimension: respawn_point.dimension, + dimension: respawn_point.dimension.clone(), }); } return None; @@ -1512,10 +1489,11 @@ impl Player { pub async fn wake_up(&self) { let world = self.world(); - let respawn_point = self - .respawn_point - .load() - .expect("Player waking up should have it's respawn point set on the bed."); + let respawn_point = self.respawn_point.lock().await; + let Some(respawn_point) = respawn_point.as_ref() else { + warn!("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; 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)); 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_int(respawn.position.0.x); nbt.put_int(respawn.position.0.y); @@ -3796,15 +3774,15 @@ impl NBTStorage for Player { let dim = nbt .get_string() .ok() - .and_then(|s| Dimension::from_name(s.as_str()).copied()) - .unwrap_or(self.world().dimension); + .and_then(|s| Dimension::from_name(s.as_str()).cloned()) + .unwrap_or(self.world().dimension.clone()); let force = nbt.get_bool().unwrap_or(false); - self.respawn_point.store(Some(RespawnPoint { + *self.respawn_point.lock().await = Some(RespawnPoint { dimension: dim, position: BlockPos(Vector3::new(x, y, z)), yaw: 0.0, force, - })); + }); } self.enchantment_seed .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). -#[derive(Copy, Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct RespawnPoint { pub dimension: Dimension, pub position: BlockPos, @@ -4176,9 +4154,6 @@ pub struct RespawnPoint { pub force: bool, } -/// Calculated respawn position ready for use. -/// Returned by `calculate_respawn_point()`. -#[derive(Debug, Clone)] pub struct CalculatedRespawnPoint { /// The exact position to spawn at (centered in block). pub position: Vector3, diff --git a/pumpkin/src/entity/type.rs b/pumpkin/src/entity/type.rs index 459c57cba..0181b273d 100644 --- a/pumpkin/src/entity/type.rs +++ b/pumpkin/src/entity/type.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use pumpkin_data::entity::EntityType; +use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; use uuid::Uuid; @@ -13,7 +14,7 @@ use crate::entity::experience_orb::ExperienceOrbEntity; use crate::entity::falling::FallingEntity; use crate::entity::item::ItemEntity; 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::breeze::BreezeEntity; 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::tnt::TNTEntity; use crate::entity::vehicle::boat::BoatEntity; -use crate::entity::{Entity, EntityBase}; +use crate::entity::{Entity, EntityBase, mob}; use crate::world::World; use pumpkin_data::Block; use pumpkin_data::item::Item; @@ -304,3 +305,41 @@ pub async fn from_type( 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 +} diff --git a/pumpkin/src/item/items/map.rs b/pumpkin/src/item/items/map.rs index 38d18dd54..5e1a3cc6a 100644 --- a/pumpkin/src/item/items/map.rs +++ b/pumpkin/src/item/items/map.rs @@ -45,7 +45,7 @@ impl ItemBehaviour for MapItem { let map_id = server.next_map_id(); let _ = server.map_manager.create_map( map_id, - player.world().dimension, + player.world().dimension.clone(), player.position().x as i32, player.position().z as i32, 0, // Default scale diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 13368534c..734ec8740 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -286,7 +286,7 @@ impl Server { .color_named(NamedColor::DarkGreen) .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 portal: Arc = Arc::new(WorldPortal( world.clone(), @@ -383,8 +383,13 @@ impl Server { let seed = server.level_info.load().world_gen_settings.seed; // TODO: gen_pool should be reused - let level = - pumpkin_world::dimension::into_level(dimension, &config, world_path, seed, None); + let level = pumpkin_world::dimension::into_level( + dimension.clone(), + &config, + world_path, + seed, + None, + ); let world: World = World::load(level.clone(), l_info, dimension, registry, weak); let world = Arc::new(world); let portal: Arc = Arc::new(WorldPortal( diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 9db865310..2117aa16e 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -150,7 +150,7 @@ pub mod weather; use crate::world::natural_spawner::{SpawnState, spawn_for_chunk}; use pumpkin_config::lighting::LightingEngineConfig; use pumpkin_data::effect::StatusEffect; -use pumpkin_world::chunk::ChunkHeightmapType::MotionBlocking; +use pumpkin_world::chunk::ChunkHeightmapType::{self, MotionBlocking}; use uuid::Uuid; use weather::Weather; @@ -241,7 +241,8 @@ impl World { // 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 dragon_fight = + (dimension == Dimension::THE_END).then(|| Mutex::new(dragon_fight::DragonFight::new())); Self { uuid: Uuid::new_v4(), level, @@ -259,8 +260,7 @@ impl World { synced_block_event_queue: Mutex::new(Vec::new()), unsent_block_changes: Mutex::new(HashMap::new()), portal_poi: Mutex::new(portal_poi), - dragon_fight: (dimension == Dimension::THE_END) - .then(|| Mutex::new(dragon_fight::DragonFight::new())), + dragon_fight, spawn_state: ArcSwap::new(Arc::new(SpawnState::empty())), active_chunks: ArcSwap::new(Arc::new(FxHashSet::default())), server, @@ -1574,6 +1574,24 @@ impl World { .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)] pub async fn spawn_bedrock_player( &self, @@ -1884,7 +1902,7 @@ impl World { false, true, false, - self.dimension, + self.dimension.clone(), biome::hash_seed(self.level.seed.0), // seed gamemode as u8, player @@ -2427,7 +2445,7 @@ impl World { ), spawn_yaw, spawn_pitch, - self.dimension, + self.dimension.clone(), ) }; @@ -3424,6 +3442,26 @@ impl World { 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 { + 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( &self, block: &Block, diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index d0462cd34..612d1febb 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -1,5 +1,5 @@ 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 arc_swap::ArcSwap; 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 { 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 if !world .is_space_empty(BoundingBox::new_from_pos(