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]]
name = "bytes"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cast"
@ -275,9 +275,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.54"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
"shlex",
@ -932,9 +932,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
@ -3269,9 +3269,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.4"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc"
dependencies = [
"base64 0.22.1",
"cookie_store",
@ -3898,6 +3898,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.17"
version = "1.0.19"
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"
tokio-util = "0.7.18"
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"
RUN apk add --no-cache musl-dev \
# Required for git-version

View File

@ -777,7 +777,7 @@ pub(crate) fn build() -> TokenStream {
impl FromResourceLocation for &'static Fluid {
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 {
// 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_11_packets.json");

View File

@ -3,7 +3,6 @@ use quote::{format_ident, quote};
use std::{collections::BTreeMap, fs};
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_11_tracked_data.json");

View File

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

View File

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

View File

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

View File

@ -2,7 +2,10 @@ use std::{
io::Cursor,
path::PathBuf,
pin::Pin,
sync::atomic::{AtomicBool, Ordering},
sync::{
RwLock,
atomic::{AtomicBool, Ordering},
},
};
use bytes::Bytes;
@ -28,7 +31,7 @@ use pumpkin_util::math::vector2::Vector2;
use serde::{Deserialize, Serialize};
use super::{
ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections, SubChunk,
ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections,
palette::{BiomePalette, BlockPalette},
};
use crate::block::BlockStateCodec;
@ -123,51 +126,57 @@ impl ChunkData {
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
.into_iter()
.map(|section| {
// Map light data to the LightContainer enum
let block_light = section
.block_light
.map(LightContainer::new)
.unwrap_or_default();
.map(LightContainer::Full)
.unwrap_or(LightContainer::Empty(0)); // Standard default
let sky_light = section
.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();
let sub_chunk = SubChunk {
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)
(block_light, sky_light, block_palette, biome_palette)
})
.fold(
(Vec::new(), Vec::new(), Vec::new()),
|(mut bl, mut sl, mut sc), (block_l, sky_l, sub_c)| {
bl.push(block_l);
sl.push(sky_l);
sc.push(sub_c);
(bl, sl, sc)
(Vec::new(), Vec::new(), Vec::new(), Vec::new()),
|(mut bl, mut sl, mut bp, mut bip), (b_l, s_l, b_p, bi_p)| {
bl.push(b_l);
sl.push(s_l);
bp.push(b_p);
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 {
block_light: block_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 {
section,
heightmap: std::sync::Mutex::new(chunk_data.heightmaps),
@ -193,42 +202,38 @@ impl ChunkData {
}
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
// 1. Process sections (Standard Mutex)
// We do this in a block or just let the guard drop naturally
// before the first .await
let sections: Vec<_> = {
let sections_guard = self.section.sections.lock().unwrap();
sections_guard
.iter()
.enumerate()
.map(|(i, section)| ChunkSectionNBT {
y: (i as i8) + section_coords::block_to_section(self.section.min_y) as i8,
block_states: Some(section.block_states.to_disk_nbt()),
biomes: Some(section.biomes.to_disk_nbt()),
block_light: match self.light_engine.block_light[i].clone() {
LightContainer::Empty(_) => None,
LightContainer::Full(data) => Some(data),
},
sky_light: match self.light_engine.sky_light[i].clone() {
LightContainer::Empty(_) => None,
LightContainer::Full(data) => Some(data),
},
let sections: Vec<ChunkSectionNBT> = {
let block_lock = self.section.block_sections.read().unwrap();
let biome_lock = self.section.biome_sections.read().unwrap();
let min_section_y = (self.section.min_y >> 4) as i8;
(0..self.section.count)
.map(|i| {
ChunkSectionNBT {
y: i as i8 + min_section_y,
// Convert the palettes to their NBT disk representation
block_states: Some(block_lock[i].to_disk_nbt()),
biomes: Some(biome_lock[i].to_disk_nbt()),
block_light: match self.light_engine.block_light.get(i) {
Some(LightContainer::Full(data)) => Some(data.clone()),
_ => None,
},
sky_light: match self.light_engine.sky_light.get(i) {
Some(LightContainer::Full(data)) => Some(data.clone()),
_ => None,
},
}
})
.collect()
};
// 2. Clone heightmaps (Standard Mutex)
let heightmaps = self.heightmap.lock().unwrap().clone();
// 3. Prepare block entities (CRITICAL: Must drop lock before .await)
let entities_to_serialize = {
// We clone the Arcs out of the map while holding the lock
let entities_guard = self.block_entities.lock().unwrap();
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(
|block_entity| async move {
let mut nbt = NbtCompound::new();

View File

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

View File

@ -8,7 +8,7 @@ TODO
use crate::block::RawBlockState;
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 pumpkin_data::block_properties::is_air;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
@ -787,10 +787,7 @@ impl Chunk {
let proto_chunk = self.get_proto_chunk();
let total_sections = dimension.height as usize / 16;
let sections = ChunkSections::new(
Mutex::new(vec![SubChunk::default(); total_sections].into_boxed_slice()),
dimension.min_y,
);
let sections = ChunkSections::new(total_sections, dimension.min_y);
let proto_biome_height = biome_coords::from_block(proto_chunk.height());
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 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;
for z in 0..4 {
for x in 0..4 {
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 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 x in 0..16 {
let block =
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 {
light_engine: ChunkLight {
sky_light: (0..len)

View File

@ -229,18 +229,20 @@ impl ProtoChunk {
let section_data = &chunk_data.section;
let heightmap_data = chunk_data.heightmap.lock().unwrap();
for (section_y, section) in section_data.sections.lock().unwrap().iter().enumerate() {
let section_base_y = section_y as i32 * 16;
let block_sections_guard = section_data.block_sections.read().unwrap();
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 {
continue;
}
// --- Block States ---
for x in 0..16 {
for y 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 absolute_y = section_base_y + y as i32 + section_data.min_y;
@ -249,26 +251,29 @@ impl ProtoChunk {
}
}
// --- Biomes ---
for x in 0..4 {
for y in 0..4 {
for z in 0..4 {
let biome_id = section.biomes.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,
);
if let Some(biome_palette) = biome_sections_guard.get(section_idx) {
for x in 0..4 {
for y in 0..4 {
for z in 0..4 {
let biome_id = biome_palette.get(x, y, z);
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 x in 0..16 {
let index = ((z << 4) + x) as usize;

View File

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

View File

@ -65,7 +65,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
down_pos.down().to_centered_f64(),
&EntityType::SNOW_GOLEM,
);
let golem = SnowGolemEntity::make(entity).await;
let golem = SnowGolemEntity::new(entity).await;
args.world.spawn_entity(golem).await;
return;
}
@ -103,7 +103,7 @@ impl BlockBehaviour for CarvedPumpkinBlock {
down_pos.down().to_centered_f64(),
&EntityType::IRON_GOLEM,
);
let golem = IronGolemEntity::make(entity).await;
let golem = IronGolemEntity::new(entity).await;
args.world.spawn_entity(golem).await;
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 grindstone;
pub mod hay;
pub mod infested;
pub mod iron_bars;
pub mod jukebox;
pub mod ladder;

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ pub struct 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 wither = Self { mob_entity };
let mob_arc = Arc::new(wither);

View File

@ -21,7 +21,7 @@ pub struct 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 entity = Self {
mob_entity,

View File

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

View File

@ -19,6 +19,7 @@ use tokio::sync::Mutex;
pub mod creeper;
pub mod drowned;
pub mod silverfish;
pub mod skeleton;
pub mod zombie;
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 {
pub async fn make(entity: Entity) -> Arc<Self> {
pub async fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity);
let zombie = Self { mob_entity };
let mob_arc = Arc::new(zombie);

View File

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

View File

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

View File

@ -13,7 +13,7 @@ pub struct 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 iron_golem = Self { mob_entity };
let mob_arc = Arc::new(iron_golem);

View File

@ -13,7 +13,7 @@ pub struct 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 wolf = Self { mob_entity };
let mob_arc = Arc::new(wolf);

View File

@ -13,7 +13,7 @@ pub struct 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 wolf = Self { mob_entity };
let mob_arc = Arc::new(wolf);

View File

@ -13,8 +13,8 @@ use crate::{
},
living::LivingEntity,
mob::{
creeper::CreeperEntity, drowned::DrownedEntity, zombie::ZombieEntity,
zombie_villager::ZombieVillagerEntity,
creeper::CreeperEntity, drowned::DrownedEntity, silverfish::SilverfishEntity,
zombie::ZombieEntity, zombie_villager::ZombieVillagerEntity,
},
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 mob: Arc<dyn EntityBase> = match entity_type.id {
id if id == EntityType::ZOMBIE.id => ZombieEntity::make(entity).await,
id if id == EntityType::DROWNED.id => DrownedEntity::make(entity).await,
id if id == EntityType::ZOMBIE_VILLAGER.id => ZombieVillagerEntity::make(entity).await,
id if id == EntityType::CREEPER.id => CreeperEntity::make(entity).await,
id if id == EntityType::SNOW_GOLEM.id => SnowGolemEntity::make(entity).await,
id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::make(entity).await,
id if id == EntityType::WOLF.id => WolfEntity::make(entity).await,
id if id == EntityType::WITHER.id => WitherEntity::make(entity).await,
id if id == EntityType::ZOMBIE.id => ZombieEntity::new(entity).await,
id if id == EntityType::DROWNED.id => DrownedEntity::new(entity).await,
id if id == EntityType::ZOMBIE_VILLAGER.id => ZombieVillagerEntity::new(entity).await,
id if id == EntityType::CREEPER.id => CreeperEntity::new(entity).await,
id if id == EntityType::SNOW_GOLEM.id => SnowGolemEntity::new(entity).await,
id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::new(entity).await,
id if id == EntityType::WOLF.id => WolfEntity::new(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::PAINTING.id => Arc::new(PaintingEntity::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
_ => {
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() {
let Some(id) = entity_nbt.get_string("id") else {
log::warn!("Entity has no ID");
log::debug!("Entity has no ID");
continue;
};
let Some(entity_type) =
@ -2777,12 +2777,12 @@ impl World {
return block_state_id;
}
chunk.mark_dirty(true);
drop(chunk);
self.unsent_block_changes
.lock()
.await
.insert(*position, block_state_id);
drop(chunk);
let old_block = Block::from_state_id(replaced_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) {
let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
chunk.block_entities.lock().unwrap().remove(block_pos);
chunk.mark_dirty(true);
if chunk
.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>) {