chore: less ugly code

and less allocations
This commit is contained in:
Alexander Medvedev 2026-01-26 22:23:58 +01:00
parent ccf62b842a
commit ac4c85192f
47 changed files with 1012 additions and 1073 deletions

View File

@ -119,8 +119,7 @@ impl SplineRepr {
.into_iter()
.zip(values)
.zip(derivatives)
.map(|((location, value), derivative)| (location, value, derivative))
.collect::<Vec<_>>();
.map(|((location, value), derivative)| (location, value, derivative));
let function_index =
location_function.get_index_for_component(stack, hash_to_index_map);

View File

@ -118,36 +118,41 @@ impl BlockState {
}
}
pub fn get_block_collision_shapes(&self) -> Vec<CollisionShape> {
pub fn is_waterlogged(&self) -> bool {
let block = Block::from_state_id(self.id);
if let Some(props) = block.properties(self.id) {
props
.to_props()
.iter()
.any(|(k, v)| k == &"waterlogged" && v == &"true")
} else {
false
}
}
pub fn get_block_collision_shapes(&self) -> impl Iterator<Item = CollisionShape> + '_ {
self.collision_shapes
.iter()
.map(|&id| COLLISION_SHAPES[id as usize])
.collect()
}
pub fn get_block_outline_shapes(&self) -> Option<Vec<CollisionShape>> {
let mut shapes: Vec<CollisionShape> = self
pub fn get_block_outline_shapes(&self) -> impl Iterator<Item = CollisionShape> + '_ {
let base_shapes = self
.outline_shapes
.iter()
.map(|&id| COLLISION_SHAPES[id as usize])
.collect();
.map(|&id| COLLISION_SHAPES[id as usize]);
let block = Block::from_state_id(self.id);
if let Some(props) = block.properties(self.id) {
let is_waterlogged = props
.to_props()
.iter()
.any(|(k, v)| *k == "waterlogged" && *v == "true");
if is_waterlogged {
shapes.push(CollisionShape::new(
let water_shape = if self.is_waterlogged() {
Some(CollisionShape::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.875, 1.0),
));
}
}
))
} else {
None
};
Some(shapes)
base_shapes.chain(water_shape)
}
}

View File

