feat: add infested blocks & silverfish spawn

ofc also includes a bunch of other things like i always do :D
This commit is contained in:
Alexander Medvedev 2026-02-05 19:34:04 +01:00
parent 707d1e9a92
commit 53803c5e94
34 changed files with 295 additions and 198 deletions

20
Cargo.lock generated
View File

@ -263,9 +263,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]] [[package]]
name = "cast" name = "cast"
@ -275,9 +275,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.54" version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -932,9 +932,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -3269,9 +3269,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "ureq" name = "ureq"
version = "3.1.4" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"cookie_store", "cookie_store",
@ -3898,6 +3898,6 @@ dependencies = [
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.17" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"

View File

@ -167,4 +167,4 @@ time = "0.3"
arc-swap = "1.8" arc-swap = "1.8"
tokio-util = "0.7.18" tokio-util = "0.7.18"
toml = "0.9" toml = "0.9"
ureq = "3.1.4" ureq = "3.2.0"

View File

@ -1,4 +1,4 @@
FROM rust:1-alpine3.22 AS builder FROM rust:1-alpine3.23 AS builder
ENV RUSTFLAGS="-C target-feature=-crt-static" ENV RUSTFLAGS="-C target-feature=-crt-static"
RUN apk add --no-cache musl-dev \ RUN apk add --no-cache musl-dev \
# Required for git-version # Required for git-version

View File

@ -777,7 +777,7 @@ pub(crate) fn build() -> TokenStream {
impl FromResourceLocation for &'static Fluid { impl FromResourceLocation for &'static Fluid {
fn from_resource_location(resource_location: &ResourceLocation) -> Option<Self> { fn from_resource_location(resource_location: &ResourceLocation) -> Option<Self> {
Fluid::from_registry_key(resource_location.strip_prefix(resource_location).unwrap_or(resource_location)) Fluid::from_registry_key(resource_location.strip_prefix("minecraft:").unwrap_or(resource_location))
} }
} }

View File

@ -11,7 +11,6 @@ pub struct Packets {
} }
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
// 1. Track the JSON assets for rebuilds
println!("cargo:rerun-if-changed=../assets/packet/1_21_7_packets.json"); println!("cargo:rerun-if-changed=../assets/packet/1_21_7_packets.json");
println!("cargo:rerun-if-changed=../assets/packet/1_21_11_packets.json"); println!("cargo:rerun-if-changed=../assets/packet/1_21_11_packets.json");

View File

@ -3,7 +3,6 @@ use quote::{format_ident, quote};
use std::{collections::BTreeMap, fs}; use std::{collections::BTreeMap, fs};
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
// 1. Track files
println!("cargo:rerun-if-changed=../assets/tracked_data/1_21_7_tracked_data.json"); println!("cargo:rerun-if-changed=../assets/tracked_data/1_21_7_tracked_data.json");
println!("cargo:rerun-if-changed=../assets/tracked_data/1_21_11_tracked_data.json"); println!("cargo:rerun-if-changed=../assets/tracked_data/1_21_11_tracked_data.json");

View File

@ -99,7 +99,7 @@ impl FromResourceLocation for &'static Block {
fn from_resource_location(resource_location: &ResourceLocation) -> Option<Self> { fn from_resource_location(resource_location: &ResourceLocation) -> Option<Self> {
Block::from_registry_key( Block::from_registry_key(
resource_location resource_location
.strip_prefix(resource_location) .strip_prefix("minecraft:")
.unwrap_or(resource_location), .unwrap_or(resource_location),
) )
} }

View File

@ -26,32 +26,25 @@ impl PacketWrite for CLevelChunk<'_> {
VarInt(self.chunk.z).write(writer)?; VarInt(self.chunk.z).write(writer)?;
VarInt(self.dimension).write(writer)?; VarInt(self.dimension).write(writer)?;
let sub_chunk_count = self.chunk.section.sections.lock().unwrap().len() as u32; let sub_chunk_count = self.chunk.section.count as u32;
assert_eq!(sub_chunk_count, 24); debug_assert_eq!(sub_chunk_count, 24);
VarUInt(sub_chunk_count).write(writer)?; VarUInt(sub_chunk_count).write(writer)?;
self.cache_enabled.write(writer)?; self.cache_enabled.write(writer)?;
let mut chunk_data = Vec::new(); let mut chunk_data = Vec::new();
let data_write = &mut chunk_data; let data_write = &mut chunk_data;
let min_y = (self.chunk.section.min_y >> 4) as i8; let block_sections = self.chunk.section.block_sections.read().unwrap();
let min_y_section = (self.chunk.section.min_y >> 4) as i8;
// Blocks for (i, block_palette) in block_sections.iter().enumerate() {
for (i, sub_chunk) in self // Version 9: [version:byte][num_storages:byte][sub_chunk_index:byte]
.chunk let y = (i as i8) + min_y_section;
.section
.sections
.lock()
.unwrap()
.iter()
.enumerate()
{
// Version 9
// [version:byte][num_storages:byte][sub_chunk_index:byte][block storage1]...[blockStorageN]
let y = i as i8 + min_y;
let num_storages = 1; let num_storages = 1;
data_write.write_all(&[VERSION, num_storages, y as _])?; data_write.write_all(&[VERSION, num_storages, y as u8])?;
let network_repr = sub_chunk.block_states.convert_be_network();
let network_repr = block_palette.convert_be_network();
(network_repr.bits_per_entry << 1 | 1).write(data_write)?; (network_repr.bits_per_entry << 1 | 1).write(data_write)?;
for data in network_repr.packed_data { for data in network_repr.packed_data {
@ -72,14 +65,14 @@ impl PacketWrite for CLevelChunk<'_> {
} }
} }
// Biomes for i in 0..self.chunk.section.count {
for i in 0..sub_chunk_count {
let num_storages = 1; let num_storages = 1;
let y = i as i8 + min_y; let y = (i as i8) + min_y_section;
data_write.write_all(&[VERSION, num_storages, y as _])?; data_write.write_all(&[VERSION, num_storages, y as u8])?;
for _ in 0..num_storages { for _ in 0..num_storages {
1u8.write(data_write)?; 1u8.write(data_write)?;
// TODO
VarInt(0).write(data_write)?; VarInt(0).write(data_write)?;
} }
} }

