feat: inital mob spawn rules
This commit is contained in:
parent
c23c7c429d
commit
2bfe2335f0
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<i64>,
|
||||
@ -47,26 +50,6 @@ pub struct Dimension {
|
||||
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.
|
||||
pub fn build() -> TokenStream {
|
||||
let dimensions: BTreeMap<String, Dimension> = 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<i32>,
|
||||
pub fog_color: Option<i32>,
|
||||
pub cloud_color: Option<i32>,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -8,19 +8,22 @@ pub fn build() -> TokenStream {
|
||||
let json: BTreeMap<String, DoublePerlinNoiseParametersCodec> =
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +148,6 @@ impl ParsedMappings {
|
||||
for (mapped_id, id) in (shifts_to[index]..).zip(*from..to) {
|
||||
result[id as usize] = mapped_id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<i32>().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 }) });
|
||||
|
||||
@ -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),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<Bytes> {\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<Bytes> {\n");
|
||||
output.push_str(
|
||||
"pub fn serialize_bedrock_packet(packet: &BClientboundPacket) -> Option<Bytes> {\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");
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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<i32>,
|
||||
pub fog_color: Option<i32>,
|
||||
pub cloud_color: Option<i32>,
|
||||
@ -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,
|
||||
|
||||
@ -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| {
|
||||
|
||||
@ -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<S: SingleChunkDataSerializer> LinearV2File<S> {
|
||||
AnvilChunkFile::<S>::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<S: SingleChunkDataSerializer> LinearV2File<S> {
|
||||
}
|
||||
|
||||
/// 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<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
.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<S: SingleChunkDataSerializer> ChunkSerializer for LinearV2File<S> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -174,6 +174,7 @@ impl TryFrom<usize> 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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ use pumpkin_data::dimension::Dimension;
|
||||
|
||||
use crate::level::Level;
|
||||
|
||||
#[must_use]
|
||||
pub fn into_level(
|
||||
dimension: Dimension,
|
||||
level_config: &LevelConfig,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -130,7 +130,7 @@ struct SculkSpreader {
|
||||
}
|
||||
|
||||
impl SculkSpreader {
|
||||
fn new() -> Self {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
cursors: Vec::new(),
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<u16> {
|
||||
let mut allowed_biomes = Vec::new();
|
||||
for entry in set.structures {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -9,6 +9,7 @@ pub struct SimpleInventory {
|
||||
}
|
||||
|
||||
impl SimpleInventory {
|
||||
#[must_use]
|
||||
pub fn new(size: usize) -> Self {
|
||||
Self {
|
||||
stacks: (0..size)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_section_y(cache: &Cache, pos_y: i32) -> Option<usize> {
|
||||
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);
|
||||
|
||||
@ -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<ScheduledTick<&'a T>> {
|
||||
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<ScheduledTick<&'a T>>
|
||||
|
||||
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()),
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -124,10 +124,10 @@ impl CommandExecutor for TargetsPosAngleExecutor {
|
||||
}
|
||||
|
||||
async fn set_spawnpoint(sender: &CommandSender, target: &Arc<Player>, 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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -410,7 +410,7 @@ pub struct Player {
|
||||
/// The player's previous gamemode
|
||||
pub previous_gamemode: AtomicCell<Option<GameMode>>,
|
||||
/// The player's spawnpoint
|
||||
pub respawn_point: AtomicCell<Option<RespawnPoint>>,
|
||||
pub respawn_point: Mutex<Option<RespawnPoint>>,
|
||||
/// The player's sleep status
|
||||
pub sleeping_since: AtomicCell<Option<u8>>,
|
||||
/// 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<f64>,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<dyn WorldPortalExt> = 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<dyn WorldPortalExt> = Arc::new(WorldPortal(
|
||||
|
||||
@ -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<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(
|
||||
&self,
|
||||
block: &Block,
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user