@ -73,10 +73,10 @@ impl NbtCompound {
Ok(compound)
}
pub fn serialize_content<W: Write>(&self, w: &mut WriteAdaptor<W>) -> Result<(), Error> {
for (name, tag) in &self.child_tags {
pub fn serialize_content<W: Write>(self, w: &mut WriteAdaptor<W>) -> Result<(), Error> {
for (name, tag) in self.child_tags {
w.write_u8_be(tag.get_type_id())?;
NbtTag::String(name.clone()).serialize_data(w)?;
NbtTag::write_string(&name, w)?;
tag.serialize_data(w)?;
}
w.write_u8_be(END_ID)?;

View File

@ -110,11 +110,11 @@ impl Nbt {
})
}
pub fn write(&self) -> Bytes {
pub fn write(self) -> Bytes {
let mut bytes = Vec::new();
let mut writer = WriteAdaptor::new(&mut bytes);
writer.write_u8_be(COMPOUND_ID).unwrap();
NbtTag::String(self.name.to_string())
NbtTag::String(self.name)
.serialize_data(&mut writer)
.unwrap();
self.root_tag.serialize_content(&mut writer).unwrap();
@ -122,13 +122,13 @@ impl Nbt {
bytes.into()
}
pub fn write_to_writer<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
pub fn write_to_writer<W: Write>(self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.write())?;
Ok(())
}
/// Writes an NBT tag without a root `Compound` name.
pub fn write_unnamed(&self) -> Bytes {
pub fn write_unnamed(self) -> Bytes {
let mut bytes = Vec::new();
let mut writer = WriteAdaptor::new(&mut bytes);
@ -138,7 +138,7 @@ impl Nbt {
bytes.into()
}
pub fn write_unnamed_to_writer<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
pub fn write_unnamed_to_writer<W: Write>(self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.write_unnamed())?;
Ok(())
}
@ -178,7 +178,7 @@ pub fn get_nbt_string<R: Read + Seek>(bytes: &mut NbtReadHelper<R>) -> Result<St
let len = bytes.get_u16_be()? as usize;
let string_bytes = bytes.read_boxed_slice(len)?;
let string = cesu8::from_java_cesu8(&string_bytes).map_err(|_| Error::Cesu8DecodingError)?;
Ok(string.to_string())
Ok(string.into_owned())
}
// TODO: This is a bit hacky

View File

@ -36,12 +36,12 @@ pub fn read_gzip_compound_tag(input: impl Read + Seek) -> Result<NbtCompound, Er
/// # Returns
///
/// A Result containing either the compressed data as a byte vector or an Error
pub fn write_gzip_compound_tag(compound: &NbtCompound, output: impl Write) -> Result<(), Error> {
pub fn write_gzip_compound_tag(compound: NbtCompound, output: impl Write) -> Result<(), Error> {
// Create a GZip encoder that writes to the output
let mut encoder = GzEncoder::new(output, Compression::default());
// Create an NBT wrapper and write directly to the encoder
let nbt = Nbt::new(String::new(), compound.clone());
let nbt = Nbt::new(String::new(), compound);
nbt.write_to_writer(&mut encoder)
.map_err(Error::Incomplete)?;
@ -52,7 +52,7 @@ pub fn write_gzip_compound_tag(compound: &NbtCompound, output: impl Write) -> Re
}
/// Convenience function that returns compressed bytes
pub fn write_gzip_compound_tag_to_bytes(compound: &NbtCompound) -> Result<Vec<u8>, Error> {
pub fn write_gzip_compound_tag_to_bytes(compound: NbtCompound) -> Result<Vec<u8>, Error> {
let mut buffer = Vec::new();
write_gzip_compound_tag(compound, &mut buffer)?;
Ok(buffer)
@ -135,7 +135,7 @@ mod tests {
// Write to GZip using streaming
let mut buffer = Vec::new();
write_gzip_compound_tag(&compound, &mut buffer).expect("Failed to compress compound");
write_gzip_compound_tag(compound, &mut buffer).expect("Failed to compress compound");
// Read from GZip using streaming
let read_compound =
@ -170,7 +170,7 @@ mod tests {
// Test convenience method for writing
let buffer =
write_gzip_compound_tag_to_bytes(&compound).expect("Failed to compress compound");
write_gzip_compound_tag_to_bytes(compound).expect("Failed to compress compound");
// Test streaming read from the buffer
let read_compound =
@ -183,7 +183,7 @@ mod tests {
fn test_gzip_empty_compound() {
let compound = NbtCompound::new();
let mut buffer = Vec::new();
write_gzip_compound_tag(&compound, &mut buffer).expect("Failed to compress empty compound");
write_gzip_compound_tag(compound, &mut buffer).expect("Failed to compress empty compound");
let read_compound = read_gzip_compound_tag(Cursor::new(buffer))
.expect("Failed to decompress empty compound");
@ -200,7 +200,7 @@ mod tests {
}
let mut buffer = Vec::new();
write_gzip_compound_tag(&compound, &mut buffer).expect("Failed to compress large compound");
write_gzip_compound_tag(compound, &mut buffer).expect("Failed to compress large compound");
let read_compound = read_gzip_compound_tag(Cursor::new(buffer))
.expect("Failed to decompress large compound");
@ -272,7 +272,7 @@ mod tests {
let uncompressed = compound.child_tags.len() * 100; // rough estimate
let mut buffer = Vec::new();
write_gzip_compound_tag(&compound, &mut buffer).expect("Failed to compress compound");
write_gzip_compound_tag(compound, &mut buffer).expect("Failed to compress compound");
println!("Uncompressed size (est): {uncompressed} bytes");
println!("Compressed size: {} bytes", buffer.len());
@ -356,7 +356,7 @@ mod tests {
compound.put_int("test_value", 42);
let file = File::create(&file_path).expect("Failed to create temp file");
write_gzip_compound_tag(&compound, file).expect("Failed to write compound to file");
write_gzip_compound_tag(compound, file).expect("Failed to write compound to file");
let file = File::open(&file_path).expect("Failed to open temp file");
let read_compound =

View File

@ -92,7 +92,7 @@ impl<W: Write> Serializer<W> {
match &mut self.state {
State::Named(name) | State::Array { name, .. } => {
self.output.write_u8_be(tag)?;
NbtTag::String(name.clone()).serialize_data(&mut self.output)?;
NbtTag::write_string(name, &mut self.output)?;
}
State::FirstListElement { len } => {
self.output.write_u8_be(tag)?;
@ -131,7 +131,7 @@ impl<W: Write> Serializer<W> {
self.handled_root = true;
self.output.write_u8_be(tag)?;
if let Some(root_name) = root_name {
NbtTag::String(root_name.clone()).serialize_data(&mut self.output)?;
NbtTag::write_string(root_name, &mut self.output)?;
}
}
}
@ -246,7 +246,7 @@ impl<W: Write> ser::Serializer for &mut Serializer<W> {
if self.state == State::MapKey {
self.state = State::Named(v.to_string());
} else {
NbtTag::String(v.to_string()).serialize_data(&mut self.output)?;
NbtTag::write_string(v, &mut self.output)?;
}
Ok(())

View File

@ -32,31 +32,13 @@ impl NbtTag {
unsafe { *(self as *const Self as *const u8) }
}
pub fn serialize<W: Write>(&self, w: &mut WriteAdaptor<W>) -> serializer::Result<()> {
pub fn serialize<W: Write>(self, w: &mut WriteAdaptor<W>) -> serializer::Result<()> {
w.write_u8_be(self.get_type_id())?;
self.serialize_data(w)?;
Ok(())
}
pub fn serialize_data<W: Write>(&self, w: &mut WriteAdaptor<W>) -> serializer::Result<()> {
match self {
NbtTag::End => {}
NbtTag::Byte(byte) => w.write_i8_be(*byte)?,
NbtTag::Short(short) => w.write_i16_be(*short)?,
NbtTag::Int(int) => w.write_i32_be(*int)?,
NbtTag::Long(long) => w.write_i64_be(*long)?,
NbtTag::Float(float) => w.write_f32_be(*float)?,
NbtTag::Double(double) => w.write_f64_be(*double)?,
NbtTag::ByteArray(byte_array) => {
let len = byte_array.len();
if len > i32::MAX as usize {
return Err(Error::LargeLength(len));
}
w.write_i32_be(len as i32)?;
w.write_slice(byte_array)?;
}
NbtTag::String(string) => {
pub fn write_string<W: Write>(string: &str, w: &mut WriteAdaptor<W>) -> serializer::Result<()> {
let java_string = cesu8::to_java_cesu8(string);
let len = java_string.len();
if len > u16::MAX as usize {
@ -65,6 +47,29 @@ impl NbtTag {
w.write_u16_be(len as u16)?;
w.write_slice(&java_string)?;
Ok(())
}
pub fn serialize_data<W: Write>(self, w: &mut WriteAdaptor<W>) -> serializer::Result<()> {
match self {
NbtTag::End => {}
NbtTag::Byte(byte) => w.write_i8_be(byte)?,
NbtTag::Short(short) => w.write_i16_be(short)?,
NbtTag::Int(int) => w.write_i32_be(int)?,
NbtTag::Long(long) => w.write_i64_be(long)?,
NbtTag::Float(float) => w.write_f32_be(float)?,
NbtTag::Double(double) => w.write_f64_be(double)?,
NbtTag::ByteArray(byte_array) => {
let len = byte_array.len();
if len > i32::MAX as usize {
return Err(Error::LargeLength(len));
}
w.write_i32_be(len as i32)?;
w.write_slice(&byte_array)?;
}
NbtTag::String(string) => {
Self::write_string(&string, w)?;
}
NbtTag::List(list) => {
let len = list.len();
@ -89,7 +94,7 @@ impl NbtTag {
w.write_i32_be(len as i32)?;
for int in int_array {
w.write_i32_be(*int)?;
w.write_i32_be(int)?;
}
}
NbtTag::LongArray(long_array) => {
@ -101,7 +106,7 @@ impl NbtTag {
w.write_i32_be(len as i32)?;
for long in long_array {
w.write_i64_be(*long)?;
w.write_i64_be(long)?;
}
}
};
@ -307,9 +312,9 @@ impl NbtTag {
}
}
pub fn extract_byte_array(&self) -> Option<Box<[u8]>> {
pub fn extract_byte_array(&self) -> Option<&[u8]> {
match self {
NbtTag::ByteArray(byte_array) => Some(byte_array.clone()),
NbtTag::ByteArray(byte_array) => Some(byte_array),
_ => None,
}
}

View File

@ -64,7 +64,7 @@ impl Ack {
let mut start = self.sequences[0];
let mut end = start;
for seq in self.sequences.clone() {
for seq in self.sequences.iter().copied() {
if seq == end + 1 {
end = seq
} else {

View File

@ -128,7 +128,7 @@ impl ClientPacket for CChunkData<'_> {
write.write_var_int(&VarInt(block_entity.get_id() as i32))?;
if let Some(nbt) = block_entity.chunk_data_nbt() {
write.write_nbt(&nbt.into())?;
write.write_nbt(nbt.into())?;
} else {
write.write_u8(END_ID)?;
}

View File

@ -285,7 +285,7 @@ pub trait NetworkWriteExt {
Ok(())
}
fn write_nbt(&mut self, data: &NbtTag) -> Result<(), WritingError>;
fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError>;
}
macro_rules! write_number_be {
@ -400,7 +400,7 @@ impl<W: Write> NetworkWriteExt for W {
Ok(())
}
fn write_nbt(&mut self, data: &NbtTag) -> Result<(), WritingError> {
fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError> {
let mut write_adaptor = WriteAdaptor::new(self);
data.serialize(&mut write_adaptor)
.map_err(|e| WritingError::Message(e.to_string()))?;

View File

@ -363,8 +363,8 @@ impl BlockPos {
x + y + z
}
pub fn squared_distance(&self, other: Self) -> i32 {
self.0.squared_distance_to_vec(other.0)
pub fn squared_distance(&self, other: &Self) -> i32 {
self.0.squared_distance_to_vec(&other.0)
}
pub fn as_long(&self) -> i64 {

View File

@ -151,7 +151,7 @@ impl<T: Math + PartialOrd + Copy> Vector3<T> {
}
}
pub fn squared_distance_to_vec(&self, other: Self) -> T {
pub fn squared_distance_to_vec(&self, other: &Self) -> T {
self.squared_distance_to(other.x, other.y, other.z)
}

View File

@ -244,13 +244,11 @@ impl OctavePerlinNoiseSampler {
result
})
.collect();
let lacunarities: Vec<f64> = (0..amplitudes.len())
.map(|_| {
let lacunarities = (0..amplitudes.len()).map(|_| {
let result = lacunarity;
lacunarity *= 2.0;
result
})
.collect();
});
let max_value = Self::get_total_amplitude_generic(2.0, &persistences, amplitudes);

View File

@ -123,31 +123,24 @@ impl Compression {
const CUSTOM_ID: u8 = 127;
fn decompress_data(&self, compressed_data: &[u8]) -> Result<Box<[u8]>, CompressionError> {
let initial_capacity = compressed_data.len();
fn decode<R: std::io::Read>(mut reader: R, capacity: usize) -> std::io::Result<Box<[u8]>> {
let mut buf = Vec::with_capacity(capacity);
reader.read_to_end(&mut buf)?;
Ok(buf.into_boxed_slice())
}
match self {
Compression::GZip => {
let mut decoder = GzDecoder::new(compressed_data);
let mut chunk_data = Vec::new();
decoder
.read_to_end(&mut chunk_data)
.map_err(CompressionError::GZipError)?;
Ok(chunk_data.into_boxed_slice())
}
Compression::ZLib => {
let mut decoder = ZlibDecoder::new(compressed_data);
let mut chunk_data = Vec::new();
decoder
.read_to_end(&mut chunk_data)
.map_err(CompressionError::ZlibError)?;
Ok(chunk_data.into_boxed_slice())
}
Compression::LZ4 => {
let mut decoder = lz4_java_wrc::Lz4BlockInput::new(compressed_data);
let mut decompressed_data = Vec::new();
decoder
.read_to_end(&mut decompressed_data)
.map_err(CompressionError::LZ4Error)?;
Ok(decompressed_data.into_boxed_slice())
}
Compression::GZip => decode(GzDecoder::new(compressed_data), initial_capacity)
.map_err(CompressionError::GZipError),
Compression::ZLib => decode(ZlibDecoder::new(compressed_data), initial_capacity)
.map_err(CompressionError::ZlibError),
Compression::LZ4 => decode(
lz4_java_wrc::Lz4BlockInput::new(compressed_data),
initial_capacity,
)
.map_err(CompressionError::LZ4Error),
Compression::Custom => todo!(),
}
}
@ -349,7 +342,10 @@ impl<S: SingleChunkDataSerializer> AnvilChunkFile<S> {
index as usize
}
async fn write_indices(&self, path: &Path, indices: &[usize]) -> Result<(), std::io::Error> {
async fn write_indices<I>(&self, path: &Path, indices: I) -> Result<(), std::io::Error>
where
I: IntoIterator<Item = usize>,
{
log::trace!("Writing in place: {}", path.display());
let file = tokio::fs::OpenOptions::new()
@ -392,11 +388,11 @@ impl<S: SingleChunkDataSerializer> AnvilChunkFile<S> {
}
let mut chunks = indices
.iter()
.into_iter()
.map(|index| {
(
index,
self.chunks_data[*index]
self.chunks_data[index]
.as_ref()
.expect("We are trying to write a chunk, but it does not exist!"),
)
@ -547,10 +543,7 @@ impl<S: SingleChunkDataSerializer> ChunkSerializer for AnvilChunkFile<S> {
Ok(())
}
WriteAction::All => self.write_all(path).await,
WriteAction::Parts(parts) => {
self.write_indices(path, Vec::from_iter(parts.iter().cloned()).as_slice())
.await
}
WriteAction::Parts(parts) => self.write_indices(path, parts.iter().copied()).await,
}?;
// If we still are in memory after this, we don't need to write again!

View File

@ -71,15 +71,12 @@ impl LinearChunkHeader {
}
}
fn to_bytes(self) -> Box<[u8]> {
let mut bytes = Vec::with_capacity(LinearChunkHeader::CHUNK_HEADER_SIZE);
pub fn to_bytes(self) -> [u8; LinearChunkHeader::CHUNK_HEADER_SIZE] {
let mut bytes = [0u8; LinearChunkHeader::CHUNK_HEADER_SIZE];
bytes[0..4].copy_from_slice(&self.size.to_be_bytes());
bytes[4..8].copy_from_slice(&self.timestamp.to_be_bytes());
bytes.put_u32(self.size);
bytes.put_u32(self.timestamp);
// This should be a clear code error if the size of the header is not the expected
// so we can unwrap the conversion safely or panic the entire program if not
bytes.into_boxed_slice()
bytes
}
}

View File

@ -167,8 +167,8 @@ impl ChunkData {
z: position.y,
// This chunk is read from disk, so it has not been modified
dirty: false,
block_ticks: ChunkTickScheduler::from_vec(&chunk_data.block_ticks),
fluid_ticks: ChunkTickScheduler::from_vec(&chunk_data.fluid_ticks),
block_ticks: ChunkTickScheduler::from_iter(chunk_data.block_ticks),
fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks),
block_entities: {
let mut block_entities = HashMap::new();
for nbt in chunk_data.block_entities {

View File

@ -166,11 +166,9 @@ where
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
let paths: Vec<_> = chunks
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
.collect();
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
Box::pin(async move {
let mut watchers = self.watchers.write().await;
for path in paths {
@ -187,10 +185,9 @@ where
folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> {
let paths: Vec<_> = chunks
let paths = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
.collect();
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)));
Box::pin(async move {
let mut watchers = self.watchers.write().await;

View File

@ -138,20 +138,20 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> PalettedContainer<V, DIM>
}
pub fn from_palette_and_packed_data(
palette_slice: &[V],
palette: Vec<V>,
packed_data: &[i64],
minimum_bits_per_entry: u8,
) -> Self {
if palette_slice.is_empty() {
if palette.is_empty() {
log::warn!("No palette data! Defaulting...");
return Self::Homogeneous(V::default());
}
if palette_slice.len() == 1 {
return Self::Homogeneous(palette_slice[0]);
if palette.len() == 1 {
return Self::Homogeneous(palette[0]);
}
let bits_per_key = encompassing_bits(palette_slice.len()).max(minimum_bits_per_entry);
let bits_per_key = encompassing_bits(palette.len()).max(minimum_bits_per_entry);
let index_mask = (1 << bits_per_key) - 1;
let keys_per_i64 = 64 / bits_per_key;
@ -174,7 +174,7 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> PalettedContainer<V, DIM>
>> (bit_index_in_word as u64 * bits_per_key as u64))
& index_mask;
let value = palette_slice
let value = palette
.get(lookup_index as usize)
.copied()
.unwrap_or_else(|| {
@ -186,12 +186,12 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> PalettedContainer<V, DIM>
}
// Now, with all decompressed values, build the counts.
let mut counts = vec![0; palette_slice.len()];
let mut counts = vec![0; palette.len()];
for &value in &decompressed_values {
// This is the key optimization: find the index in the palette Vec
// and increment the corresponding count.
if let Some(index) = palette_slice.iter().position(|v| v == &value) {
if let Some(index) = palette.iter().position(|v| v == &value) {
counts[index] += 1;
} else {
// This case should ideally not happen if the palette is complete.
@ -204,11 +204,9 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> PalettedContainer<V, DIM>
.as_flattened_mut()
.copy_from_slice(&decompressed_values);
let palette_vec: Vec<V> = palette_slice.to_vec();
Self::Heterogeneous(Box::new(HeterogeneousPaletteData {
cube,
palette: palette_vec,
palette,
counts,
}))
}
@ -336,7 +334,7 @@ impl BiomePalette {
.collect::<Vec<_>>();
Self::from_palette_and_packed_data(
&palette,
palette,
nbt.data.as_ref().unwrap_or(&Box::default()),
BIOME_DISK_MIN_BITS,
)
@ -515,7 +513,7 @@ impl BlockPalette {
.collect::<Vec<_>>();
Self::from_palette_and_packed_data(
&palette,
palette,
nbt.data.as_ref().unwrap_or(&Box::default()),
BLOCK_DISK_MIN_BITS,
)

View File

@ -138,7 +138,7 @@ impl PlayerDataStorage {
// Create the file and write directly with GZip compression
match File::create(&path) {
Ok(file) => {
if let Err(e) = pumpkin_nbt::nbt_compress::write_gzip_compound_tag(&data, file) {
if let Err(e) = pumpkin_nbt::nbt_compress::write_gzip_compound_tag(data, file) {
log::error!("Failed to write compressed player data for {uuid}: {e}");
Err(PlayerDataError::Nbt(e.to_string()))
} else {

View File

@ -30,10 +30,7 @@ impl CoralClawFeature {
let direction = BlockDirection::horizontal()
[random.next_bounded_i32(BlockDirection::horizontal().len() as i32 - 1) as usize];
// TODO: Shuffle
let directions: Vec<_> = BlockDirection::horizontal()
.into_iter()
.take(i as usize)
.collect();
let directions = BlockDirection::horizontal().into_iter().take(i as usize);
'block0: for direction2 in directions {
let mut pos = pos;
let j = random.next_bounded_i32(2) + 1;

View File

@ -34,10 +34,7 @@ impl CoralTreeFeature {
let i = random.next_bounded_i32(3) + 2;
// TODO: Shuffle
let directions: Vec<_> = BlockDirection::horizontal()
.into_iter()
.take(i as usize)
.collect();
let directions = BlockDirection::horizontal().into_iter().take(i as usize);
for dir in directions {
pos = pos.offset(dir.to_offset());
let times = random.next_bounded_i32(5) + 2;

View File

@ -12,6 +12,7 @@ pub mod height_provider;
pub mod noise;
pub mod positions;
pub mod proto_chunk;
pub mod proto_chunk_test;
pub mod rule;
mod rule_test;
pub mod settings;

View File

@ -1164,684 +1164,3 @@ impl BlockAccessor for ProtoChunk {
})
}
}
#[cfg(test)] // TODO: Fix tests to work with new ProtoChunk API
mod test {
/*
TODO: Update all tests to work with the new ProtoChunk API that doesn't use lifetimes.
The new API requires passing noise samplers and other dependencies as parameters to methods
instead of storing them in the struct.
const SEED: u64 = 0;
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
LazyLock::new(|| GlobalRandomConfig::new(SEED, false)); // TODO: use legacy when needed
static TERRAIN_CACHE: LazyLock<TerrainCache> =
LazyLock::new(|| TerrainCache::from_random(&RANDOM_CONFIG));
static BASE_NOISE_ROUTER: LazyLock<ProtoNoiseRouters> =
LazyLock::new(|| ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG));
const SEED2: u64 = 13579;
static RANDOM_CONFIG2: LazyLock<GlobalRandomConfig> =
LazyLock::new(|| GlobalRandomConfig::new(SEED2, false)); // TODO: use legacy when needed
static BASE_NOISE_ROUTER2: LazyLock<ProtoNoiseRouters> = LazyLock::new(|| {
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG2)
});
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_cache() {
// We say no wrapper, but it technically has a top-level cell cache
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk");
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_2d_cache() {
// it technically has a top-level cell cache
// should be the same as only cell_cache
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk");
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::Cache2D => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_flat_cache() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_flat_cache_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::CacheFlat => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_once_cache() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_once_cache_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::CacheOnce => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_interpolated() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_interpolated_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::Interpolated => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard() {
let _expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_0_0.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
// TODO: Create ProtoChunk and call populate_noise with proper parameters
let _chunk = ProtoChunk::new(
Vector2::new(0, 0),
surface_config,
surface_config.default_block.get_state(),
0, // biome_mixer_seed
);
// assert_eq!(
// expected_data,
// chunk.flat_block_map.into_iter().collect::<Vec<u16>>()
// );
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_aquifer() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_7_4.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(7, 4),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
assert_eq!(
expected_data,
chunk.flat_block_map.into_iter().collect::<Vec<u16>>()
);
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_-595_544.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-595, 544),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_frozen_ocean() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_-119_183.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-119, 183),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands2() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_13579_-6_11.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-6, 11),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands3() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_13579_-2_15.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-2, 15),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_0_0.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_badlands_-595_544.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-595, 544),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands2() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-6_11.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-6, 11),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands3() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-7_9.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-7, 9),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_biome_blend() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-2_15.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-2, 15),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_frozen_ocean() {
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_surface_frozen_ocean_-119_183.chunk"
);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-119, 183),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
*/
}

View File

@ -0,0 +1,680 @@
#[cfg(test)] // TODO: Fix tests to work with new ProtoChunk API
mod test {
/*
TODO: Update all tests to work with the new ProtoChunk API that doesn't use lifetimes.
The new API requires passing noise samplers and other dependencies as parameters to methods
instead of storing them in the struct.
const SEED: u64 = 0;
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
LazyLock::new(|| GlobalRandomConfig::new(SEED, false)); // TODO: use legacy when needed
static TERRAIN_CACHE: LazyLock<TerrainCache> =
LazyLock::new(|| TerrainCache::from_random(&RANDOM_CONFIG));
static BASE_NOISE_ROUTER: LazyLock<ProtoNoiseRouters> =
LazyLock::new(|| ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG));
const SEED2: u64 = 13579;
static RANDOM_CONFIG2: LazyLock<GlobalRandomConfig> =
LazyLock::new(|| GlobalRandomConfig::new(SEED2, false)); // TODO: use legacy when needed
static BASE_NOISE_ROUTER2: LazyLock<ProtoNoiseRouters> = LazyLock::new(|| {
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG2)
});
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_cache() {
// We say no wrapper, but it technically has a top-level cell cache
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk");
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_2d_cache() {
// it technically has a top-level cell cache
// should be the same as only cell_cache
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk");
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::Cache2D => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_flat_cache() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_flat_cache_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::CacheFlat => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_once_cache() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_once_cache_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::CacheOnce => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_only_cell_interpolated() {
// it technically has a top-level cell cache
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_only_cell_cache_interpolated_0_0.chunk"
);
let mut base_router =
ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG);
macro_rules! set_wrappers {
($stack: expr) => {
$stack.iter_mut().for_each(|component| {
if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component {
match wrapper.wrapper_type {
WrapperType::CellCache => (),
WrapperType::Interpolated => (),
_ => {
*component =
ProtoNoiseFunctionComponent::PassThrough(PassThrough::new(
wrapper.input_index,
wrapper.min(),
wrapper.max(),
));
}
}
}
});
};
}
set_wrappers!(base_router.noise.full_component_stack);
set_wrappers!(base_router.surface_estimator.full_component_stack);
set_wrappers!(base_router.multi_noise.full_component_stack);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&base_router,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("{expected} vs {actual} ({index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard() {
let _expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_0_0.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
// TODO: Create ProtoChunk and call populate_noise with proper parameters
let _chunk = ProtoChunk::new(
Vector2::new(0, 0),
surface_config,
surface_config.default_block.get_state(),
0, // biome_mixer_seed
);
// assert_eq!(
// expected_data,
// chunk.flat_block_map.into_iter().collect::<Vec<u16>>()
// );
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_aquifer() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_7_4.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(7, 4),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
assert_eq!(
expected_data,
chunk.flat_block_map.into_iter().collect::<Vec<u16>>()
);
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_-595_544.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-595, 544),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_frozen_ocean() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_-119_183.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-119, 183),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands2() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_13579_-6_11.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-6, 11),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_badlands3() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_13579_-2_15.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-2, 15),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_noise();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_0_0.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(0, 0),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_badlands_-595_544.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-595, 544),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands2() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-6_11.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-6, 11),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_badlands3() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-7_9.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-7, 9),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_biome_blend() {
let expected_data: Vec<u16> =
read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-2_15.chunk");
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let terrain_cache = TerrainCache::from_random(&RANDOM_CONFIG2);
let mut chunk = ProtoChunk::new(
Vector2::new(-2, 15),
&BASE_NOISE_ROUTER2,
&RANDOM_CONFIG2,
surface_config,
&terrain_cache,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
}
#[test]
#[ignore] // TODO: Update this test to work with new API
fn test_no_blend_no_beard_surface_frozen_ocean() {
let expected_data: Vec<u16> = read_data_from_file!(
"../../assets/no_blend_no_beard_surface_frozen_ocean_-119_183.chunk"
);
let surface_config = GENERATION_SETTINGS
.get(&GeneratorSetting::Overworld)
.unwrap();
let mut chunk = ProtoChunk::new(
Vector2::new(-119, 183),
&BASE_NOISE_ROUTER,
&RANDOM_CONFIG,
surface_config,
&TERRAIN_CACHE,
surface_config.default_block.get_state(),
);
chunk.populate_biomes(Dimension::Overworld);
chunk.populate_noise();
chunk.build_surface();
expected_data
.into_iter()
.zip(chunk.flat_block_map)
.enumerate()
.for_each(|(index, (expected, actual))| {
if expected != actual {
panic!("expected {expected}, was {actual} (at {index})");
}
});
*/
}

View File

@ -435,12 +435,7 @@ impl Level {
};
let mut rng = SmallRng::from_rng(&mut rand::rng());
let chunks = self
.loaded_chunks
.iter()
.map(|x| x.value().clone())
.collect::<Vec<_>>();
for chunk_sync in chunks {
for chunk_sync in self.loaded_chunks.iter() {
// Try to get a READ lock first.
// Most chunks won't have pending ticks, so this is very fast.
let chunk = chunk_sync.read().await;
@ -595,14 +590,18 @@ impl Level {
let cancel_notifier = level.cancel_token.cancelled();
let fetch_task = async {
let mut to_fetch = Vec::new();
for pos in &chunks {
let to_fetch: Vec<_> = chunks
.iter()
.filter(|pos| {
if let Some(chunk) = level.loaded_entity_chunks.get(pos) {
let _ = sender.send((chunk.clone(), false));
false // Don't fetch
} else {
to_fetch.push(*pos);
}
true // Needs fetch
}
})
.copied()
.collect();
if !to_fetch.is_empty() {
let (tx, mut rx) = tokio::sync::mpsc::channel::<

View File

@ -53,11 +53,22 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
}
res
}
}
pub fn from_vec(ticks: &[ScheduledTick<&'a T>]) -> Self {
impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
for ChunkTickScheduler<&'a T>
{
fn from_iter<I: IntoIterator<Item = ScheduledTick<&'a T>>>(iter: I) -> Self {
let mut scheduler = Self::default();
for tick in ticks {
scheduler.schedule_tick(tick, 0);
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
if lower > 0 {
scheduler.queued_ticks.reserve(lower);
}
for tick in iter {
scheduler.schedule_tick(&tick, 0);
}
scheduler
}

View File

@ -135,12 +135,6 @@ impl CommandBlock {
if command.is_empty() {
command_entity.success_count.store(0, Ordering::Release);
} else {
if command == "Searge" && command_entity.track_output.load(Ordering::Relaxed) {
let mut last_output = command_entity.last_output.lock().await;
*last_output = "#itzlipofutzli".to_string();
return;
}
server
.command_dispatcher
.read()

View File

@ -193,32 +193,36 @@ impl FromStr for TargetSelector {
fn from_str(arg: &str) -> Result<Self, Self::Err> {
if arg.starts_with('@') {
let body: Vec<_> = arg.splitn(2, '[').collect();
let r#type = match body[0] {
let (type_str, arguments) = arg
.find('[')
.map_or((arg, None), |idx| (&arg[..idx], Some(&arg[idx + 1..])));
let selector_type = match type_str {
"@a" => EntitySelectorType::AllPlayers,
"@e" => EntitySelectorType::AllEntities,
"@s" => EntitySelectorType::Source,
"@p" => EntitySelectorType::NearestPlayer,
"@r" => EntitySelectorType::RandomPlayer,
"@n" => EntitySelectorType::NearestEntity,
_ => return Err(format!("Invalid target selector type {}", body[0])),
_ => return Err(format!("Invalid target selector type {type_str}")),
};
let mut selector = Self::new(r#type);
if body.len() < 2 {
// No conditions specified, return the selector with default conditions
return Ok(selector);
}
// parse conditions
if body[1].as_bytes()[body[1].len() - 1] != b']' {
return Err("Target selector must end with ]".to_string());
}
let conditions: Vec<_> = body[1][..body[1].len() - 1]
let mut selector = Self::new(selector_type);
if let Some(args_content) = arguments {
let trimmed_args = args_content
.strip_suffix(']')
.ok_or_else(|| "Target selector must end with ]".to_string())?;
for s in trimmed_args
.split(',')
.map(str::trim)
.collect();
for s in conditions {
.filter(|s| !s.is_empty())
{
selector.conditions.push(EntityFilter::from_str(s)?);
}
}
Ok(selector)
} else if let Ok(uuid) = Uuid::parse_str(arg) {
Ok(Self::new(EntitySelectorType::Uuid(uuid)))

View File

@ -121,7 +121,7 @@ impl CommandExecutor for Executor {
// Check if player can hear the sound based on volume and distance
let player_pos = target.living_entity.entity.pos.load();
let distance = player_pos.squared_distance_to_vec(pos);
let distance = player_pos.squared_distance_to_vec(&pos);
let max_distance: f64 = (16.0 * volume).into(); // 16 blocks is base distance at volume 1.0
if distance <= max_distance || min_volume > 0.0 {

View File

@ -43,7 +43,7 @@ impl CommandExecutor for ListExecutor {
};
let mut message = TextComponent::text(message_text);
for (i, metadata) in plugins.clone().into_iter().enumerate() {
for (i, metadata) in plugins.iter().enumerate() {
let fmt = if i == plugins.len() - 1 {
metadata.name.to_string()
} else {

View File

@ -1,5 +1,4 @@
use crate::command::tree::{CommandTree, Node, NodeType};
use std::collections::VecDeque;
use std::fmt::{Display, Formatter, Write};
trait IsVisible {
@ -52,65 +51,48 @@ fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec<usize> {
impl Display for CommandTree {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_char('/')?;
f.write_str(&self.names[0])?;
write!(f, "/{}", self.names[0])?;
let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len());
todo.push_back(&self.children);
let current_children = &self.children[..];
loop {
let Some(children) = todo.pop_front() else {
while !current_children.is_empty() {
let flattened = flatten_require_nodes(&self.nodes, current_children);
let mut visible_iter = flattened
.iter()
.copied()
.filter(|&i| self.nodes[i].is_visible());
let Some(first_idx) = visible_iter.next() else {
break;
};
let flattened_children = flatten_require_nodes(&self.nodes, children);
let visible_children = flattened_children
.iter()
.copied()
.filter(|&i| self.nodes[i].is_visible())
.collect::<Vec<_>>();
if visible_children.is_empty() {
break;
}
let second_idx = visible_iter.next();
f.write_char(' ')?;
let is_optional = flattened_children
let is_optional = flattened
.iter()
.map(|&i| &self.nodes[i].node_type)
.any(|node| matches!(node, NodeType::ExecuteLeaf { .. }));
.any(|&i| matches!(self.nodes[i].node_type, NodeType::ExecuteLeaf { .. }));
if is_optional {
f.write_char('[')?;
}
match visible_children.as_slice() {
[] => unreachable!(),
[i] => {
let node = &self.nodes[*i];
node.fmt(f)?;
todo.push_back(&node.children);
}
_ => {
// todo: handle cases where one of these nodes has visible children
if let Some(second_idx) = second_idx {
f.write_char('(')?;
self.nodes[first_idx].fmt(f)?;
let mut iter = visible_children.iter().map(|&i| &self.nodes[i]);
write!(f, " | ")?;
self.nodes[second_idx].fmt(f)?;
if let Some(node) = iter.next() {
node.fmt(f)?;
for idx in visible_iter {
write!(f, " | ")?;
self.nodes[idx].fmt(f)?;
}
for node in iter {
f.write_str(" | ")?;
node.fmt(f)?;
}
f.write_char(')')?;
}
break;
}
if is_optional {

View File

@ -36,7 +36,7 @@ impl Goal for CreeperIgniteGoal {
.get_entity()
.pos
.load()
.squared_distance_to_vec(target.get_entity().pos.load());
.squared_distance_to_vec(&target.get_entity().pos.load());
return dist_sq < 9.0;
}
@ -72,7 +72,7 @@ impl Goal for CreeperIgniteGoal {
.get_entity()
.pos
.load()
.squared_distance_to_vec(target.get_entity().pos.load());
.squared_distance_to_vec(&target.get_entity().pos.load());
if dist_sq > 49.0 {
self.creeper.set_fuse_speed(-1).await;

View File

@ -119,7 +119,7 @@ impl Goal for LookAtEntityGoal {
}
let mob_pos = mob_entity.living_entity.entity.pos.load();
let target_pos = target.get_entity().pos.load();
if mob_pos.squared_distance_to_vec(target_pos) as f32 > (self.range * self.range) {
if mob_pos.squared_distance_to_vec(&target_pos) as f32 > (self.range * self.range) {
return false;
}
return self.look_time > 0;

View File

@ -88,7 +88,7 @@ impl Goal for MeleeAttackGoal {
if mob
.get_mob_entity()
.is_in_position_target_range_pos(target.get_entity().block_pos.load())
.is_in_position_target_range_pos(&target.get_entity().block_pos.load())
{
// This is sync based on the assumed Player methods
target

View File

@ -87,7 +87,7 @@ impl<M: MoveToTargetPos> MoveToTargetPosGoal<M> {
if mob
.get_mob_entity()
.is_in_position_target_range_pos(block_pos_mut)
.is_in_position_target_range_pos(&block_pos_mut)
&& can_target
{
self.target_pos = block_pos_mut;
@ -177,7 +177,7 @@ impl<M: MoveToTargetPos> Goal for MoveToTargetPosGoal<M> {
};
let desired_distance = move_to_target_pos.get_desired_distance_to_target();
if block_pos.squared_distance_to_vec(mob.get_entity().pos.load())
if block_pos.squared_distance_to_vec(&mob.get_entity().pos.load())
< desired_distance * desired_distance
{
self.reached = true;

View File

@ -49,8 +49,7 @@ impl Navigator {
let state = world
.get_block_state(&BlockPos(potential_pos.to_i32()))
.await;
let shapes = state.get_block_collision_shapes();
if !shapes.is_empty() {
if !state.collision_shapes.is_empty() {
continue;
}
@ -100,6 +99,6 @@ impl Node {
/// Returns an `f64`; higher means more expensive.
#[must_use]
pub fn get_expense(&self, end: Vector3<f64>) -> f64 {
self.location.squared_distance_to_vec(end).sqrt()
self.location.squared_distance_to_vec(&end).sqrt()
}
}

View File

@ -87,12 +87,9 @@ impl ItemEntity {
async fn try_merge(&self) {
let bounding_box = self.entity.bounding_box.load().expand(0.5, 0.0, 0.5);
let items: Vec<_> = self
.entity
.world
.entities
.read()
.await
let entities_guard = self.entity.world.entities.read().await;
let items = entities_guard
.values()
.filter_map(|entity: &Arc<dyn EntityBase>| {
entity.clone().get_item_entity().filter(|item| {
@ -100,8 +97,7 @@ impl ItemEntity {
&& !item.never_despawn.load(Ordering::Relaxed)
&& item.entity.bounding_box.load().intersects(&bounding_box)
})
})
.collect();
});
for item in items {
if item.can_merge().await {

View File

@ -322,7 +322,7 @@ impl LivingEntity {
f64::from(pos.0.y) + 0.5,
f64::from(pos.0.z) + 0.5,
);
let distance = entity_pos.squared_distance_to_vec(block_center);
let distance = entity_pos.squared_distance_to_vec(&block_center);
return distance.sqrt()
<= Self::FALL_DAMAGE_SAFE_DISTANCE * Self::FALL_DAMAGE_SAFE_DISTANCE;

View File

@ -57,10 +57,10 @@ impl MobEntity {
}
}
pub fn is_in_position_target_range(&self) -> bool {
self.is_in_position_target_range_pos(self.living_entity.entity.block_pos.load())
self.is_in_position_target_range_pos(&self.living_entity.entity.block_pos.load())
}
pub fn is_in_position_target_range_pos(&self, block_pos: BlockPos) -> bool {
pub fn is_in_position_target_range_pos(&self, block_pos: &BlockPos) -> bool {
let position_target_range = self.position_target_range.load(Relaxed);
if position_target_range == -1 {
true

View File

@ -984,7 +984,7 @@ impl Entity {
}
let mut fluid_velo =
self.world.get_fluid_velocity(pos, &fluid, &state).await;
self.world.get_fluid_velocity(pos, fluid, state).await;
if fluid_height[i] < 0.4 {
fluid_velo = fluid_velo * fluid_height[i];
@ -1006,7 +1006,7 @@ impl Entity {
for (_, fluid) in fluids {
self.world
.block_registry
.on_entity_collision_fluid(&fluid, caller.as_ref())
.on_entity_collision_fluid(fluid, caller.as_ref())
.await;
}
@ -1701,8 +1701,7 @@ impl Entity {
let (block, state) = world.get_block_and_state(&pos).await;
let block_outlines = state.get_block_outline_shapes();
if let Some(outlines) = block_outlines {
if outlines.is_empty() {
if state.outline_shapes.is_empty() {
world
.block_registry
.on_entity_collision(block, world, entity, &pos, state, server)
@ -1714,7 +1713,7 @@ impl Entity {
.await;
continue;
}
for outline in outlines {
for outline in block_outlines {
let outline_aabb = outline.at_pos(pos);
if outline_aabb.intersects(&aabb) {
world
@ -1729,17 +1728,6 @@ impl Entity {
break;
}
}
} else {
world
.block_registry
.on_entity_collision(block, world, entity, &pos, state, server)
.await;
let fluid = world.get_fluid(&pos).await;
world
.block_registry
.on_entity_collision_fluid(fluid, entity)
.await;
}
}
}
}

View File

@ -1019,15 +1019,6 @@ impl JavaClient {
return Err(ChatError::InvalidPublicKey);
}
// Verify signature with RSA-SHA1
let mojang_verifying_keys = server
.mojang_public_keys
.lock()
.await
.iter()
.map(|key| VerifyingKey::<Sha1>::new(key.clone()))
.collect::<Vec<_>>();
let key_signature = RsaPkcs1v15Signature::try_from(session.key_signature.as_ref())
.map_err(|_| ChatError::InvalidPublicKey)?;
@ -1036,11 +1027,16 @@ impl JavaClient {
signable.extend_from_slice(&session.expires_at.to_be_bytes());
signable.extend_from_slice(&session.public_key);
let public_keys_guard = server.mojang_public_keys.lock().await;
// Verify signature with RSA-SHA1
let is_valid = public_keys_guard.iter().any(|key| {
let verifying_key = VerifyingKey::<Sha1>::new(key.clone());
verifying_key.verify(&signable, &key_signature).is_ok()
});
// Verify that the signable is valid for any one of Mojang's public keys
if !mojang_verifying_keys
.iter()
.any(|key| key.verify(&signable, &key_signature).is_ok())
{
if !is_valid {
return Err(ChatError::InvalidPublicKey);
}
@ -2063,10 +2059,10 @@ impl JavaClient {
.await;
// Check if there is a player in the way of the block being placed
let shapes = BlockState::from_id(new_state).get_block_collision_shapes();
let state = BlockState::from_id(new_state);
for player in world.get_nearby_players(location.0.to_f64(), 3.0).await {
let player_box = player.1.living_entity.entity.bounding_box.load();
for shape in &shapes {
for shape in state.get_block_collision_shapes() {
if shape.at_pos(final_block_pos).intersects(&player_box) {
return Ok(false);
}

View File

@ -1,6 +1,5 @@
use std::{net::IpAddr, net::SocketAddr};
use pumpkin_protocol::Property;
use thiserror::Error;
use tokio::sync::Mutex;
@ -35,30 +34,30 @@ pub async fn bungeecord_login(
server_address: &str,
name: String,
) -> Result<(IpAddr, GameProfile), BungeeCordError> {
let data = server_address.split('\0').take(4).collect::<Vec<_>>();
let mut parts = server_address.split('\0');
// The IP address of the player; only given if `ip_forward` on bungee is true.
let ip = match data.get(1) {
Some(ip) => ip
// Skip the first part (the actual server address/host)
let _host = parts.next();
let ip = match parts.next() {
Some(ip_str) if !ip_str.is_empty() => ip_str
.parse()
.map_err(|_| BungeeCordError::FailedParseAddress)?,
None => client_address.lock().await.ip(),
_ => client_address.lock().await.ip(),
};
// The UUID of the player; only given if `ip_forward` on bungee is true.
let id = match data.get(2) {
Some(uuid) => uuid.parse().map_err(|_| BungeeCordError::FailedParseUUID)?,
None => offline_uuid(name.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?,
let id = match parts.next() {
Some(uuid_str) if !uuid_str.is_empty() => uuid_str
.parse()
.map_err(|_| BungeeCordError::FailedParseUUID)?,
_ => offline_uuid(&name).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?,
};
// Read properties and get textures.
// Properties of the player's game profile are only given if `ip_forward` and `online_mode`
// on bungee are both `true`.
let properties: Vec<Property> = match data.get(3) {
Some(properties) => {
serde_json::from_str(properties).map_err(|_| BungeeCordError::FailedParseProperties)?
let properties = match parts.next() {
Some(json_str) if !json_str.is_empty() => {
serde_json::from_str(json_str).map_err(|_| BungeeCordError::FailedParseProperties)?
}
None => vec![],
_ => Vec::new(),
};
Ok((

View File

@ -26,8 +26,7 @@ impl KeyStore {
let public_key_der = public_key
.to_public_key_der()
.expect("Failed to encode public key to SPKI DER")
.as_bytes()
.to_vec()
.into_vec()
.into_boxed_slice();
log::debug!("Created RSA keys, took {}ms", instant.elapsed().as_millis());

View File

@ -600,9 +600,7 @@ impl Server {
let mut players = Vec::<Arc<Player>>::new();
for world in self.worlds.read().await.iter() {
for player in world.players.read().await.values() {
players.push(player.clone());
}
players.extend(world.players.read().await.values().cloned());
}
players
@ -926,8 +924,8 @@ impl Server {
};
let mut entities = iter.collect::<Vec<_>>();
entities.sort_by(|a, b| {
let a_distance = a.get_entity().pos.load().squared_distance_to_vec(center);
let b_distance = b.get_entity().pos.load().squared_distance_to_vec(center);
let a_distance = a.get_entity().pos.load().squared_distance_to_vec(&center);
let b_distance = b.get_entity().pos.load().squared_distance_to_vec(&center);
if target_selector.get_sort() == Some(EntityFilterSort::Nearest) {
a_distance
.partial_cmp(&b_distance)

View File

@ -114,10 +114,9 @@ impl CustomBossbars {
let players: Vec<Arc<Player>> = server.get_all_players().await;
let online_players: Vec<&Arc<Player>> = players
let online_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
if bossbar.visible {
for player in online_players {
@ -170,10 +169,9 @@ impl CustomBossbars {
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
let matching_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
for player in matching_players {
player
.update_bossbar_health(&bossbar.bossbar_data.uuid, bossbar.bossbar_data.health)
@ -206,10 +204,9 @@ impl CustomBossbars {
bossbar.visible = new_visibility;
let players: Vec<Arc<Player>> = server.get_all_players().await;
let online_players: Vec<&Arc<Player>> = players
let online_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
for player in online_players {
if bossbar.visible {
@ -245,10 +242,9 @@ impl CustomBossbars {
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
let matching_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
for player in matching_players {
player
.update_bossbar_title(
@ -284,10 +280,9 @@ impl CustomBossbars {
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
let matching_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
for player in matching_players {
player
.update_bossbar_style(
@ -324,10 +319,9 @@ impl CustomBossbars {
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
let matching_players = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
.filter(|player| bossbar.player.contains(&player.gameprofile.id));
for player in matching_players {
player
.update_bossbar_style(

View File

@ -799,7 +799,7 @@ impl World {
}
}
pub async fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<Fluid> {
pub async fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<&Fluid> {
let mut collisions = Vec::new();
let min = bounding_box.min_block_pos();
@ -854,14 +854,10 @@ impl World {
}
// FlowableFluid.getVelocity()
pub async fn get_fluid_velocity(
&self,
pos0: BlockPos,
fluid0: &Fluid,
state0: &FluidState,
) -> Vector3<f64> {
let mut velo = Vector3::default();
@ -900,9 +896,7 @@ impl World {
}
//let state = fluid.get_state(block_state_id);
let state = fluid.states[0].clone();
amplitude = f64::from(state0.height - state.height);
amplitude = f64::from(state0.height - fluid.states[0].height);
}
if amplitude == 0.0 {
@ -990,7 +984,7 @@ impl World {
}
let mut inside = false;
'shapes: for shape in state.get_block_outline_shapes().unwrap() {
'shapes: for shape in state.get_block_outline_shapes() {
let outline_shape = shape.at_pos(pos);
if outline_shape.intersects(bounding_box) {
@ -1012,30 +1006,32 @@ impl World {
pos: BlockPos,
state: &BlockState,
use_collision_shape: bool,
mut using_collision_shape: F,
mut on_collision: F,
) -> bool
where
F: FnMut(&BoundingBox),
{
if state.is_air() || !state.is_solid() {
return false;
}
let mut shapes = state
.get_block_collision_shapes()
.map(|shape| shape.at_pos(pos));
if use_collision_shape {
let mut collided = false;
if !state.is_air() && state.is_solid() && !state.collision_shapes.is_empty() {
for shape in state.get_block_collision_shapes() {
let collision_shape = shape.at_pos(pos);
for collision_shape in shapes {
if collision_shape.intersects(bounding_box) {
collided = true;
if !use_collision_shape {
break;
}
using_collision_shape(&collision_shape.to_bounding_box());
// Convert to BB and trigger the callback
on_collision(&collision_shape.to_bounding_box());
}
}
}
collided
} else {
shapes.any(|s| s.intersects(bounding_box))
}
}
// For adjusting movement
@ -1847,7 +1843,7 @@ impl World {
};
let sound = IdOr::<SoundEvent>::Id(Sound::EntityGenericExplode as u16);
for player in self.players.read().await.values() {
if player.position().squared_distance_to_vec(position) > 4096.0 {
if player.position().squared_distance_to_vec(&position) > 4096.0 {
continue;
}
player
@ -2214,7 +2210,7 @@ impl World {
.iter()
.filter_map(|(id, player)| {
let player_pos = player.living_entity.entity.pos.load();
(player_pos.squared_distance_to_vec(pos) <= radius_squared)
(player_pos.squared_distance_to_vec(&pos) <= radius_squared)
.then(|| (*id, player.clone()))
})
.collect()
@ -2233,7 +2229,7 @@ impl World {
.iter()
.filter_map(|(id, entity)| {
let entity_pos = entity.get_entity().pos.load();
(entity_pos.squared_distance_to_vec(pos) <= radius_squared)
(entity_pos.squared_distance_to_vec(&pos) <= radius_squared)
.then(|| (*id, entity.clone()))
})
.collect()
@ -2248,13 +2244,13 @@ impl World {
.entity
.pos
.load()
.squared_distance_to_vec(pos)
.squared_distance_to_vec(&pos)
.partial_cmp(
&b.1.living_entity
.entity
.pos
.load()
.squared_distance_to_vec(pos),
.squared_distance_to_vec(&pos),
)
.unwrap()
})
@ -2301,8 +2297,8 @@ impl World {
a.1.get_entity()
.pos
.load()
.squared_distance_to_vec(pos)
.partial_cmp(&b.1.get_entity().pos.load().squared_distance_to_vec(pos))
.squared_distance_to_vec(&pos)
.partial_cmp(&b.1.get_entity().pos.load().squared_distance_to_vec(&pos))
.unwrap()
})
.map(|p| p.1.clone())
@ -3000,7 +2996,10 @@ impl World {
(block, fluid)
}
pub async fn get_fluid_and_fluid_state(&self, position: &BlockPos) -> (Fluid, FluidState) {
pub async fn get_fluid_and_fluid_state(
&self,
position: &BlockPos,
) -> (&'static Fluid, &'static FluidState) {
let id = self.get_block_state_id(position).await;
let Some(fluid) = Fluid::from_state_id(id) else {
@ -3009,9 +3008,8 @@ impl World {
for (name, value) in properties.to_props() {
if name == "waterlogged" {
if value == "true" {
let fluid = Fluid::FLOWING_WATER;
let state = fluid.states[0].clone();
return (fluid, state);
let state = &Fluid::FLOWING_WATER.states[0];
return (&Fluid::FLOWING_WATER, state);
}
break;
@ -3019,16 +3017,14 @@ impl World {
}
}
let fluid = Fluid::EMPTY;
let state = fluid.states[0].clone();
return (fluid, state);
let state = &Fluid::EMPTY.states[0];
return (&Fluid::EMPTY, state);
};
//let state = fluid.get_state(id);
let state = fluid.states[0].clone();
let state = &fluid.states[0];
(fluid.clone(), state)
(fluid, state)
}
pub async fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId {
@ -3277,15 +3273,13 @@ impl World {
) -> (bool, Option<BlockDirection>) {
let state = self.get_block_state(block_pos).await;
let Some(bounding_boxes) = state.get_block_outline_shapes() else {
return (false, None);
};
let bounding_boxes = state.get_block_outline_shapes();
if bounding_boxes.is_empty() {
if state.outline_shapes.is_empty() {
return (true, None);
}
for shape in &bounding_boxes {
for shape in bounding_boxes {
let world_min = shape.min.add(&block_pos.0.to_f64());
let world_max = shape.max.add(&block_pos.0.to_f64());

View File

@ -474,7 +474,7 @@ pub fn get_nearest_player(pos: &Vector3<f64>, player_positions: &[Vector3<f64>])
let mut min_dst_sq = f64::MAX;
for player_pos in player_positions {
let cur_dst_sq = player_pos.squared_distance_to_vec(*pos);
let cur_dst_sq = player_pos.squared_distance_to_vec(pos);
if cur_dst_sq < min_dst_sq {
min_dst_sq = cur_dst_sq;
}