View File

@ -50,15 +50,17 @@ impl ClientPacket for CChunkData<'_> {
{ {
let mut blocks_and_biomes_buf = Vec::new(); let mut blocks_and_biomes_buf = Vec::new();
for section in self.0.section.sections.lock().unwrap().iter() { let block_sections = self.0.section.block_sections.read().unwrap();
// Block count let biome_sections = self.0.section.biome_sections.read().unwrap();
let non_empty_block_count = section.block_states.non_air_block_count() as i16;
for (block_palette, biome_palette) in block_sections.iter().zip(biome_sections.iter()) {
let non_empty_block_count = block_palette.non_air_block_count() as i16;
blocks_and_biomes_buf.write_i16_be(non_empty_block_count)?; blocks_and_biomes_buf.write_i16_be(non_empty_block_count)?;
// This is a bit messy, but we dont have access to VarInt in pumpkin-world let block_network = block_palette.convert_network();
let network_repr = section.block_states.convert_network(); blocks_and_biomes_buf.write_u8(block_network.bits_per_entry)?;
blocks_and_biomes_buf.write_u8(network_repr.bits_per_entry)?;
match network_repr.palette { match block_network.palette {
NetworkPalette::Single(registry_id) => { NetworkPalette::Single(registry_id) => {
blocks_and_biomes_buf.write_var_int(&registry_id.into())?; blocks_and_biomes_buf.write_var_int(&registry_id.into())?;
} }
@ -78,13 +80,14 @@ impl ClientPacket for CChunkData<'_> {
NetworkPalette::Direct => {} NetworkPalette::Direct => {}
} }
for packed in network_repr.packed_data { for packed in block_network.packed_data {
blocks_and_biomes_buf.write_i64_be(packed)?; blocks_and_biomes_buf.write_i64_be(packed)?;
} }
let network_repr = section.biomes.convert_network(); let biome_network = biome_palette.convert_network();
blocks_and_biomes_buf.write_u8(network_repr.bits_per_entry)?; blocks_and_biomes_buf.write_u8(biome_network.bits_per_entry)?;
match network_repr.palette {
match biome_network.palette {
NetworkPalette::Single(registry_id) => { NetworkPalette::Single(registry_id) => {
blocks_and_biomes_buf.write_var_int(&registry_id.into())?; blocks_and_biomes_buf.write_var_int(&registry_id.into())?;
} }
@ -104,12 +107,11 @@ impl ClientPacket for CChunkData<'_> {
NetworkPalette::Direct => {} NetworkPalette::Direct => {}
} }
// NOTE: Not updated in wiki; i64 array length is now determined by the bits per entry for packed in biome_network.packed_data {
//data_buf.write_var_int(&network_repr.packed_data.len().into())?;
for packed in network_repr.packed_data {
blocks_and_biomes_buf.write_i64_be(packed)?; blocks_and_biomes_buf.write_i64_be(packed)?;
} }
} }
write.write_var_int(&blocks_and_biomes_buf.len().try_into().map_err(|_| { write.write_var_int(&blocks_and_biomes_buf.len().try_into().map_err(|_| {
WritingError::Message(format!( WritingError::Message(format!(
"{} is not representable as a VarInt!", "{} is not representable as a VarInt!",

View File

@ -2,7 +2,10 @@ use std::{
io::Cursor, io::Cursor,
path::PathBuf, path::PathBuf,
pin::Pin, pin::Pin,
sync::atomic::{AtomicBool, Ordering}, sync::{
RwLock,
atomic::{AtomicBool, Ordering},
},
}; };
use bytes::Bytes; use bytes::Bytes;
@ -28,7 +31,7 @@ use pumpkin_util::math::vector2::Vector2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ use super::{
ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections, SubChunk, ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections,
palette::{BiomePalette, BlockPalette}, palette::{BiomePalette, BlockPalette},
}; };
use crate::block::BlockStateCodec; use crate::block::BlockStateCodec;
@ -123,51 +126,57 @@ impl ChunkData {
position.x, position.y, chunk_data.x_pos, chunk_data.z_pos, position.x, position.y, chunk_data.x_pos, chunk_data.z_pos,
))); )));
} }
let (block_lights, sky_lights, sub_chunks) = chunk_data let (block_lights, sky_lights, block_palettes, biome_palettes) = chunk_data
.sections .sections
.into_iter() .into_iter()
.map(|section| { .map(|section| {
// Map light data to the LightContainer enum
let block_light = section let block_light = section
.block_light .block_light
.map(LightContainer::new) .map(LightContainer::Full)
.unwrap_or_default(); .unwrap_or(LightContainer::Empty(0)); // Standard default
let sky_light = section let sky_light = section
.sky_light .sky_light
.map(LightContainer::new) .map(LightContainer::Full)
.unwrap_or(LightContainer::Empty(15)); // Sky is usually bright
// Convert NBT to Palettes
let block_palette = section
.block_states
.map(BlockPalette::from_disk_nbt)
.unwrap_or_default();
let biome_palette = section
.biomes
.map(BiomePalette::from_disk_nbt)
.unwrap_or_default(); .unwrap_or_default();
let sub_chunk = SubChunk { (block_light, sky_light, block_palette, biome_palette)
block_states: section
.block_states
.map(BlockPalette::from_disk_nbt)
.unwrap_or_default(),
biomes: section
.biomes
.map(BiomePalette::from_disk_nbt)
.unwrap_or_default(),
};
(block_light, sky_light, sub_chunk)
}) })
.fold( .fold(
(Vec::new(), Vec::new(), Vec::new()), (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
|(mut bl, mut sl, mut sc), (block_l, sky_l, sub_c)| { |(mut bl, mut sl, mut bp, mut bip), (b_l, s_l, b_p, bi_p)| {
bl.push(block_l); bl.push(b_l);
sl.push(sky_l); sl.push(s_l);
sc.push(sub_c); bp.push(b_p);
(bl, sl, sc) bip.push(bi_p);
(bl, sl, bp, bip)
}, },
); );
// 2. Assemble the final structs using the collected vectors. // 2. Assemble the LightEngine
let light_engine = ChunkLight { let light_engine = ChunkLight {
block_light: block_lights.into_boxed_slice(), block_light: block_lights.into_boxed_slice(),
sky_light: sky_lights.into_boxed_slice(), sky_light: sky_lights.into_boxed_slice(),
}; };
let min_y = section_coords::section_to_block(chunk_data.min_y_section);
let section =
ChunkSections::new(std::sync::Mutex::new(sub_chunks.into_boxed_slice()), min_y);
// 3. Assemble the ChunkSections using your specific struct fields
let min_y = section_coords::section_to_block(chunk_data.min_y_section);
let section = ChunkSections {
count: block_palettes.len(),
block_sections: RwLock::new(block_palettes.into_boxed_slice()),
biome_sections: RwLock::new(biome_palettes.into_boxed_slice()),
min_y,
};
Ok(Self { Ok(Self {
section, section,
heightmap: std::sync::Mutex::new(chunk_data.heightmaps), heightmap: std::sync::Mutex::new(chunk_data.heightmaps),
@ -193,42 +202,38 @@ impl ChunkData {
} }
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> { async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
// 1. Process sections (Standard Mutex) let sections: Vec<ChunkSectionNBT> = {
// We do this in a block or just let the guard drop naturally let block_lock = self.section.block_sections.read().unwrap();
// before the first .await let biome_lock = self.section.biome_sections.read().unwrap();
let sections: Vec<_> = { let min_section_y = (self.section.min_y >> 4) as i8;
let sections_guard = self.section.sections.lock().unwrap();
sections_guard (0..self.section.count)
.iter() .map(|i| {
.enumerate() ChunkSectionNBT {
.map(|(i, section)| ChunkSectionNBT { y: i as i8 + min_section_y,
y: (i as i8) + section_coords::block_to_section(self.section.min_y) as i8, // Convert the palettes to their NBT disk representation
block_states: Some(section.block_states.to_disk_nbt()), block_states: Some(block_lock[i].to_disk_nbt()),
biomes: Some(section.biomes.to_disk_nbt()), biomes: Some(biome_lock[i].to_disk_nbt()),
block_light: match self.light_engine.block_light[i].clone() { block_light: match self.light_engine.block_light.get(i) {
LightContainer::Empty(_) => None, Some(LightContainer::Full(data)) => Some(data.clone()),
LightContainer::Full(data) => Some(data), _ => None,
}, },
sky_light: match self.light_engine.sky_light[i].clone() { sky_light: match self.light_engine.sky_light.get(i) {
LightContainer::Empty(_) => None, Some(LightContainer::Full(data)) => Some(data.clone()),
LightContainer::Full(data) => Some(data), _ => None,
}, },
}
}) })
.collect() .collect()
}; };
// 2. Clone heightmaps (Standard Mutex)
let heightmaps = self.heightmap.lock().unwrap().clone(); let heightmaps = self.heightmap.lock().unwrap().clone();
// 3. Prepare block entities (CRITICAL: Must drop lock before .await)
let entities_to_serialize = { let entities_to_serialize = {
// We clone the Arcs out of the map while holding the lock
let entities_guard = self.block_entities.lock().unwrap(); let entities_guard = self.block_entities.lock().unwrap();
entities_guard.values().cloned().collect::<Vec<_>>() entities_guard.values().cloned().collect::<Vec<_>>()
}; };
// entities_guard drops here automatically!
// 4. Now we can safely .await because no MutexGuards are held
let block_entities_nbt = join_all(entities_to_serialize.into_iter().map( let block_entities_nbt = join_all(entities_to_serialize.into_iter().map(
|block_entity| async move { |block_entity| async move {
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();

View File

@ -15,8 +15,8 @@ use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Write; use std::fmt::Write;
use std::ops::{BitAnd, BitOr}; use std::ops::{BitAnd, BitOr};
use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::{Arc, RwLock};
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -102,7 +102,9 @@ pub struct ChunkEntityData {
/// A chunk can be: /// A chunk can be:
/// - Subchunks: 24 separate subchunks are stored. /// - Subchunks: 24 separate subchunks are stored.
pub struct ChunkSections { pub struct ChunkSections {
pub sections: std::sync::Mutex<Box<[SubChunk]>>, pub count: usize,
pub block_sections: RwLock<Box<[BlockPalette]>>,
pub biome_sections: RwLock<Box<[BiomePalette]>>,
pub min_y: i32, pub min_y: i32,
} }
@ -112,8 +114,8 @@ impl ChunkSections {
pub fn dump_blocks(&self) -> Vec<u16> { pub fn dump_blocks(&self) -> Vec<u16> {
// TODO: this is not optimal, we could use rust iters // TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in self.sections.lock().unwrap().iter() { for section in self.block_sections.read().unwrap().iter() {
section.block_states.for_each(|raw_id| { section.for_each(|raw_id| {
dump.push(raw_id); dump.push(raw_id);
}); });
} }
@ -125,8 +127,8 @@ impl ChunkSections {
pub fn dump_biomes(&self) -> Vec<u8> { pub fn dump_biomes(&self) -> Vec<u8> {
// TODO: this is not optimal, we could use rust iters // TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in self.sections.lock().unwrap().iter() { for section in self.biome_sections.read().unwrap().iter() {
section.biomes.for_each(|raw_id| { section.for_each(|raw_id| {
dump.push(raw_id); dump.push(raw_id);
}); });
} }
@ -134,12 +136,6 @@ impl ChunkSections {
} }
} }
#[derive(Default, Clone)]
pub struct SubChunk {
pub block_states: BlockPalette,
pub biomes: BiomePalette,
}
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct ChunkLight { pub struct ChunkLight {
pub sky_light: Box<[LightContainer]>, pub sky_light: Box<[LightContainer]>,
@ -292,8 +288,16 @@ impl Default for ChunkHeightmaps {
impl ChunkSections { impl ChunkSections {
#[must_use] #[must_use]
pub const fn new(sections: std::sync::Mutex<Box<[SubChunk]>>, min_y: i32) -> Self { pub fn new(num_sections: usize, min_y: i32) -> Self {
Self { sections, min_y } let block_sections = vec![BlockPalette::default(); num_sections].into_boxed_slice();
let biome_sections = vec![BiomePalette::default(); num_sections].into_boxed_slice();
Self {
count: num_sections,
block_sections: RwLock::new(block_sections),
biome_sections: RwLock::new(biome_sections),
min_y,
}
} }
#[must_use] #[must_use]
@ -362,11 +366,11 @@ impl ChunkSections {
let section_index = relative_y / BlockPalette::SIZE; let section_index = relative_y / BlockPalette::SIZE;
let relative_y = relative_y % BlockPalette::SIZE; let relative_y = relative_y % BlockPalette::SIZE;
self.sections self.block_sections
.lock() .read()
.unwrap() .unwrap()
.get(section_index) .get(section_index)
.map(|section| section.block_states.get(relative_x, relative_y, relative_z)) .map(|section| section.get(relative_x, relative_y, relative_z))
} }
/// Sets the given block in the chunk, returning the old block state ID /// Sets the given block in the chunk, returning the old block state ID
@ -399,10 +403,8 @@ impl ChunkSections {
let section_index = relative_y / BlockPalette::SIZE; let section_index = relative_y / BlockPalette::SIZE;
let relative_y = relative_y % BlockPalette::SIZE; let relative_y = relative_y % BlockPalette::SIZE;
if let Some(section) = self.sections.lock().unwrap().get_mut(section_index) { if let Some(section) = self.block_sections.write().unwrap().get_mut(section_index) {
return section return section.set(relative_x, relative_y, relative_z, block_state_id);
.block_states
.set(relative_x, relative_y, relative_z, block_state_id);
} }
0 0
} }
@ -419,10 +421,8 @@ impl ChunkSections {
let section_index = relative_y / BiomePalette::SIZE; let section_index = relative_y / BiomePalette::SIZE;
let relative_y = relative_y % BiomePalette::SIZE; let relative_y = relative_y % BiomePalette::SIZE;
if let Some(section) = self.sections.lock().unwrap().get_mut(section_index) { if let Some(section) = self.biome_sections.write().unwrap().get_mut(section_index) {
section section.set(relative_x, relative_y, relative_z, biome_id);
.biomes
.set(relative_x, relative_y, relative_z, biome_id);
} }
} }
@ -436,11 +436,11 @@ impl ChunkSections {
) -> Option<u8> { ) -> Option<u8> {
debug_assert!(scale_x < BiomePalette::SIZE); debug_assert!(scale_x < BiomePalette::SIZE);
debug_assert!(scale_z < BiomePalette::SIZE); debug_assert!(scale_z < BiomePalette::SIZE);
self.sections self.biome_sections
.lock() .read()
.unwrap() .unwrap()
.get(index) .get(index)
.map(|section| section.biomes.get(scale_x, scale_y, scale_z)) .map(|section| section.get(scale_x, scale_y, scale_z))
} }
#[must_use] #[must_use]
@ -582,13 +582,15 @@ impl ChunkData {
#[must_use] #[must_use]
pub fn get_highest_non_empty_subchunk(&self) -> usize { pub fn get_highest_non_empty_subchunk(&self) -> usize {
let sections = self.section.sections.lock().unwrap(); self.section
sections .block_sections
.read()
.unwrap()
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev()
.position(|(_, sub)| !sub.block_states.has_only_air()) .find(|(_, sub)| !sub.has_only_air())
.map_or(0, |p| sections.len() - 1 - p) .map_or(0, |(idx, _)| idx)
} }
} }

View File

@ -8,7 +8,7 @@ TODO
use crate::block::RawBlockState; use crate::block::RawBlockState;
use crate::chunk::io::LoadedData::Loaded; use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk}; use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections};
use crate::generation::biome_coords; use crate::generation::biome_coords;
use pumpkin_data::block_properties::is_air; use pumpkin_data::block_properties::is_air;
use pumpkin_data::chunk_gen_settings::GenerationSettings; use pumpkin_data::chunk_gen_settings::GenerationSettings;
@ -787,10 +787,7 @@ impl Chunk {
let proto_chunk = self.get_proto_chunk(); let proto_chunk = self.get_proto_chunk();
let total_sections = dimension.height as usize / 16; let total_sections = dimension.height as usize / 16;
let sections = ChunkSections::new( let sections = ChunkSections::new(total_sections, dimension.min_y);
Mutex::new(vec![SubChunk::default(); total_sections].into_boxed_slice()),
dimension.min_y,
);
let proto_biome_height = biome_coords::from_block(proto_chunk.height()); let proto_biome_height = biome_coords::from_block(proto_chunk.height());
let biome_min_y = biome_coords::from_block(dimension.min_y); let biome_min_y = biome_coords::from_block(dimension.min_y);
@ -799,13 +796,18 @@ impl Chunk {
let section_index = y_offset as usize / 4; let section_index = y_offset as usize / 4;
let relative_y = y_offset as usize % 4; let relative_y = y_offset as usize % 4;
if let Some(section) = sections.sections.lock().unwrap().get_mut(section_index) { if let Some(section) = sections
.biome_sections
.write()
.unwrap()
.get_mut(section_index)
{
let absolute_biome_y = biome_min_y + y_offset as i32; let absolute_biome_y = biome_min_y + y_offset as i32;
for z in 0..4 { for z in 0..4 {
for x in 0..4 { for x in 0..4 {
let biome = proto_chunk.get_biome_id(x as i32, absolute_biome_y, z as i32); let biome = proto_chunk.get_biome_id(x as i32, absolute_biome_y, z as i32);
section.biomes.set(x, relative_y, z, biome); section.set(x, relative_y, z, biome);
} }
} }
} }
@ -817,18 +819,23 @@ impl Chunk {
let section_index = (y_offset as usize) / 16; let section_index = (y_offset as usize) / 16;
let relative_y = (y_offset as usize) % 16; let relative_y = (y_offset as usize) % 16;
if let Some(section) = sections.sections.lock().unwrap().get_mut(section_index) { if let Some(section) = sections
.block_sections
.write()
.unwrap()
.get_mut(section_index)
{
for z in 0..16 { for z in 0..16 {
for x in 0..16 { for x in 0..16 {
let block = let block =
proto_chunk.get_block_state_raw(x as i32, y_offset as i32, z as i32); proto_chunk.get_block_state_raw(x as i32, y_offset as i32, z as i32);
section.block_states.set(x, relative_y, z, block); section.set(x, relative_y, z, block);
} }
} }
} }
} }
let len = sections.sections.lock().unwrap().len(); let len = sections.count;
let mut chunk = ChunkData { let mut chunk = ChunkData {
light_engine: ChunkLight { light_engine: ChunkLight {
sky_light: (0..len) sky_light: (0..len)

View File

@ -229,18 +229,20 @@ impl ProtoChunk {
let section_data = &chunk_data.section; let section_data = &chunk_data.section;
let heightmap_data = chunk_data.heightmap.lock().unwrap(); let heightmap_data = chunk_data.heightmap.lock().unwrap();
for (section_y, section) in section_data.sections.lock().unwrap().iter().enumerate() { let block_sections_guard = section_data.block_sections.read().unwrap();
let section_base_y = section_y as i32 * 16; let biome_sections_guard = section_data.biome_sections.read().unwrap();
for (section_idx, block_palette) in block_sections_guard.iter().enumerate() {
let section_base_y = section_idx as i32 * 16;
if section_base_y >= proto_chunk.height() as i32 { if section_base_y >= proto_chunk.height() as i32 {
continue; continue;
} }
// --- Block States ---
for x in 0..16 { for x in 0..16 {
for y in 0..16 { for y in 0..16 {
for z in 0..16 { for z in 0..16 {
let block_state_id = section.block_states.get(x, y, z); let block_state_id = block_palette.get(x, y, z);
let block_state = BlockState::from_id(block_state_id); let block_state = BlockState::from_id(block_state_id);
let absolute_y = section_base_y + y as i32 + section_data.min_y; let absolute_y = section_base_y + y as i32 + section_data.min_y;
@ -249,26 +251,29 @@ impl ProtoChunk {
} }
} }
// --- Biomes --- if let Some(biome_palette) = biome_sections_guard.get(section_idx) {
for x in 0..4 { for x in 0..4 {
for y in 0..4 { for y in 0..4 {
for z in 0..4 { for z in 0..4 {
let biome_id = section.biomes.get(x, y, z); let biome_id = biome_palette.get(x, y, z);
let relative_y_block = section_base_y + (y as i32 * 4);
let biome_y_idx = biome_coords::from_block(relative_y_block);
let index = proto_chunk.local_biome_pos_to_biome_index(
x as i32,
biome_y_idx,
z as i32,
);
proto_chunk.flat_biome_map[index] = biome_id; let biome_y_idx = (section_idx * 4) + y;
let index = proto_chunk.local_biome_pos_to_biome_index(
x as i32,
biome_y_idx as i32,
z as i32,
);
proto_chunk.flat_biome_map[index] = biome_id;
}
} }
} }
} }
} }
drop(block_sections_guard);
drop(biome_sections_guard);
// --- Heightmaps ---
for z in 0..16 { for z in 0..16 {
for x in 0..16 { for x in 0..16 {
let index = ((z << 4) + x) as usize; let index = ((z << 4) + x) as usize;

View File

@ -441,7 +441,7 @@ impl Level {
for chunk in self.loaded_chunks.iter() { for chunk in self.loaded_chunks.iter() {
let chunk_x_base = chunk.x * 16; let chunk_x_base = chunk.x * 16;
let chunk_z_base = chunk.z * 16; let chunk_z_base = chunk.z * 16;
let section_count = chunk.section.sections.lock().unwrap().len(); let section_count = chunk.section.count;
ticks ticks
.block_entities .block_entities

View File

@ -65,7 +65,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
down_pos.down().to_centered_f64(), down_pos.down().to_centered_f64(),
&EntityType::SNOW_GOLEM, &EntityType::SNOW_GOLEM,
); );
let golem = SnowGolemEntity::make(entity).await; let golem = SnowGolemEntity::new(entity).await;
args.world.spawn_entity(golem).await; args.world.spawn_entity(golem).await;
return; return;
} }
@ -103,7 +103,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
down_pos.down().to_centered_f64(), down_pos.down().to_centered_f64(),
&EntityType::IRON_GOLEM, &EntityType::IRON_GOLEM,
); );
let golem = IronGolemEntity::make(entity).await; let golem = IronGolemEntity::new(entity).await;
args.world.spawn_entity(golem).await; args.world.spawn_entity(golem).await;
return; return;
} }

View File

@ -0,0 +1,30 @@
use std::sync::Arc;
use pumpkin_data::entity::EntityType;
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::GameMode;
use crate::block::BrokenArgs;
use crate::block::{BlockBehaviour, BlockFuture};
use crate::entity::Entity;
#[pumpkin_block_from_tag("c:cobblestones/infested")]
pub struct InfestedBlock;
impl BlockBehaviour for InfestedBlock {
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async {
// TODO: ugly fix, use onStacksDropped
if args.player.gamemode.load() == GameMode::Creative {
return;
}
let entity = Entity::new(
args.world.clone(),
args.position.0.to_f64(),
&EntityType::SILVERFISH,
);
args.world.spawn_entity(Arc::new(entity)).await;
})
}
}

View File

@ -30,6 +30,7 @@ pub mod glass_panes;
pub mod glazed_terracotta; pub mod glazed_terracotta;
pub mod grindstone; pub mod grindstone;
pub mod hay; pub mod hay;
pub mod infested;
pub mod iron_bars; pub mod iron_bars;
pub mod jukebox; pub mod jukebox;
pub mod ladder; pub mod ladder;

View File

@ -88,7 +88,7 @@ impl BlockBehaviour for WitherSkeletonSkullBlock {
top_middle.to_centered_f64(), top_middle.to_centered_f64(),
&EntityType::WITHER, &EntityType::WITHER,
); );
let wither = WitherEntity::make(entity).await; let wither = WitherEntity::new(entity).await;
world.spawn_entity(wither).await; world.spawn_entity(wither).await;
return; return;
} }

View File

@ -22,6 +22,7 @@ use crate::block::blocks::furnace::FurnaceBlock;
use crate::block::blocks::glass_panes::GlassPaneBlock; use crate::block::blocks::glass_panes::GlassPaneBlock;
use crate::block::blocks::grindstone::GrindstoneBlock; use crate::block::blocks::grindstone::GrindstoneBlock;
use crate::block::blocks::hay::HayBlock; use crate::block::blocks::hay::HayBlock;
use crate::block::blocks::infested::InfestedBlock;
use crate::block::blocks::iron_bars::IronBarsBlock; use crate::block::blocks::iron_bars::IronBarsBlock;
use crate::block::blocks::logs::LogBlock; use crate::block::blocks::logs::LogBlock;
use crate::block::blocks::mangrove_roots::MangroveRootsBlock; use crate::block::blocks::mangrove_roots::MangroveRootsBlock;
@ -179,6 +180,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(HayBlock); manager.register(HayBlock);
manager.register(GrindstoneBlock); manager.register(GrindstoneBlock);
manager.register(IronBarsBlock); manager.register(IronBarsBlock);
manager.register(InfestedBlock);
manager.register(JukeboxBlock); manager.register(JukeboxBlock);
manager.register(LogBlock); manager.register(LogBlock);
manager.register(BambooBlock); manager.register(BambooBlock);

View File

@ -31,10 +31,8 @@ where
_server: &'a Server, _server: &'a Server,
args: &'b mut RawArgs<'a>, args: &'b mut RawArgs<'a>,
) -> ConsumeResult<'a> { ) -> ConsumeResult<'a> {
// 1. Perform the synchronous mutable operation (pop) outside the Future.
let s_opt: Option<&'a str> = args.pop(); let s_opt: Option<&'a str> = args.pop();
// 2. Process the entire argument and check bounds synchronously.
let result: Option<Arg<'a>> = s_opt let result: Option<Arg<'a>> = s_opt
// Replace args.pop()?.parse::<T>().ok()? // Replace args.pop()?.parse::<T>().ok()?
.and_then(|s| s.parse::<T>().ok()) .and_then(|s| s.parse::<T>().ok())
@ -57,7 +55,6 @@ where
Arg::Num(Ok(x.to_number())) Arg::Num(Ok(x.to_number()))
}); });
// 3. Return a Future that immediately resolves to the calculated result.
Box::pin(async move { result }) Box::pin(async move { result })
} }
} }

View File

@ -83,10 +83,16 @@ impl CommandExecutor for Executor {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
let locale = sender.get_locale(); let locale = sender.get_locale();
let profile = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let version_string = format!( let version_string = format!(
"{} (Commit: {}) - {} Contributors", "{} (Commit: {}/{}) - {} Contributors",
CARGO_PKG_VERSION, CARGO_PKG_VERSION,
GIT_HASH, GIT_HASH,
profile,
contributors.len() contributors.len()
); );
sender sender

View File

@ -13,7 +13,7 @@ pub struct WitherEntity {
} }
impl WitherEntity { impl WitherEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let wither = Self { mob_entity }; let wither = Self { mob_entity };
let mob_arc = Arc::new(wither); let mob_arc = Arc::new(wither);

View File

@ -21,7 +21,7 @@ pub struct CreeperEntity {
} }
impl CreeperEntity { impl CreeperEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let entity = Self { let entity = Self {
mob_entity, mob_entity,

View File

@ -13,8 +13,8 @@ pub struct DrownedEntity {
} }
impl DrownedEntity { impl DrownedEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let entity = ZombieEntity::make(entity).await; let entity = ZombieEntity::new(entity).await;
let zombie = Self { entity }; let zombie = Self { entity };
let mob_arc = Arc::new(zombie); let mob_arc = Arc::new(zombie);

View File

@ -19,6 +19,7 @@ use tokio::sync::Mutex;
pub mod creeper; pub mod creeper;
pub mod drowned; pub mod drowned;
pub mod silverfish;
pub mod skeleton; pub mod skeleton;
pub mod zombie; pub mod zombie;
pub mod zombie_villager; pub mod zombie_villager;

View File

@ -0,0 +1,41 @@
use std::sync::Arc;
use pumpkin_data::entity::EntityType;
use crate::entity::{
Entity, NBTStorage,
ai::goal::active_target::ActiveTargetGoal,
mob::{Mob, MobEntity},
};
pub struct SilverfishEntity {
entity: Arc<MobEntity>,
}
impl SilverfishEntity {
pub async fn new(entity: Entity) -> Arc<Self> {
let entity = Arc::new(MobEntity::new(entity));
let zombie = Self { entity };
let mob_arc = Arc::new(zombie);
{
let mut target_selector = mob_arc.entity.target_selector.lock().await;
// TODO
target_selector.add_goal(
2,
ActiveTargetGoal::with_default(&mob_arc.entity, &EntityType::PLAYER, true),
);
};
mob_arc
}
}
impl NBTStorage for SilverfishEntity {}
impl Mob for SilverfishEntity {
fn get_mob_entity(&self) -> &MobEntity {
&self.entity
}
}

View File

@ -24,7 +24,7 @@ pub struct ZombieEntity {
} }
impl ZombieEntity { impl ZombieEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let zombie = Self { mob_entity }; let zombie = Self { mob_entity };
let mob_arc = Arc::new(zombie); let mob_arc = Arc::new(zombie);

View File

@ -8,8 +8,8 @@ pub struct ZombieVillagerEntity {
} }
impl ZombieVillagerEntity { impl ZombieVillagerEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = ZombieEntity::make(entity).await; let mob_entity = ZombieEntity::new(entity).await;
let zombie = Self { mob_entity }; let zombie = Self { mob_entity };
Arc::new(zombie) Arc::new(zombie)
} }

View File

@ -978,7 +978,6 @@ impl Entity {
async fn update_fluid_state(&self, caller: &Arc<dyn EntityBase>) { async fn update_fluid_state(&self, caller: &Arc<dyn EntityBase>) {
let is_pushed = caller.is_pushed_by_fluids(); let is_pushed = caller.is_pushed_by_fluids();
let mut fluids = BTreeMap::new(); let mut fluids = BTreeMap::new();
let water_push = Vector3::default(); let water_push = Vector3::default();

View File

@ -13,7 +13,7 @@ pub struct IronGolemEntity {
} }
impl IronGolemEntity { impl IronGolemEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let iron_golem = Self { mob_entity }; let iron_golem = Self { mob_entity };
let mob_arc = Arc::new(iron_golem); let mob_arc = Arc::new(iron_golem);

View File

@ -13,7 +13,7 @@ pub struct SnowGolemEntity {
} }
impl SnowGolemEntity { impl SnowGolemEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let wolf = Self { mob_entity }; let wolf = Self { mob_entity };
let mob_arc = Arc::new(wolf); let mob_arc = Arc::new(wolf);

View File

@ -13,7 +13,7 @@ pub struct WolfEntity {
} }
impl WolfEntity { impl WolfEntity {
pub async fn make(entity: Entity) -> Arc<Self> { pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity); let mob_entity = MobEntity::new(entity);
let wolf = Self { mob_entity }; let wolf = Self { mob_entity };
let mob_arc = Arc::new(wolf); let mob_arc = Arc::new(wolf);

View File

@ -13,8 +13,8 @@ use crate::{
}, },
living::LivingEntity, living::LivingEntity,
mob::{ mob::{
creeper::CreeperEntity, drowned::DrownedEntity, zombie::ZombieEntity, creeper::CreeperEntity, drowned::DrownedEntity, silverfish::SilverfishEntity,
zombie_villager::ZombieVillagerEntity, zombie::ZombieEntity, zombie_villager::ZombieVillagerEntity,
}, },
passive::{iron_golem::IronGolemEntity, snow_golem::SnowGolemEntity, wolf::WolfEntity}, passive::{iron_golem::IronGolemEntity, snow_golem::SnowGolemEntity, wolf::WolfEntity},
}, },
@ -30,17 +30,18 @@ pub async fn from_type(
let entity = Entity::from_uuid(uuid, world.clone(), position, entity_type); let entity = Entity::from_uuid(uuid, world.clone(), position, entity_type);
let mob: Arc<dyn EntityBase> = match entity_type.id { let mob: Arc<dyn EntityBase> = match entity_type.id {
id if id == EntityType::ZOMBIE.id => ZombieEntity::make(entity).await, id if id == EntityType::ZOMBIE.id => ZombieEntity::new(entity).await,
id if id == EntityType::DROWNED.id => DrownedEntity::make(entity).await, id if id == EntityType::DROWNED.id => DrownedEntity::new(entity).await,
id if id == EntityType::ZOMBIE_VILLAGER.id => ZombieVillagerEntity::make(entity).await, id if id == EntityType::ZOMBIE_VILLAGER.id => ZombieVillagerEntity::new(entity).await,
id if id == EntityType::CREEPER.id => CreeperEntity::make(entity).await, id if id == EntityType::CREEPER.id => CreeperEntity::new(entity).await,
id if id == EntityType::SNOW_GOLEM.id => SnowGolemEntity::make(entity).await, id if id == EntityType::SNOW_GOLEM.id => SnowGolemEntity::new(entity).await,
id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::make(entity).await, id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::new(entity).await,
id if id == EntityType::WOLF.id => WolfEntity::make(entity).await, id if id == EntityType::WOLF.id => WolfEntity::new(entity).await,
id if id == EntityType::WITHER.id => WitherEntity::make(entity).await, id if id == EntityType::WITHER.id => WitherEntity::new(entity).await,
id if id == EntityType::ARMOR_STAND.id => Arc::new(ArmorStandEntity::new(entity)), id if id == EntityType::ARMOR_STAND.id => Arc::new(ArmorStandEntity::new(entity)),
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)), id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),
id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)), id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)),
id if id == EntityType::SILVERFISH.id => SilverfishEntity::new(entity).await,
// Fallback Entity // Fallback Entity
_ => { _ => {
if entity_type.max_health.is_some() { if entity_type.max_health.is_some() {

View File

@ -2186,7 +2186,7 @@ impl World {
for (uuid, entity_nbt) in chunk.data.lock().await.iter() { for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
let Some(id) = entity_nbt.get_string("id") else { let Some(id) = entity_nbt.get_string("id") else {
log::warn!("Entity has no ID"); log::debug!("Entity has no ID");
continue; continue;
}; };
let Some(entity_type) = let Some(entity_type) =
@ -2777,12 +2777,12 @@ impl World {
return block_state_id; return block_state_id;
} }
chunk.mark_dirty(true); chunk.mark_dirty(true);
drop(chunk);
self.unsent_block_changes self.unsent_block_changes
.lock() .lock()
.await .await
.insert(*position, block_state_id); .insert(*position, block_state_id);
drop(chunk);
let old_block = Block::from_state_id(replaced_block_state_id); let old_block = Block::from_state_id(replaced_block_state_id);
let new_block = Block::from_state_id(block_state_id); let new_block = Block::from_state_id(block_state_id);
@ -3337,8 +3337,15 @@ impl World {
pub async fn remove_block_entity(&self, block_pos: &BlockPos) { pub async fn remove_block_entity(&self, block_pos: &BlockPos) {
let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
chunk.block_entities.lock().unwrap().remove(block_pos); if chunk
chunk.mark_dirty(true); .block_entities
.lock()
.unwrap()
.remove(block_pos)
.is_some()
{
chunk.mark_dirty(true);
}
} }
pub async fn update_block_entity(&self, block_entity: &Arc<dyn BlockEntity>) { pub async fn update_block_entity(&self, block_entity: &Arc<dyn BlockEntity>) {