feat: add infested blocks & silverfish spawn
ofc also includes a bunch of other things like i always do :D
This commit is contained in:
parent
707d1e9a92
commit
53803c5e94
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(®istry_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(®istry_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!",
|
||||
|
||||
@ -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)
|
||||
.unwrap_or_default();
|
||||
.map(LightContainer::Full)
|
||||
.unwrap_or(LightContainer::Empty(15)); // Sky is usually bright
|
||||
|
||||
let sub_chunk = SubChunk {
|
||||
block_states: section
|
||||
// Convert NBT to Palettes
|
||||
let block_palette = section
|
||||
.block_states
|
||||
.map(BlockPalette::from_disk_nbt)
|
||||
.unwrap_or_default(),
|
||||
biomes: section
|
||||
.unwrap_or_default();
|
||||
let biome_palette = section
|
||||
.biomes
|
||||
.map(BiomePalette::from_disk_nbt)
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
.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),
|
||||
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[i].clone() {
|
||||
LightContainer::Empty(_) => None,
|
||||
LightContainer::Full(data) => Some(data),
|
||||
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();
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,16 +251,17 @@ impl ProtoChunk {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Biomes ---
|
||||
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 = 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 biome_id = biome_palette.get(x, y, z);
|
||||
|
||||
let biome_y_idx = (section_idx * 4) + y;
|
||||
|
||||
let index = proto_chunk.local_biome_pos_to_biome_index(
|
||||
x as i32,
|
||||
biome_y_idx,
|
||||
biome_y_idx as i32,
|
||||
z as i32,
|
||||
);
|
||||
|
||||
@ -267,8 +270,10 @@ impl ProtoChunk {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
30
pumpkin/src/block/blocks/infested.rs
Normal file
30
pumpkin/src/block/blocks/infested.rs
Normal 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;
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
41
pumpkin/src/entity/mob/silverfish.rs
Normal file
41
pumpkin/src/entity/mob/silverfish.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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,9 +3337,16 @@ 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);
|
||||
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>) {
|
||||
let block_pos = block_entity.get_position();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user