From dc2206c5fbcaaabde208befacff52907ad847063 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 16 Dec 2024 18:29:14 +1030 Subject: [PATCH 01/13] Block reading somewhat working --- src/bin/src/packet_handlers/login_process.rs | 4 + .../src/data_packing/errors.rs | 9 ++ .../general_purpose/src/data_packing/i16.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/i32.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/i8.rs | 101 ++++++++++++++++++ .../general_purpose/src/data_packing/mod.rs | 7 ++ .../general_purpose/src/data_packing/u16.rs | 93 ++++++++++++++++ .../general_purpose/src/data_packing/u32.rs | 93 ++++++++++++++++ .../general_purpose/src/data_packing/u8.rs | 92 ++++++++++++++++ src/lib/utils/general_purpose/src/lib.rs | 1 + src/lib/world/src/chunk_format.rs | 10 +- src/lib/world/src/edits.rs | 43 ++++++++ src/lib/world/src/errors.rs | 15 ++- src/lib/world/src/lib.rs | 1 + src/lib/world/src/vanilla_chunk_format.rs | 15 ++- 15 files changed, 676 insertions(+), 10 deletions(-) create mode 100644 src/lib/utils/general_purpose/src/data_packing/errors.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i16.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i32.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/i8.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/mod.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u16.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u32.rs create mode 100644 src/lib/utils/general_purpose/src/data_packing/u8.rs create mode 100644 src/lib/world/src/edits.rs diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 0fc450ea..3806fcc5 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -178,6 +178,10 @@ async fn handle_ack_finish_configuration( chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld"))); chunk_recv.calculate_chunks().await; + debug!( + "Block: {}", + state.world.get_block(1, 163, 2, "overworld").await.unwrap() + ); send_keep_alive(conn_id, state, &mut writer).await?; Ok(ack_finish_configuration_event) diff --git a/src/lib/utils/general_purpose/src/data_packing/errors.rs b/src/lib/utils/general_purpose/src/data_packing/errors.rs new file mode 100644 index 00000000..0ea7f02a --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/errors.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DataPackingError { + #[error("Size ({0}) exceeds maximum size of data type: {1}")] + SizeExceedsMaxSize(u8, u8), + #[error("Not enough bits to read with size {0} at offset {1}")] + NotEnoughBits(u8, u32), +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i16.rs b/src/lib/utils/general_purpose/src/data_packing/i16.rs new file mode 100644 index 00000000..d0131f7d --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i16.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 16 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i16)` - The extracted bits as a 16-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i16(data: &i64, size: u8, offset: u32) -> Result { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i16; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i16) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 16-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 16 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i16( + data: &mut i64, + offset: u32, + value: i16, + size: u8, +) -> Result<(), DataPackingError> { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as i64) << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_i16` function with various inputs. + #[test] + fn test_read_nbit_i16() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i16(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i16(&data, 3, 3).unwrap(), -3); // 0b101 as i16 is -3 + assert_eq!(read_nbit_i16(&data, 3, 6).unwrap(), -2); // 0b110 as i16 is -2 + assert_eq!(read_nbit_i16(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_i16` function with various inputs. + #[test] + fn test_write_nbit_i16() { + let mut data: i64 = 0; + write_nbit_i16(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i16(&mut data, 3, -3, 3).unwrap(); // 0b101 as i16 is -3 + assert_eq!(data, 0b101011); + write_nbit_i16(&mut data, 6, -2, 3).unwrap(); // 0b110 as i16 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i16(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} \ No newline at end of file diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs new file mode 100644 index 00000000..18d26a93 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 32 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i32)` - The extracted bits as a 32-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i32(data: &i64, size: u8, offset: u32) -> Result { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i32; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i32) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 32-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 32 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i32( + data: &mut i64, + offset: u32, + value: i32, + size: u8, +) -> Result<(), DataPackingError> { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !(mask << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_i32` function with various inputs. + #[test] + fn test_read_nbit_i32() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i32(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), -3); // 0b101 as i32 is -3 + assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), -2); // 0b110 as i32 is -2 + assert_eq!(read_nbit_i32(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_i32` function with various inputs. + #[test] + fn test_write_nbit_i32() { + let mut data: i64 = 0; + write_nbit_i32(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i32(&mut data, 3, -3, 3).unwrap(); // 0b101 as i32 is -3 + assert_eq!(data, 0b101011); + write_nbit_i32(&mut data, 6, -2, 3).unwrap(); // 0b110 as i32 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i32(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i8.rs b/src/lib/utils/general_purpose/src/data_packing/i8.rs new file mode 100644 index 00000000..f95679a5 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/i8.rs @@ -0,0 +1,101 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit signed integer to read from. +/// * `size` - The number of bits to read (must be 8 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(i8)` - The extracted bits as an 8-bit signed integer. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_i8(data: &i64, size: u8, offset: u32) -> Result { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + let extracted_bits = ((data >> offset) & mask) as i8; + // Sign extend if the extracted bits represent a negative number + let sign_bit = 1 << (size - 1); + if extracted_bits & sign_bit != 0 { + Ok(extracted_bits | !mask as i8) + } else { + Ok(extracted_bits) + } +} + +/// Writes a specified number of bits to a given offset in a 64-bit signed integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit signed integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 8-bit signed integer value to write. +/// * `size` - The number of bits to write (must be 8 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_i8( + data: &mut i64, + offset: u32, + value: i8, + size: u8, +) -> Result<(), DataPackingError> { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as i64) << offset); + *data |= ((value as i64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `write_nbit_i8` function with various inputs. + #[test] + fn test_write_nbit_i8() { + let mut data: i64 = 0; + write_nbit_i8(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_i8(&mut data, 3, -3, 3).unwrap(); // 0b101 as i8 is -3 + assert_eq!(data, 0b101011); + write_nbit_i8(&mut data, 6, -2, 3).unwrap(); // 0b110 as i8 is -2 + assert_eq!(data, 0b110101011); + write_nbit_i8(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } + + /// Tests the `read_nbit_i8` function with various inputs. + #[test] + fn test_read_nbit_i8() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_i8(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_i8(&data, 3, 3).unwrap(), -3); // 0b101 as i8 is -3 + assert_eq!(read_nbit_i8(&data, 3, 6).unwrap(), -2); // 0b110 as i8 is -2 + assert_eq!(read_nbit_i8(&data, 3, 9).unwrap(), 0b000); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/mod.rs b/src/lib/utils/general_purpose/src/data_packing/mod.rs new file mode 100644 index 00000000..4afa9f63 --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/mod.rs @@ -0,0 +1,7 @@ +pub mod errors; +pub mod i16; +pub mod i32; +pub mod i8; +pub mod u16; +pub mod u32; +pub mod u8; diff --git a/src/lib/utils/general_purpose/src/data_packing/u16.rs b/src/lib/utils/general_purpose/src/data_packing/u16.rs new file mode 100644 index 00000000..9bc8c06d --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u16.rs @@ -0,0 +1,93 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 16 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u16)` - The extracted bits as a 16-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u16(data: &u64, size: u8, offset: u32) -> Result { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u16) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 16-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 16 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u16( + data: &mut u64, + offset: u32, + value: u16, + size: u8, +) -> Result<(), DataPackingError> { + if size > 16 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 16)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as u64) << offset); + *data |= ((value as u64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u16` function with various inputs. + #[test] + fn test_read_nbit_u16() { + let data: u64 = 0b110101011; + assert_eq!(read_nbit_u16(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u16(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u16(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u16(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u16` function with various inputs. + #[test] + fn test_write_nbit_u16() { + let mut data: u64 = 0; + write_nbit_u16(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u16(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u16(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u16(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs new file mode 100644 index 00000000..6e498ebf --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -0,0 +1,93 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 32 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u32)` - The extracted bits as a 32-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u32) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 32-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 32 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 32 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u32( + data: &mut u64, + offset: u32, + value: u32, + size: u8, +) -> Result<(), DataPackingError> { + if size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + let mask = (1 << size) - 1; + *data &= !((mask as u64) << offset); + *data |= ((value as u64) & mask) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u32` function with various inputs. + #[test] + fn test_read_nbit_u32() { + let data: u64 = 0b110101011; + assert_eq!(read_nbit_u32(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u32(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u32(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u32(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u32` function with various inputs. + #[test] + fn test_write_nbit_u32() { + let mut data: u64 = 0; + write_nbit_u32(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u32(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u32(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u32(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/data_packing/u8.rs b/src/lib/utils/general_purpose/src/data_packing/u8.rs new file mode 100644 index 00000000..a05b04ae --- /dev/null +++ b/src/lib/utils/general_purpose/src/data_packing/u8.rs @@ -0,0 +1,92 @@ +use crate::data_packing::errors::DataPackingError; + +/// Reads a specified number of bits from a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A reference to the 64-bit unsigned integer to read from. +/// * `size` - The number of bits to read (must be 8 or less). +/// * `offset` - The bit offset from which to start reading. +/// +/// # Returns +/// +/// * `Ok(u8)` - The extracted bits as an 8-bit unsigned integer. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn read_nbit_u8(data: &i64, size: u8, offset: u32) -> Result { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + Ok(((data >> offset) & ((1 << size) - 1)) as u8) +} + +/// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to the 64-bit unsigned integer to write to. +/// * `offset` - The bit offset from which to start writing. +/// * `value` - The 8-bit unsigned integer value to write. +/// * `size` - The number of bits to write (must be 8 or less). +/// +/// # Returns +/// +/// * `Ok(())` - If the bits were successfully written. +/// * `Err(DataPackingError)` - If the size exceeds 8 bits or the offset plus size exceeds 64 bits. +/// +/// # Errors +/// +/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 8. +/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. +pub fn write_nbit_u8( + data: &mut u64, + offset: u32, + value: u8, + size: u8, +) -> Result<(), DataPackingError> { + if size > 8 { + return Err(DataPackingError::SizeExceedsMaxSize(size, 8)); + } + if offset + size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(size, offset)); + } + *data &= !(((1 << size) - 1) << offset); + *data |= (value as u64) << offset; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests the `read_nbit_u8` function with various inputs. + #[test] + fn test_read_nbit_u8() { + let data: i64 = 0b110101011; + assert_eq!(read_nbit_u8(&data, 3, 0).unwrap(), 0b011); + assert_eq!(read_nbit_u8(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_u8(&data, 3, 6).unwrap(), 0b110); + assert_eq!(read_nbit_u8(&data, 3, 9).unwrap(), 0b000); + } + + /// Tests the `write_nbit_u8` function with various inputs. + #[test] + fn test_write_nbit_u8() { + let mut data: u64 = 0; + write_nbit_u8(&mut data, 0, 0b011, 3).unwrap(); + assert_eq!(data, 0b011); + write_nbit_u8(&mut data, 3, 0b101, 3).unwrap(); + assert_eq!(data, 0b101011); + write_nbit_u8(&mut data, 6, 0b110, 3).unwrap(); + assert_eq!(data, 0b110101011); + write_nbit_u8(&mut data, 9, 0b000, 3).unwrap(); + assert_eq!(data, 0b110101011); + } +} diff --git a/src/lib/utils/general_purpose/src/lib.rs b/src/lib/utils/general_purpose/src/lib.rs index 3dc1a972..4ee317f8 100644 --- a/src/lib/utils/general_purpose/src/lib.rs +++ b/src/lib/utils/general_purpose/src/lib.rs @@ -1,3 +1,4 @@ pub mod hashing; pub mod paths; pub mod simd; +pub mod data_packing; diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index ea6210b5..f063dbdb 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -9,7 +9,7 @@ use lazy_static::lazy_static; use std::collections::HashMap; use std::io::Read; use tracing::error; -use vanilla_chunk_format::Palette; +use vanilla_chunk_format::BlockData; #[cfg(test)] const BLOCKSFILE: &[u8] = &[0]; @@ -23,17 +23,17 @@ const BLOCKSFILE: &[u8] = &[0]; const BLOCKSFILE: &[u8] = include_bytes!("../../../../.etc/blockmappings.bz2"); lazy_static! { - static ref ID2BLOCK: HashMap = { + pub static ref ID2BLOCK: HashMap = { let mut bzipreader = bzip2::read::BzDecoder::new(BLOCKSFILE); let mut output = String::new(); bzipreader.read_to_string(&mut output).unwrap(); - let string_keys: HashMap = serde_json::from_str(&output).unwrap(); + let string_keys: HashMap = serde_json::from_str(&output).unwrap(); string_keys .iter() .map(|(k, v)| (k.parse::().unwrap(), v.clone())) .collect() }; - static ref BLOCK2ID: HashMap = + pub static ref BLOCK2ID: HashMap = ID2BLOCK.iter().map(|(k, v)| (v.clone(), *k)).collect(); } @@ -72,7 +72,7 @@ pub struct BlockStates { pub palette: Vec, } -fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { +fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { let mut new_palette = Vec::new(); for palette in vanilla_palettes { if let Some(id) = BLOCK2ID.get(&palette) { diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs new file mode 100644 index 00000000..cdb68570 --- /dev/null +++ b/src/lib/world/src/edits.rs @@ -0,0 +1,43 @@ +use crate::errors::WorldError; +use crate::vanilla_chunk_format::BlockData; +use crate::World; + +impl World { + pub async fn get_block( + &self, + x: i32, + y: i32, + z: i32, + dimension: &str, + ) -> Result { + let chunk_x = x / 16; + let chunk_z = z / 16; + let chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; + let section = chunk + .sections + .iter() + .find(|section| section.y == (y / 16) as i8) + .ok_or(WorldError::SectionOutOfBounds(y / 16))?; + let bits_per_block = section.block_states.bits_per_block as usize; + let data = §ion.block_states.data; + // for some reason the y is off by one block + let index = ((y) % 16) * 256 + (z % 16) * 16 + (x % 16); + let i64_index = (index * bits_per_block as i32) as usize / 64; + let packed_u64 = data.get(i64_index).ok_or(WorldError::ChunkNotFound)?; + let offset = (index as usize * bits_per_block) % 64; + let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + packed_u64, + bits_per_block as u8, + offset as u32, + )?; + let palette_id = section + .block_states + .palette + .get(id as usize) + .ok_or(WorldError::ChunkNotFound)?; + Ok(crate::chunk_format::ID2BLOCK + .get(&palette_id.val) + .unwrap_or(&BlockData::default()) + .clone()) + } +} diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index 9119e249..85b9b183 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -1,10 +1,11 @@ use crate::errors::WorldError::{GenericIOError, PermissionError}; -use crate::vanilla_chunk_format::Palette; +use crate::vanilla_chunk_format::BlockData; use errors::AnvilError; use ferrumc_anvil::errors; use ferrumc_storage::errors::StorageError; use std::io::ErrorKind; use thiserror::Error; +use ferrumc_general_purpose::data_packing::errors::DataPackingError; #[derive(Debug, Error)] pub enum WorldError { @@ -35,9 +36,13 @@ pub enum WorldError { #[error("Anvil Decode Error: {0}")] AnvilDecodeError(AnvilError), #[error("Missing block mapping: {0}")] - MissingBlockMapping(Palette), + MissingBlockMapping(BlockData), #[error("Invalid memory map size: {0}")] InvalidMapSize(u64), + #[error("Section out of bounds: {0}")] + SectionOutOfBounds(i32), + #[error("Invalid block state data: {0}")] + InvalidBlockStateData(#[source] DataPackingError), } impl From for WorldError { @@ -61,3 +66,9 @@ impl From for WorldError { WorldError::AnvilDecodeError(err) } } + +impl From for WorldError { + fn from(err: DataPackingError) -> Self { + WorldError::InvalidBlockStateData(err) + } +} diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index d4736b95..b371ba8b 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -5,6 +5,7 @@ mod db_functions; pub mod errors; mod importing; mod vanilla_chunk_format; +pub mod edits; use crate::chunk_format::Chunk; use crate::errors::WorldError; diff --git a/src/lib/world/src/vanilla_chunk_format.rs b/src/lib/world/src/vanilla_chunk_format.rs index 5cdfb149..4f25a758 100644 --- a/src/lib/world/src/vanilla_chunk_format.rs +++ b/src/lib/world/src/vanilla_chunk_format.rs @@ -95,24 +95,33 @@ pub(crate) struct Section { #[derive(deepsize::DeepSizeOf)] pub(crate) struct BlockStates { pub data: Option>, - pub palette: Option>, + pub palette: Option>, } #[apply(ChunkDerives)] #[derive(deepsize::DeepSizeOf, Hash)] -pub struct Palette { +pub struct BlockData { #[nbt(rename = "Name")] pub name: String, #[nbt(rename = "Properties")] pub properties: Option>, } -impl Display for Palette { +impl Display for BlockData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } +impl Default for BlockData { + fn default() -> Self { + BlockData { + name: String::from("minecraft:air"), + properties: None, + } + } +} + #[apply(ChunkDerives)] #[derive(deepsize::DeepSizeOf)] pub(crate) struct Properties { From 2c8841058d5a877b343674c68e94e2483747613e Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 17 Dec 2024 14:48:21 +1030 Subject: [PATCH 02/13] now works more reliably --- src/bin/src/packet_handlers/login_process.rs | 5 ---- .../transform/update_player_position.rs | 15 ++++++++++- .../general_purpose/src/data_packing/i16.rs | 4 +-- .../general_purpose/src/data_packing/i8.rs | 2 +- .../general_purpose/src/data_packing/u16.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 4 +-- src/lib/utils/general_purpose/src/lib.rs | 2 +- src/lib/world/src/edits.rs | 27 ++++++++++++------- src/lib/world/src/errors.rs | 10 +++---- src/lib/world/src/lib.rs | 2 +- 10 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 3806fcc5..748a4b33 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -177,11 +177,6 @@ async fn handle_ack_finish_configuration( let mut chunk_recv = state.universe.get_mut::(conn_id)?; chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld"))); chunk_recv.calculate_chunks().await; - - debug!( - "Block: {}", - state.world.get_block(1, 163, 2, "overworld").await.unwrap() - ); send_keep_alive(conn_id, state, &mut writer).await?; Ok(ack_finish_configuration_event) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index 6487e68f..f5cccefe 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -7,7 +7,7 @@ use ferrumc_net::errors::NetError; use ferrumc_net::packets::packet_events::TransformEvent; use ferrumc_net::utils::ecs_helpers::EntityExt; use ferrumc_state::GlobalState; -use tracing::trace; +use tracing::{debug, trace}; #[event_handler] async fn handle_player_move( @@ -16,6 +16,19 @@ async fn handle_player_move( ) -> Result { let conn_id = event.conn_id; if let Some(ref new_position) = event.position { + debug!( + "Block: {}", + state + .world + .get_block( + new_position.x as i32, + new_position.y as i32 - 1, + new_position.z as i32, + "overworld" + ) + .await + .unwrap() + ); trace!("Getting chunk_recv 1 for player move"); let mut chunk_recv = state.universe.get_mut::(conn_id)?; trace!("Got chunk_recv 1 for player move"); diff --git a/src/lib/utils/general_purpose/src/data_packing/i16.rs b/src/lib/utils/general_purpose/src/data_packing/i16.rs index d0131f7d..e63e62c0 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i16.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i16.rs @@ -66,7 +66,7 @@ pub fn write_nbit_i16( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as i64) << offset); + *data &= !(mask << offset); *data |= ((value as i64) & mask) << offset; Ok(()) } @@ -98,4 +98,4 @@ mod tests { write_nbit_i16(&mut data, 9, 0b000, 3).unwrap(); assert_eq!(data, 0b110101011); } -} \ No newline at end of file +} diff --git a/src/lib/utils/general_purpose/src/data_packing/i8.rs b/src/lib/utils/general_purpose/src/data_packing/i8.rs index f95679a5..310d72e5 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i8.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i8.rs @@ -66,7 +66,7 @@ pub fn write_nbit_i8( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as i64) << offset); + *data &= !((mask) << offset); *data |= ((value as i64) & mask) << offset; Ok(()) } diff --git a/src/lib/utils/general_purpose/src/data_packing/u16.rs b/src/lib/utils/general_purpose/src/data_packing/u16.rs index 9bc8c06d..e697bc8b 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u16.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u16.rs @@ -58,7 +58,7 @@ pub fn write_nbit_u16( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as u64) << offset); + *data &= !((mask) << offset); *data |= ((value as u64) & mask) << offset; Ok(()) } diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 6e498ebf..cc9d16cb 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -58,7 +58,7 @@ pub fn write_nbit_u32( return Err(DataPackingError::NotEnoughBits(size, offset)); } let mask = (1 << size) - 1; - *data &= !((mask as u64) << offset); + *data &= !((mask) << offset); *data |= ((value as u64) & mask) << offset; Ok(()) } @@ -70,7 +70,7 @@ mod tests { /// Tests the `read_nbit_u32` function with various inputs. #[test] fn test_read_nbit_u32() { - let data: u64 = 0b110101011; + let data: i64 = 0b110101011; assert_eq!(read_nbit_u32(&data, 3, 0).unwrap(), 0b011); assert_eq!(read_nbit_u32(&data, 3, 3).unwrap(), 0b101); assert_eq!(read_nbit_u32(&data, 3, 6).unwrap(), 0b110); diff --git a/src/lib/utils/general_purpose/src/lib.rs b/src/lib/utils/general_purpose/src/lib.rs index 4ee317f8..d6eb177f 100644 --- a/src/lib/utils/general_purpose/src/lib.rs +++ b/src/lib/utils/general_purpose/src/lib.rs @@ -1,4 +1,4 @@ +pub mod data_packing; pub mod hashing; pub mod paths; pub mod simd; -pub mod data_packing; diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index cdb68570..e19a7113 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -1,3 +1,4 @@ +use crate::chunk_format::ID2BLOCK; use crate::errors::WorldError; use crate::vanilla_chunk_format::BlockData; use crate::World; @@ -10,21 +11,29 @@ impl World { z: i32, dimension: &str, ) -> Result { - let chunk_x = x / 16; - let chunk_z = z / 16; + let chunk_x = x >> 4; + let chunk_z = z >> 4; let chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; let section = chunk .sections .iter() - .find(|section| section.y == (y / 16) as i8) - .ok_or(WorldError::SectionOutOfBounds(y / 16))?; + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + if section.block_states.palette.len() == 1 { + return ID2BLOCK + .get(§ion.block_states.palette[0].val) + .cloned() + .ok_or(WorldError::ChunkNotFound); + } let bits_per_block = section.block_states.bits_per_block as usize; let data = §ion.block_states.data; - // for some reason the y is off by one block - let index = ((y) % 16) * 256 + (z % 16) * 16 + (x % 16); - let i64_index = (index * bits_per_block as i32) as usize / 64; - let packed_u64 = data.get(i64_index).ok_or(WorldError::ChunkNotFound)?; - let offset = (index as usize * bits_per_block) % 64; + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = data + .get(i64_index) + .ok_or(WorldError::InvalidBlockStateData())?; + let offset = (index % blocks_per_i64) * bits_per_block; let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( packed_u64, bits_per_block as u8, diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index 85b9b183..8f768611 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -2,10 +2,10 @@ use crate::errors::WorldError::{GenericIOError, PermissionError}; use crate::vanilla_chunk_format::BlockData; use errors::AnvilError; use ferrumc_anvil::errors; +use ferrumc_general_purpose::data_packing::errors::DataPackingError; use ferrumc_storage::errors::StorageError; use std::io::ErrorKind; use thiserror::Error; -use ferrumc_general_purpose::data_packing::errors::DataPackingError; #[derive(Debug, Error)] pub enum WorldError { @@ -41,8 +41,8 @@ pub enum WorldError { InvalidMapSize(u64), #[error("Section out of bounds: {0}")] SectionOutOfBounds(i32), - #[error("Invalid block state data: {0}")] - InvalidBlockStateData(#[source] DataPackingError), + #[error("Invalid block state data")] + InvalidBlockStateData(), } impl From for WorldError { @@ -68,7 +68,7 @@ impl From for WorldError { } impl From for WorldError { - fn from(err: DataPackingError) -> Self { - WorldError::InvalidBlockStateData(err) + fn from(_: DataPackingError) -> Self { + WorldError::InvalidBlockStateData() } } diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index b371ba8b..52a00b03 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -2,10 +2,10 @@ pub mod chunk_format; mod db_functions; +pub mod edits; pub mod errors; mod importing; mod vanilla_chunk_format; -pub mod edits; use crate::chunk_format::Chunk; use crate::errors::WorldError; From 5efed7e659ab86ea6b25200296033761659ff0ba Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 17 Dec 2024 16:01:42 +1030 Subject: [PATCH 03/13] Block reading fixed --- .../transform/update_player_position.rs | 6 +++--- .../general_purpose/src/data_packing/u32.rs | 2 +- src/lib/world/src/chunk_format.rs | 3 ++- src/lib/world/src/edits.rs | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index f5cccefe..01d7f976 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -21,9 +21,9 @@ async fn handle_player_move( state .world .get_block( - new_position.x as i32, - new_position.y as i32 - 1, - new_position.z as i32, + new_position.x.floor() as i32, + new_position.y.floor() as i32 - 1, + new_position.z.floor() as i32, "overworld" ) .await diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index cc9d16cb..91e2ed48 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -24,7 +24,7 @@ pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result 64 { return Err(DataPackingError::NotEnoughBits(size, offset)); } - Ok(((data >> offset) & ((1 << size) - 1)) as u32) + Ok(((*data as u64 >> offset as u64) & ((1u64 << size) - 1u64)) as u32) } /// Writes a specified number of bits to a given offset in a 64-bit unsigned integer. diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index f063dbdb..6c0e9f6d 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -6,6 +6,7 @@ use deepsize::DeepSizeOf; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; +use std::cmp::max; use std::collections::HashMap; use std::io::Read; use tracing::error; @@ -126,7 +127,7 @@ impl VanillaChunk { .map_or(vec![], |biome_data| biome_data.palette.clone()); let non_air_blocks = palette.iter().filter(|id| id.name != "air").count() as u16; let block_states = BlockStates { - bits_per_block: (palette.len() as f32).log2().ceil() as u8, + bits_per_block: max((palette.len() as f32).log2().ceil() as u8, 4), non_air_blocks, data: block_data, palette: convert_to_net_palette(palette)?, diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index e19a7113..5bdd0303 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -4,6 +4,25 @@ use crate::vanilla_chunk_format::BlockData; use crate::World; impl World { + /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. + /// + /// # Arguments + /// + /// * `x` - The x-coordinate of the block. + /// * `y` - The y-coordinate of the block. + /// * `z` - The z-coordinate of the block. + /// * `dimension` - The dimension in which the block is located. + /// + /// # Returns + /// + /// * `Ok(BlockData)` - The block data at the specified coordinates. + /// * `Err(WorldError)` - If an error occurs while retrieving the block data. + /// + /// # Errors + /// + /// * `WorldError::SectionOutOfBounds` - If the section containing the block is out of bounds. + /// * `WorldError::ChunkNotFound` - If the chunk or block data is not found. + /// * `WorldError::InvalidBlockStateData` - If the block state data is invalid. pub async fn get_block( &self, x: i32, From af982e246c8153a619efa44ec858496cf8efeac1 Mon Sep 17 00:00:00 2001 From: ReCore Date: Fri, 20 Dec 2024 15:44:55 +1030 Subject: [PATCH 04/13] Block count tracking + dependency cleanup --- Cargo.toml | 8 +++----- src/lib/storage/src/lmdb.rs | 1 - src/lib/world/Cargo.toml | 4 ---- src/lib/world/src/chunk_format.rs | 32 +++++++++++++++++++++++++++++-- src/lib/world/src/importing.rs | 9 ++++++--- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a532388..c5dbb764 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,6 @@ hashbrown = "0.15.0" tinyvec = "1.8.0" dashmap = "6.1.0" uuid = { version = "1.1", features = ["v4", "v3", "serde"] } -whirlwind = "0.1.1" # Macros lazy_static = "1.5.0" @@ -168,12 +167,12 @@ libflate = "2.1.0" flate2 = { version = "1.0.33", features = ["zlib"], default-features = false } zstd = { version = "0.13.2" } brotli = "7.0.0" -lzzzz = "1.1.0" +lzzzz = "2.0.0" yazi = "0.2.0" -bzip2 = "0.4.1" +bzip2 = "0.5.0" # Database -heed = "0.20.5" +heed = "0.21.0" moka = "0.12.8" # CLI @@ -186,7 +185,6 @@ deepsize = "0.2.0" page_size = "0.6.0" # I/O -tempfile = "3.12.0" memmap2 = "0.9.5" # Benchmarking diff --git a/src/lib/storage/src/lmdb.rs b/src/lib/storage/src/lmdb.rs index bc9c5960..7988a586 100644 --- a/src/lib/storage/src/lmdb.rs +++ b/src/lib/storage/src/lmdb.rs @@ -18,7 +18,6 @@ impl From for StorageError { Error::Io(e) => StorageError::GenericIoError(e), Error::Encoding(e) => StorageError::WriteError(e.to_string()), Error::Decoding(e) => StorageError::ReadError(e.to_string()), - Error::DatabaseClosing => StorageError::CloseError("Database closing".to_string()), _ => StorageError::DatabaseError(err.to_string()), } } diff --git a/src/lib/world/Cargo.toml b/src/lib/world/Cargo.toml index 2ccc3a3f..03b0089e 100644 --- a/src/lib/world/Cargo.toml +++ b/src/lib/world/Cargo.toml @@ -6,9 +6,6 @@ edition = "2021" [dependencies] thiserror = { workspace = true } - -ferrumc-logging = { workspace = true } -ferrumc-profiling = { workspace = true } ferrumc-storage = { workspace = true } ferrumc-config = { workspace = true } tracing = { workspace = true } @@ -31,4 +28,3 @@ serde_json = { workspace = true } indicatif = { workspace = true } wyhash = { workspace = true } moka = { workspace = true, features = ["future"] } -log = "0.4.22" diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 6c0e9f6d..21a8d563 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -71,6 +71,7 @@ pub struct BlockStates { pub non_air_blocks: u16, pub data: Vec, pub palette: Vec, + pub block_counts: HashMap, } fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { @@ -125,9 +126,36 @@ impl VanillaChunk { .biomes .as_ref() .map_or(vec![], |biome_data| biome_data.palette.clone()); - let non_air_blocks = palette.iter().filter(|id| id.name != "air").count() as u16; + let bits_per_block = max((palette.len() as f32).log2().ceil() as u8, 4); + let mut block_counts = HashMap::new(); + for chunk in &block_data { + let mut i = 0; + while i + bits_per_block < 64 { + let id = ferrumc_general_purpose::data_packing::i32::read_nbit_i32( + chunk, + bits_per_block, + i as u32, + )?; + *block_counts.entry(id).or_insert(0) += 1; + i += bits_per_block; + } + } + if block_data.is_empty() { + let single_block = if let Some(block) = palette.first() { + if let Some(id) = BLOCK2ID.get(block) { + *id + } else { + 0 + } + } else { + 0 + }; + block_counts.insert(single_block, 4096); + } + let non_air_blocks = 4096 - *block_counts.get(&0).unwrap_or(&0) as u16; let block_states = BlockStates { - bits_per_block: max((palette.len() as f32).log2().ceil() as u8, 4), + bits_per_block, + block_counts, non_air_blocks, data: block_data, palette: convert_to_net_palette(palette)?, diff --git a/src/lib/world/src/importing.rs b/src/lib/world/src/importing.rs index d2ba641a..2869b108 100644 --- a/src/lib/world/src/importing.rs +++ b/src/lib/world/src/importing.rs @@ -123,15 +123,18 @@ impl World { let cloned_progress_bar = progress_bar.clone(); let self_clone = self.clone(); task_set.spawn(async move { - if let Ok(chunk) = vanilla_chunk.to_custom_format() { + match vanilla_chunk.to_custom_format() { + Ok(chunk) => { if let Err(e) = save_chunk_internal(&self_clone, chunk).await { error!("Could not save chunk: {}", e); } else { cloned_progress_bar.inc(1); } - } else { - error!("Could not convert chunk to custom format: {:?}", chunk); } + Err(e) => { + error!("Could not convert chunk to custom format: {}", e); + } + } }); } Err(e) => { From 26f30d55d36b727262d8bfe61b9a287116d75542 Mon Sep 17 00:00:00 2001 From: ReCore Date: Fri, 20 Dec 2024 16:16:33 +1030 Subject: [PATCH 05/13] Added packet generator script --- scripts/new_packet.py | 64 +++++++++++++++++++ src/lib/net/src/packets/incoming/mod.rs | 2 + .../net/src/packets/incoming/place_block.rs | 17 +++++ 3 files changed, 83 insertions(+) create mode 100644 scripts/new_packet.py create mode 100644 src/lib/net/src/packets/incoming/place_block.rs diff --git a/scripts/new_packet.py b/scripts/new_packet.py new file mode 100644 index 00000000..0f493608 --- /dev/null +++ b/scripts/new_packet.py @@ -0,0 +1,64 @@ +import os.path + +incoming_template = """ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = ++id++, state = "play")] +pub struct ++name++ { +} + +impl IncomingPacket for ++name++ { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + todo!() + } +} +""" + +outgoing_template = """ +use ferrumc_macros::{packet, NetEncode};\ +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = ++id++)] +pub struct ++name++ {} +""" + + +def to_snake_case(string) -> str: + return string.lower().replace(" ", "_") + + +def to_camel_case(string) -> str: + return string.title().replace(" ", "") + + +packet_type_input = input("Incoming or outgoing packet? (i/o): ") +packet_type = "" +if packet_type_input == "i": + packet_type = "incoming" +elif packet_type_input == "o": + packet_type = "outgoing" +else: + print("Invalid input") + exit() + +packet_name = input("Packet name: ") +packets_dir = os.path.join(os.path.join(os.path.dirname(__file__), ".."), "src/lib/net/src/packets") + +packet_id = input("Packet ID (formatted like 0x01): ") +packet_id = packet_id[:-2] + packet_id[-2:].upper() + +with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f: + if packet_type == "incoming": + f.write(incoming_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id)) + with open(f"{packets_dir}/incoming/mod.rs", "a") as modfile: + modfile.write(f"\npub mod {to_snake_case(packet_name)};") + else: + f.write(outgoing_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id)) + with open(f"{packets_dir}/outgoing/mod.rs", "a") as modfile: + modfile.write(f"\npub mod {to_snake_case(packet_name)};") diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 2f5254a1..a857e372 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -14,3 +14,5 @@ pub mod packet_skeleton; pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; + +pub mod place_block; \ No newline at end of file diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs new file mode 100644 index 00000000..fae22682 --- /dev/null +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -0,0 +1,17 @@ + +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = 0x3C, state = "play")] +pub struct PlaceBlock { +} + +impl IncomingPacket for PlaceBlock { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + todo!() + } +} From 8ad1965aa64ab0522646d352acbd16083110107c Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 16:56:43 +1030 Subject: [PATCH 06/13] Now correctly resizes sections --- src/bin/Cargo.toml | 1 + src/bin/src/main.rs | 1 + .../transform/update_player_position.rs | 13 --- src/bin/src/systems/chunk_sender.rs | 32 ++++-- src/lib/core/src/chunks/chunk_receiver.rs | 2 + .../codec/src/net_types/network_position.rs | 22 +++- .../src/packets/incoming/chunks_per_tick.rs | 20 ++++ src/lib/net/src/packets/incoming/mod.rs | 1 + .../net/src/packets/incoming/place_block.rs | 19 +++- src/lib/net/src/utils/broadcast.rs | 3 +- .../general_purpose/src/data_packing/i32.rs | 34 +++--- src/lib/world/src/chunk_format.rs | 101 ++++++++++++++++-- src/lib/world/src/edits.rs | 5 +- src/lib/world/src/errors.rs | 6 +- 14 files changed, 207 insertions(+), 53 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/chunks_per_tick.rs diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 0c0aaaea..71dce6c1 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -36,6 +36,7 @@ futures = { workspace = true } serde_json = { workspace = true } async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } +rand = "0.9.0-beta.1" [[bin]] diff --git a/src/bin/src/main.rs b/src/bin/src/main.rs index aca67b80..d8436910 100644 --- a/src/bin/src/main.rs +++ b/src/bin/src/main.rs @@ -1,5 +1,6 @@ #![feature(portable_simd)] #![forbid(unsafe_code)] +#![feature(random)] extern crate core; use crate::errors::BinaryError; diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index 7debcade..cb937152 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -16,19 +16,6 @@ async fn handle_player_move( ) -> Result { let conn_id = event.conn_id; if let Some(ref new_position) = event.position { - debug!( - "Block: {}", - state - .world - .get_block( - new_position.x.floor() as i32, - new_position.y.floor() as i32 - 1, - new_position.z.floor() as i32, - "overworld" - ) - .await - .unwrap() - ); trace!("Getting chunk_recv 1 for player move"); { let mut chunk_recv = state.universe.get_mut::(conn_id)?; diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 6b359bb3..ced644ab 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -10,11 +10,12 @@ use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::GlobalState; +use rand::random; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, @@ -67,20 +68,26 @@ impl System for ChunkSenderSystem { centre_coords = (chunk.0, chunk.1); } } - let mut sent_chunks = 0; { - let Ok(chunk_recv) = state.universe.get::(eid) else { - trace!("A player disconnected before we could get the ChunkReceiver"); - return Ok(()); - }; - for possible_chunk in chunk_recv.needed_chunks.iter_mut() { - if let Some(chunk) = possible_chunk.pair().1 { - let key = possible_chunk.pair().0; + trace!("Getting chunk_recv 3 for sender"); + let chunk_recv = state + .universe + .get_mut::(eid) + .expect("ChunkReceiver not found"); + trace!("Got chunk_recv 3 for sender"); + for mut possible_chunk in chunk_recv.needed_chunks.iter_mut() { + if let (key, Some(chunk)) = possible_chunk.pair_mut() { + chunk.sections.iter_mut().for_each(|section| { + // if random::() < 25 { + if let Err(e) = section.block_states.resize(8) { + error!("Error resizing block states: {:?}", e); + } + // } + }); to_drop.push(key.clone()); match ChunkAndLightData::from_chunk(&chunk.clone()) { Ok(packet) => { packets.push(packet); - sent_chunks += 1; } Err(e) => { error!("Error sending chunk: {:?}", e); @@ -125,17 +132,20 @@ impl System for ChunkSenderSystem { { error!("Error sending chunk: {:?}", e); } + let mut count = 0; for packet in packets { if let Err(e) = conn.send_packet(&packet, &NetEncodeOpts::WithLength).await { error!("Error sending chunk: {:?}", e); + } else { + count += 1; } } if let Err(e) = conn .send_packet( &ChunkBatchFinish { - batch_size: VarInt::new(sent_chunks), + batch_size: VarInt::new(count), }, &NetEncodeOpts::WithLength, ) diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index dcae60c0..16ebaaf5 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -8,6 +8,7 @@ pub struct ChunkReceiver { pub can_see: DashSet<(i32, i32, String)>, pub last_update: Instant, pub last_chunk: Option<(i32, i32, String)>, + pub chunks_per_tick: f32, } impl Default for ChunkReceiver { @@ -23,6 +24,7 @@ impl ChunkReceiver { can_see: DashSet::new(), last_update: Instant::now(), last_chunk: None, + chunks_per_tick: 0.0, } } } diff --git a/src/lib/net/crates/codec/src/net_types/network_position.rs b/src/lib/net/crates/codec/src/net_types/network_position.rs index 4a745b0a..cc428ab3 100644 --- a/src/lib/net/crates/codec/src/net_types/network_position.rs +++ b/src/lib/net/crates/codec/src/net_types/network_position.rs @@ -1,8 +1,9 @@ // I have no clue why it is saving i32 and i16. There is no precision. The actual player position is saved in f32. +use crate::decode::{NetDecode, NetDecodeOpts, NetDecodeResult}; use crate::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; use std::fmt::Display; -use std::io::Write; +use std::io::{Read, Write}; use tokio::io::AsyncWrite; /// The definition of a "Position" in the Minecraft protocol. @@ -47,10 +48,29 @@ impl NetEncode for NetworkPosition { Ok(()) } } + +impl NetDecode for NetworkPosition { + fn decode(reader: &mut R, _: &NetDecodeOpts) -> NetDecodeResult { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(NetworkPosition::from_u64(u64::from_be_bytes(buf))) + } +} + impl NetworkPosition { pub fn as_u64(&self) -> u64 { ((self.x as u64 & 0x3FFFFFF) << 38) | ((self.z as u64 & 0x3FFFFFF) << 12) | (self.y as u64 & 0xFFF) } + + pub fn from_u64(val: u64) -> Self { + let mut x = (val >> 38) as i32; + let mut y = (val << 52 >> 52) as i16; + let mut z = (val << 26 >> 38) as i32; + if x >= 1 << 25 { x -= 1 << 26 } + if y >= 1 << 11 { y -= 1 << 12 } + if z >= 1 << 25 { z -= 1 << 26 } + Self { x, y, z } + } } diff --git a/src/lib/net/src/packets/incoming/chunks_per_tick.rs b/src/lib/net/src/packets/incoming/chunks_per_tick.rs new file mode 100644 index 00000000..a0f1f759 --- /dev/null +++ b/src/lib/net/src/packets/incoming/chunks_per_tick.rs @@ -0,0 +1,20 @@ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = 0x08, state = "play")] +pub struct ChunksPerTick { + chunks_per_tick: f32, +} + +impl IncomingPacket for ChunksPerTick { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + let mut chunk_recv = state.universe.get_mut::(conn_id)?; + chunk_recv.chunks_per_tick = self.chunks_per_tick; + Ok(()) + } +} diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 94e04a8f..bfc70f9b 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -15,5 +15,6 @@ pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; +pub mod chunks_per_tick; pub mod place_block; pub mod swing_arm; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index fae22682..0b891a29 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,17 +1,28 @@ - use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::net_types::network_position::NetworkPosition; +use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; use std::sync::Arc; +use tracing::debug; -#[derive(NetDecode)] -#[packet(packet_id = 0x3C, state = "play")] +#[derive(NetDecode, Debug)] +#[packet(packet_id = 0x38, state = "play")] pub struct PlaceBlock { + pub hand: VarInt, + pub position: NetworkPosition, + pub face: VarInt, + pub cursor_x: f32, + pub cursor_y: f32, + pub cursor_z: f32, + pub inside_block: bool, + pub sequence: VarInt, } impl IncomingPacket for PlaceBlock { async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { - todo!() + debug!("{:?}", self); + Ok(()) } } diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs index c67b068d..b2fa6468 100644 --- a/src/lib/net/src/utils/broadcast.rs +++ b/src/lib/net/src/utils/broadcast.rs @@ -2,6 +2,7 @@ use crate::connection::StreamWriter; use crate::NetResult; use async_trait::async_trait; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_ecs::entities::Entity; use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts}; use ferrumc_state::GlobalState; @@ -70,7 +71,7 @@ fn get_all_entities(state: &GlobalState) -> HashSet { state .universe .get_component_manager() - .get_entities_with::() + .get_entities_with::() .into_iter() .collect() } diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index 18d26a93..d79b10f7 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -17,22 +17,30 @@ use crate::data_packing::errors::DataPackingError; /// /// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. /// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. -pub fn read_nbit_i32(data: &i64, size: u8, offset: u32) -> Result { - if size > 32 { - return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); +/// Reads an n-bit integer from a packed `i64`. +pub fn read_nbit_i32( + word: &i64, + bit_size: usize, + bit_offset: u32, +) -> Result { + if bit_size == 0 || bit_size > 32 { + return Err(DataPackingError::SizeExceedsMaxSize(bit_size as u8, 32)); } - if offset + size as u32 > 64 { - return Err(DataPackingError::NotEnoughBits(size, offset)); + if bit_offset >= 64 { + return Err(DataPackingError::SizeExceedsMaxSize(bit_size as u8, 32)); } - let mask = (1 << size) - 1; - let extracted_bits = ((data >> offset) & mask) as i32; - // Sign extend if the extracted bits represent a negative number - let sign_bit = 1 << (size - 1); - if extracted_bits & sign_bit != 0 { - Ok(extracted_bits | !mask as i32) - } else { - Ok(extracted_bits) + if bit_offset + bit_size as u32 > 64 { + return Err(DataPackingError::NotEnoughBits(bit_size as u8, bit_offset)); } + + // Create a mask for the n-bit value + let mask = (1u64 << bit_size) - 1; + + // Extract the value from the word + let value = ((*word as u64) >> bit_offset) & mask; + + // Cast to i32 and return + Ok(value as i32) } /// Writes a specified number of bits to a given offset in a 64-bit signed integer. diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 612a6fe7..6d09bb23 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -3,13 +3,14 @@ use crate::vanilla_chunk_format; use crate::vanilla_chunk_format::VanillaChunk; use bitcode_derive::{Decode, Encode}; use deepsize::DeepSizeOf; +use ferrumc_general_purpose::data_packing::i32::{read_nbit_i32, write_nbit_i32}; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; use std::cmp::max; use std::collections::HashMap; use std::io::Read; -use tracing::error; +use tracing::{debug, error}; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -131,11 +132,7 @@ impl VanillaChunk { for chunk in &block_data { let mut i = 0; while i + bits_per_block < 64 { - let id = ferrumc_general_purpose::data_packing::i32::read_nbit_i32( - chunk, - bits_per_block, - i as u32, - )?; + let id = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; *block_counts.entry(id).or_insert(0) += 1; i += bits_per_block; } @@ -206,3 +203,95 @@ impl VanillaChunk { }) } } + +impl BlockStates { + pub fn resize(&mut self, new_bit_size: usize) -> Result<(), WorldError> { + let max_int_value = (1 << new_bit_size) - 1; + + if self.data.is_empty() { + let data_size = (4096 * new_bit_size + 63) / 64; + self.data = vec![0; data_size]; + self.bits_per_block = new_bit_size as u8; + return Ok(()); + } + + // Step 1: Read existing packed data into a list of normal integers + let mut normalised_ints = Vec::with_capacity(4096); + let mut values_read = 0; + + for &long in &self.data { + let mut bit_offset = 0; + + while bit_offset + self.bits_per_block as usize <= 64 { + if values_read >= 4096 { + break; + } + + // Extract value at the current bit offset + let value = read_nbit_i32(&long, self.bits_per_block as usize, bit_offset as u32)?; + if value > max_int_value { + return Err(WorldError::InvalidBlockStateData(format!( + "Value {} exceeds maximum value for {}-bit block state", + value, new_bit_size + ))); + } + normalised_ints.push(value); + values_read += 1; + + bit_offset += self.bits_per_block as usize; + } + + // Stop reading if we’ve already hit 4096 values + if values_read >= 4096 { + break; + } + } + + // Check if we read exactly 4096 block states + if normalised_ints.len() != 4096 { + return Err(WorldError::InvalidBlockStateData(format!( + "Expected 4096 block states, but got {}", + normalised_ints.len() + ))); + } + + // Step 2: Write the normalised integers into the new packed format + let mut new_data = Vec::new(); + let mut current_long: i64 = 0; + let mut bit_position = 0; + + for &value in &normalised_ints { + current_long |= (value as i64) << bit_position; + bit_position += new_bit_size; + + if bit_position >= 64 { + new_data.push(current_long); + current_long = (value as i64) >> (new_bit_size - (bit_position - 64)); + bit_position -= 64; + } + } + + // Push any remaining bits in the final long + if bit_position > 0 { + new_data.push(current_long); + } + + // Verify the size of the new data matches expectations + let expected_size = (4096 * new_bit_size + 63) / 64; + if new_data.len() != expected_size { + return Err(WorldError::InvalidBlockStateData(format!( + "Expected packed data size of {}, but got {}", + expected_size, + new_data.len() + ))); + } + + // Update the chunk with the new packed data and bit size + self.data = new_data; + self.bits_per_block = new_bit_size as u8; + + // debug!("Resize complete. New data: {:?}", self.data); + + Ok(()) + } +} diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 5bdd0303..2af46ea7 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -51,7 +51,10 @@ impl World { let i64_index = index / blocks_per_i64; let packed_u64 = data .get(i64_index) - .ok_or(WorldError::InvalidBlockStateData())?; + .ok_or(WorldError::InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; let offset = (index % blocks_per_i64) * bits_per_block; let id = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( packed_u64, diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index ad1a1380..b4a18eda 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -44,7 +44,7 @@ pub enum WorldError { #[error("Section out of bounds: {0}")] SectionOutOfBounds(i32), #[error("Invalid block state data")] - InvalidBlockStateData(), + InvalidBlockStateData(String), } // implemente AcquireError for WorldError @@ -78,7 +78,7 @@ impl From for WorldError { } impl From for WorldError { - fn from(_: DataPackingError) -> Self { - WorldError::InvalidBlockStateData() + fn from(e: DataPackingError) -> Self { + WorldError::InvalidBlockStateData(e.to_string()) } } From 7acc227db743893be7a12e31aa2c473699efe0a5 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 17:06:49 +1030 Subject: [PATCH 07/13] Clippy + fmt --- .../transform/update_player_position.rs | 2 +- src/bin/src/systems/chunk_sender.rs | 3 +-- .../crates/codec/src/net_types/network_position.rs | 12 +++++++++--- src/lib/net/src/packets/incoming/place_block.rs | 2 +- src/lib/net/src/utils/broadcast.rs | 1 - .../utils/general_purpose/src/data_packing/i32.rs | 2 +- src/lib/world/src/chunk_format.rs | 7 ++----- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/bin/src/packet_handlers/transform/update_player_position.rs b/src/bin/src/packet_handlers/transform/update_player_position.rs index cb937152..e262e267 100644 --- a/src/bin/src/packet_handlers/transform/update_player_position.rs +++ b/src/bin/src/packet_handlers/transform/update_player_position.rs @@ -7,7 +7,7 @@ use ferrumc_net::errors::NetError; use ferrumc_net::packets::packet_events::TransformEvent; use ferrumc_net::utils::ecs_helpers::EntityExt; use ferrumc_state::GlobalState; -use tracing::{debug, trace}; +use tracing::trace; #[event_handler] async fn handle_player_move( diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index ced644ab..be723b03 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -10,12 +10,11 @@ use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::GlobalState; -use rand::random; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinSet; -use tracing::{debug, error, info, trace}; +use tracing::{error, info, trace}; pub(super) struct ChunkSenderSystem { pub stop: AtomicBool, diff --git a/src/lib/net/crates/codec/src/net_types/network_position.rs b/src/lib/net/crates/codec/src/net_types/network_position.rs index cc428ab3..fc787a79 100644 --- a/src/lib/net/crates/codec/src/net_types/network_position.rs +++ b/src/lib/net/crates/codec/src/net_types/network_position.rs @@ -68,9 +68,15 @@ impl NetworkPosition { let mut x = (val >> 38) as i32; let mut y = (val << 52 >> 52) as i16; let mut z = (val << 26 >> 38) as i32; - if x >= 1 << 25 { x -= 1 << 26 } - if y >= 1 << 11 { y -= 1 << 12 } - if z >= 1 << 25 { z -= 1 << 26 } + if x >= 1 << 25 { + x -= 1 << 26 + } + if y >= 1 << 11 { + y -= 1 << 12 + } + if z >= 1 << 25 { + z -= 1 << 26 + } Self { x, y, z } } } diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 0b891a29..4d0bda06 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -21,7 +21,7 @@ pub struct PlaceBlock { } impl IncomingPacket for PlaceBlock { - async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + async fn handle(self, _conn_id: usize, _state: Arc) -> NetResult<()> { debug!("{:?}", self); Ok(()) } diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs index b2fa6468..51a9dc99 100644 --- a/src/lib/net/src/utils/broadcast.rs +++ b/src/lib/net/src/utils/broadcast.rs @@ -1,7 +1,6 @@ use crate::connection::StreamWriter; use crate::NetResult; use async_trait::async_trait; -use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_ecs::entities::Entity; use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts}; diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index d79b10f7..4d4f20db 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -17,7 +17,7 @@ use crate::data_packing::errors::DataPackingError; /// /// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 32. /// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits. -/// Reads an n-bit integer from a packed `i64`. +/// Reads an n-bit integer from a packed `i64`. pub fn read_nbit_i32( word: &i64, bit_size: usize, diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 6d09bb23..88925abd 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -3,14 +3,14 @@ use crate::vanilla_chunk_format; use crate::vanilla_chunk_format::VanillaChunk; use bitcode_derive::{Decode, Encode}; use deepsize::DeepSizeOf; -use ferrumc_general_purpose::data_packing::i32::{read_nbit_i32, write_nbit_i32}; +use ferrumc_general_purpose::data_packing::i32::read_nbit_i32; use ferrumc_macros::{NBTDeserialize, NBTSerialize}; use ferrumc_net_codec::net_types::var_int::VarInt; use lazy_static::lazy_static; use std::cmp::max; use std::collections::HashMap; use std::io::Read; -use tracing::{debug, error}; +use tracing::error; use vanilla_chunk_format::BlockData; #[cfg(test)] @@ -285,13 +285,10 @@ impl BlockStates { new_data.len() ))); } - // Update the chunk with the new packed data and bit size self.data = new_data; self.bits_per_block = new_bit_size as u8; - // debug!("Resize complete. New data: {:?}", self.data); - Ok(()) } } From aaf5d34f011f1f776803e9b0b7b5828101014319 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 31 Dec 2024 17:15:06 +1030 Subject: [PATCH 08/13] Fix tests --- src/lib/utils/general_purpose/src/data_packing/i32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/general_purpose/src/data_packing/i32.rs b/src/lib/utils/general_purpose/src/data_packing/i32.rs index 4d4f20db..aecbde08 100644 --- a/src/lib/utils/general_purpose/src/data_packing/i32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/i32.rs @@ -88,8 +88,8 @@ mod tests { fn test_read_nbit_i32() { let data: i64 = 0b110101011; assert_eq!(read_nbit_i32(&data, 3, 0).unwrap(), 0b011); - assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), -3); // 0b101 as i32 is -3 - assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), -2); // 0b110 as i32 is -2 + assert_eq!(read_nbit_i32(&data, 3, 3).unwrap(), 0b101); + assert_eq!(read_nbit_i32(&data, 3, 6).unwrap(), 0b110); assert_eq!(read_nbit_i32(&data, 3, 9).unwrap(), 0b000); } From a4e8ad9427ff83c416297d9f2a4b313e34fedd79 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 14:54:44 +1030 Subject: [PATCH 09/13] Now prints blocks clicked Also teleports you way up in the sky, no more spawning in the ground --- scripts/new_packet.py | 7 +-- src/bin/src/systems/chunk_sender.rs | 8 ++-- src/lib/core/src/chunks/chunk_receiver.rs | 3 ++ src/lib/derive_macros/src/net/packets/mod.rs | 2 +- src/lib/net/src/errors.rs | 7 ++- .../src/packets/incoming/chunk_batch_ack.rs | 45 +++++++++++++++++++ .../src/packets/incoming/chunks_per_tick.rs | 20 --------- src/lib/net/src/packets/incoming/mod.rs | 8 ++-- .../net/src/packets/incoming/place_block.rs | 15 +++++-- .../outgoing/set_default_spawn_position.rs | 2 +- 10 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/chunk_batch_ack.rs delete mode 100644 src/lib/net/src/packets/incoming/chunks_per_tick.rs diff --git a/scripts/new_packet.py b/scripts/new_packet.py index 0f493608..9db5e790 100644 --- a/scripts/new_packet.py +++ b/scripts/new_packet.py @@ -8,7 +8,7 @@ use std::sync::Arc; #[derive(NetDecode)] -#[packet(packet_id = ++id++, state = "play")] +#[packet(packet_id = "++id++", state = "play")] pub struct ++name++ { } @@ -24,7 +24,7 @@ use std::io::Write; #[derive(NetEncode)] -#[packet(packet_id = ++id++)] +#[packet(packet_id = "++id++")] pub struct ++name++ {} """ @@ -50,7 +50,8 @@ def to_camel_case(string) -> str: packet_name = input("Packet name: ") packets_dir = os.path.join(os.path.join(os.path.dirname(__file__), ".."), "src/lib/net/src/packets") -packet_id = input("Packet ID (formatted like 0x01): ") +packet_id = input( + "Packet ID (formatted as snake case, look on https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol if you need to get the id): ") packet_id = packet_id[:-2] + packet_id[-2:].upper() with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f: diff --git a/src/bin/src/systems/chunk_sender.rs b/src/bin/src/systems/chunk_sender.rs index 9053ecd6..ffaed9d0 100644 --- a/src/bin/src/systems/chunk_sender.rs +++ b/src/bin/src/systems/chunk_sender.rs @@ -69,13 +69,13 @@ impl System for ChunkSenderSystem { } { trace!("Getting chunk_recv 3 for sender"); - let chunk_recv = state + let mut chunk_recv = state .universe .get_mut::(eid) .expect("ChunkReceiver not found"); trace!("Got chunk_recv 3 for sender"); - for mut possible_chunk in chunk_recv.needed_chunks.iter_mut() { - if let (key, Some(chunk)) = possible_chunk.pair_mut() { + for (key, chunk) in chunk_recv.needed_chunks.iter_mut() { + if let Some(chunk) = chunk { chunk.sections.iter_mut().for_each(|section| { // if random::() < 25 { if let Err(e) = section.block_states.resize(8) { @@ -138,7 +138,7 @@ impl System for ChunkSenderSystem { } if let Err(e) = conn.send_packet( ChunkBatchFinish { - batch_size: VarInt::new(sent_chunks), + batch_size: VarInt::new(count), }, &NetEncodeOpts::WithLength, ) { diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 37d71b5f..3deb15ac 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -1,5 +1,6 @@ use ferrumc_world::chunk_format::Chunk; use std::collections::{HashMap, HashSet}; +use std::sync::atomic::AtomicBool; use tokio::time::Instant; const VIEW_DISTANCE: i32 = 8; @@ -9,6 +10,7 @@ pub struct ChunkReceiver { pub last_update: Instant, pub last_chunk: Option<(i32, i32, String)>, pub chunks_per_tick: f32, + pub has_loaded: AtomicBool, } impl Default for ChunkReceiver { @@ -25,6 +27,7 @@ impl ChunkReceiver { last_update: Instant::now(), last_chunk: None, chunks_per_tick: 0.0, + has_loaded: AtomicBool::new(false), } } } diff --git a/src/lib/derive_macros/src/net/packets/mod.rs b/src/lib/derive_macros/src/net/packets/mod.rs index 0bec0cff..44fbd9c8 100644 --- a/src/lib/derive_macros/src/net/packets/mod.rs +++ b/src/lib/derive_macros/src/net/packets/mod.rs @@ -118,7 +118,7 @@ pub fn bake_registry(input: TokenStream) -> TokenStream { .expect( "parse_packet_attribute failed\ \nPlease provide the packet_id and state fields in the #[packet(...)] attribute.\ - \nExample: #[packet(packet_id = 0x00, state = \"handshake\")]", + \nExample: #[packet(packet_id = \"example_packet\", state = \"handshake\")]", ); let struct_name = &item_struct.ident; diff --git a/src/lib/net/src/errors.rs b/src/lib/net/src/errors.rs index d24d3684..e61863a0 100644 --- a/src/lib/net/src/errors.rs +++ b/src/lib/net/src/errors.rs @@ -38,11 +38,14 @@ pub enum NetError { #[error("Invalid State: {0}")] InvalidState(u8), - #[error("{0}")] + #[error("Packet error: {0}")] Packet(#[from] PacketError), - #[error("{0}")] + #[error("Chunk error: {0}")] Chunk(#[from] ChunkError), + + #[error("World error: {0}")] + World(#[from] ferrumc_world::errors::WorldError), } #[derive(Debug, Error)] diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs new file mode 100644 index 00000000..83635945 --- /dev/null +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -0,0 +1,45 @@ +use crate::connection::StreamWriter; +use crate::packets::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket; +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::encode::NetEncodeOpts; +use ferrumc_state::ServerState; +use std::sync::Arc; + +#[derive(NetDecode)] +#[packet(packet_id = "chunk_batch_received", state = "play")] +pub struct ChunkBatchAck { + chunks_per_tick: f32, +} + +impl IncomingPacket for ChunkBatchAck { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + // The first chunk batch should be the ones sent when the player first joins the server. + // This just moves them to their spawn position when all their chunks are done loading, + // preventing them from falling into the floor. + let mut move_to_spawn = false; + { + let mut chunk_recv = state.universe.get_mut::(conn_id)?; + chunk_recv.chunks_per_tick = self.chunks_per_tick; + if !chunk_recv + .has_loaded + .load(std::sync::atomic::Ordering::Relaxed) + { + move_to_spawn = true; + chunk_recv + .has_loaded + .store(true, std::sync::atomic::Ordering::Relaxed); + } + } + if move_to_spawn { + let mut conn = state.universe.get_mut::(conn_id)?; + conn.send_packet( + SynchronizePlayerPositionPacket::default(), + &NetEncodeOpts::WithLength, + )?; + } + Ok(()) + } +} diff --git a/src/lib/net/src/packets/incoming/chunks_per_tick.rs b/src/lib/net/src/packets/incoming/chunks_per_tick.rs deleted file mode 100644 index a0f1f759..00000000 --- a/src/lib/net/src/packets/incoming/chunks_per_tick.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::packets::IncomingPacket; -use crate::NetResult; -use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; -use ferrumc_macros::{packet, NetDecode}; -use ferrumc_state::ServerState; -use std::sync::Arc; - -#[derive(NetDecode)] -#[packet(packet_id = 0x08, state = "play")] -pub struct ChunksPerTick { - chunks_per_tick: f32, -} - -impl IncomingPacket for ChunksPerTick { - async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { - let mut chunk_recv = state.universe.get_mut::(conn_id)?; - chunk_recv.chunks_per_tick = self.chunks_per_tick; - Ok(()) - } -} diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 7f233987..6701becf 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -11,11 +11,11 @@ pub mod status_request; pub mod keep_alive; pub mod packet_skeleton; +pub mod place_block; +pub mod player_command; pub mod set_player_position; pub mod set_player_position_and_rotation; pub mod set_player_rotation; - -pub mod chunks_per_tick; -pub mod place_block; -pub mod player_command; pub mod swing_arm; + +pub mod chunk_batch_ack; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 4d0bda06..8a7906f2 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use tracing::debug; #[derive(NetDecode, Debug)] -#[packet(packet_id = 0x38, state = "play")] +#[packet(packet_id = "use_item_on", state = "play")] pub struct PlaceBlock { pub hand: VarInt, pub position: NetworkPosition, @@ -21,8 +21,17 @@ pub struct PlaceBlock { } impl IncomingPacket for PlaceBlock { - async fn handle(self, _conn_id: usize, _state: Arc) -> NetResult<()> { - debug!("{:?}", self); + async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { + let block_clicked = state + .world + .get_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + ) + .await?; + debug!("Block clicked: {:?}", block_clicked); Ok(()) } } diff --git a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs index d8cff7b8..85f70543 100644 --- a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs +++ b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs @@ -9,7 +9,7 @@ pub struct SetDefaultSpawnPositionPacket { pub angle: f32, } -pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 256, z: 0 }; +pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 320, z: 0 }; const DEFAULT_ANGLE: f32 = 0.0; From 75a2f04fa6c1e581300e1350f4ea303aa0c932f6 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 17:48:02 +1030 Subject: [PATCH 10/13] Now sets blocks correctly --- src/lib/core/src/chunks/chunk_receiver.rs | 4 + .../src/packets/incoming/chunk_batch_ack.rs | 13 +++ .../net/src/packets/incoming/place_block.rs | 24 +++++ .../outgoing/set_default_spawn_position.rs | 2 +- .../packets/outgoing/set_render_distance.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 16 +-- src/lib/utils/logging/src/lib.rs | 4 + src/lib/world/src/chunk_format.rs | 23 ++-- src/lib/world/src/edits.rs | 100 +++++++++++++++++- src/lib/world/src/errors.rs | 2 + src/lib/world/src/lib.rs | 2 +- 11 files changed, 171 insertions(+), 21 deletions(-) diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 3deb15ac..07c38dce 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -30,6 +30,10 @@ impl ChunkReceiver { has_loaded: AtomicBool::new(false), } } + + pub fn queue_chunk_resend(&mut self, x: i32, z: i32, dimension: String) { + self.needed_chunks.insert((x, z, dimension), None); + } } impl ChunkReceiver { diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs index 83635945..6d7f36d1 100644 --- a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -7,6 +7,7 @@ use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::ServerState; use std::sync::Arc; +use ferrumc_core::transform::position::Position; #[derive(NetDecode)] #[packet(packet_id = "chunk_batch_received", state = "play")] @@ -33,6 +34,18 @@ impl IncomingPacket for ChunkBatchAck { .store(true, std::sync::atomic::Ordering::Relaxed); } } + { + // If they aren't underground, don't move them to spawn + let pos = state.universe.get_mut::(conn_id)?; + let head_block = state + .world + .get_block(pos.x as i32, pos.y as i32 - 1, pos.z as i32, "overworld") + .await?; + if head_block.name == "minecraft:air" { + move_to_spawn = false; + } + + } if move_to_spawn { let mut conn = state.universe.get_mut::(conn_id)?; conn.send_packet( diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index 8a7906f2..e4128182 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,9 +1,11 @@ use crate::packets::IncomingPacket; use crate::NetResult; +use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; +use ferrumc_world::vanilla_chunk_format::BlockData; use std::sync::Arc; use tracing::debug; @@ -23,6 +25,7 @@ pub struct PlaceBlock { impl IncomingPacket for PlaceBlock { async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { let block_clicked = state + .clone() .world .get_block( self.position.x, @@ -32,6 +35,27 @@ impl IncomingPacket for PlaceBlock { ) .await?; debug!("Block clicked: {:?}", block_clicked); + state + .world + .set_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + ) + .await?; + let q = state.universe.query::<&mut ChunkReceiver>(); + for (_, mut chunk_recv) in q { + chunk_recv.queue_chunk_resend( + self.position.x >> 4, + self.position.z >> 4, + "overworld".to_string(), + ); + } Ok(()) } } diff --git a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs index 85f70543..d8cff7b8 100644 --- a/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs +++ b/src/lib/net/src/packets/outgoing/set_default_spawn_position.rs @@ -9,7 +9,7 @@ pub struct SetDefaultSpawnPositionPacket { pub angle: f32, } -pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 320, z: 0 }; +pub const DEFAULT_SPAWN_POSITION: NetworkPosition = NetworkPosition { x: 0, y: 256, z: 0 }; const DEFAULT_ANGLE: f32 = 0.0; diff --git a/src/lib/net/src/packets/outgoing/set_render_distance.rs b/src/lib/net/src/packets/outgoing/set_render_distance.rs index 6c038c10..6257b741 100644 --- a/src/lib/net/src/packets/outgoing/set_render_distance.rs +++ b/src/lib/net/src/packets/outgoing/set_render_distance.rs @@ -8,7 +8,7 @@ pub struct SetRenderDistance { pub distance: VarInt, } -const DEFAULT_RENDER_DISTANCE: u8 = 5; +const DEFAULT_RENDER_DISTANCE: u8 = 18; impl Default for SetRenderDistance { fn default() -> Self { diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 91e2ed48..7e79db29 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -46,23 +46,27 @@ pub fn read_nbit_u32(data: &i64, size: u8, offset: u32) -> Result Result<(), DataPackingError> { + if size == 0 { + return Ok(()); // Nothing to do for 0 bits + } if size > 32 { return Err(DataPackingError::SizeExceedsMaxSize(size, 32)); } - if offset + size as u32 > 64 { + if offset >= 64 || offset + size as u32 > 64 { return Err(DataPackingError::NotEnoughBits(size, offset)); } - let mask = (1 << size) - 1; - *data &= !((mask) << offset); - *data |= ((value as u64) & mask) << offset; + let mask = ((1u64 << size) - 1) as i64; // Use u64 to avoid overflow + *data &= !(mask << offset); // Clear the target bits + *data |= ((value as i64) & mask) << offset; // Write the new value Ok(()) } + #[cfg(test)] mod tests { use super::*; @@ -80,7 +84,7 @@ mod tests { /// Tests the `write_nbit_u32` function with various inputs. #[test] fn test_write_nbit_u32() { - let mut data: u64 = 0; + let mut data: i64 = 0; write_nbit_u32(&mut data, 0, 0b011, 3).unwrap(); assert_eq!(data, 0b011); write_nbit_u32(&mut data, 3, 0b101, 3).unwrap(); diff --git a/src/lib/utils/logging/src/lib.rs b/src/lib/utils/logging/src/lib.rs index 97fece40..fe9ebbed 100644 --- a/src/lib/utils/logging/src/lib.rs +++ b/src/lib/utils/logging/src/lib.rs @@ -45,6 +45,10 @@ pub fn init_logging(trace_level: Level) { .with_thread_ids(false) .with_thread_names(false); } + #[cfg(debug_assertions)] + { + fmt_layer = fmt_layer.with_file(true).with_line_number(true); + } let profiler_layer = ProfilerTracingLayer; diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 88925abd..54fab67b 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -72,7 +72,7 @@ pub struct BlockStates { pub non_air_blocks: u16, pub data: Vec, pub palette: Vec, - pub block_counts: HashMap, + pub block_counts: HashMap, } fn convert_to_net_palette(vanilla_palettes: Vec) -> Result, WorldError> { @@ -132,24 +132,25 @@ impl VanillaChunk { for chunk in &block_data { let mut i = 0; while i + bits_per_block < 64 { - let id = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; - *block_counts.entry(id).or_insert(0) += 1; + let palette_index = read_nbit_i32(chunk, bits_per_block as usize, i as u32)?; + let block = palette + .get(palette_index as usize) + .unwrap_or(&BlockData::default()) + .clone(); + *block_counts.entry(block).or_insert(0) += 1; i += bits_per_block; } } if block_data.is_empty() { let single_block = if let Some(block) = palette.first() { - if let Some(id) = BLOCK2ID.get(block) { - *id - } else { - 0 - } + block } else { - 0 + &BlockData::default() }; - block_counts.insert(single_block, 4096); + block_counts.insert(single_block.clone(), 4096); } - let non_air_blocks = 4096 - *block_counts.get(&0).unwrap_or(&0) as u16; + let non_air_blocks = + 4096 - *block_counts.get(&BlockData::default()).unwrap_or(&0) as u16; let block_states = BlockStates { bits_per_block, block_counts, diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 2af46ea7..e5a08cf8 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -1,7 +1,9 @@ -use crate::chunk_format::ID2BLOCK; +use crate::chunk_format::{BLOCK2ID, ID2BLOCK}; use crate::errors::WorldError; +use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format::BlockData; use crate::World; +use std::cmp::max; impl World { /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. @@ -71,4 +73,100 @@ impl World { .unwrap_or(&BlockData::default()) .clone()) } + + pub async fn set_block( + &self, + x: i32, + y: i32, + z: i32, + dimension: &str, + block: BlockData, + ) -> Result<(), WorldError> { + if !BLOCK2ID.contains_key(&block) { + return Err(WorldError::InvalidBlock(block)); + }; + // Get chunk + let chunk_x = x >> 4; + let chunk_z = z >> 4; + let mut chunk = self.load_chunk(chunk_x, chunk_z, dimension).await?; + let section = chunk + .sections + .iter_mut() + .find(|section| section.y == (y >> 4) as i8) + .ok_or(WorldError::SectionOutOfBounds(y >> 4))?; + let mut bits_per_block = section.block_states.bits_per_block; + // Remove old block + let old_block = self.get_block(x, y, z, dimension).await?; + let old_block_count = section + .block_states + .block_counts + .get_mut(&old_block) + .expect("Block not found"); + *old_block_count -= 1; + if *old_block_count == 0 { + section.block_states.block_counts.remove(&old_block); + } + let block_id = BLOCK2ID + .get(&block) + .ok_or(WorldError::InvalidBlock(block.clone()))?; + // Add new block + if let Some(e) = section.block_states.block_counts.get(&block) { + section.block_states.block_counts.insert(block, e + 1); + } else { + section.block_states.block_counts.insert(block, 1); + } + // Check if we need to resize bits_per_block + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } + // Get block index + let mut block_palette_index = -1i16; + for (index, palette) in section.block_states.palette.iter().enumerate() { + if palette.val == *block_id { + block_palette_index = index as i16; + break; + } + } + // Add block to palette if it doesn't exist + if block_palette_index == -1 { + block_palette_index = section.block_states.palette.len() as i16; + section.block_states.palette.push((*block_id).into()); + } + // Set block + let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; + let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; + let i64_index = index / blocks_per_i64; + let packed_u64 = + section + .block_states + .data + .get_mut(i64_index) + .ok_or(InvalidBlockStateData(format!( + "Invalid block state data at index {}", + i64_index + )))?; + let offset = (index % blocks_per_i64) * bits_per_block as usize; + if let Err(e) = ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + packed_u64, + offset as u32, + block_palette_index as u32, + bits_per_block, + ) { + return Err(InvalidBlockStateData(format!( + "Failed to write block: {}", + e + ))); + } + // Save chunk + self.save_chunk(chunk).await?; + // TODO: Remove empty palette entries + Ok(()) + } } diff --git a/src/lib/world/src/errors.rs b/src/lib/world/src/errors.rs index b4a18eda..998c8bcf 100644 --- a/src/lib/world/src/errors.rs +++ b/src/lib/world/src/errors.rs @@ -45,6 +45,8 @@ pub enum WorldError { SectionOutOfBounds(i32), #[error("Invalid block state data")] InvalidBlockStateData(String), + #[error("Invalid block: {0}")] + InvalidBlock(BlockData), } // implemente AcquireError for WorldError diff --git a/src/lib/world/src/lib.rs b/src/lib/world/src/lib.rs index 52a00b03..a58afdd0 100644 --- a/src/lib/world/src/lib.rs +++ b/src/lib/world/src/lib.rs @@ -5,7 +5,7 @@ mod db_functions; pub mod edits; pub mod errors; mod importing; -mod vanilla_chunk_format; +pub mod vanilla_chunk_format; use crate::chunk_format::Chunk; use crate::errors::WorldError; From bf07482c0a7849d05741114b3f049543bcecec6b Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 18:49:30 +1030 Subject: [PATCH 11/13] Breaking blocks and removing empty palette entries --- src/lib/core/src/chunks/chunk_receiver.rs | 6 +- .../src/packets/incoming/chunk_batch_ack.rs | 3 +- src/lib/net/src/packets/incoming/mod.rs | 2 + .../net/src/packets/incoming/player_action.rs | 45 ++++++++++ .../packets/outgoing/set_render_distance.rs | 2 +- .../general_purpose/src/data_packing/u32.rs | 1 - src/lib/world/src/edits.rs | 87 +++++++++++++------ 7 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 src/lib/net/src/packets/incoming/player_action.rs diff --git a/src/lib/core/src/chunks/chunk_receiver.rs b/src/lib/core/src/chunks/chunk_receiver.rs index 07c38dce..b796620f 100644 --- a/src/lib/core/src/chunks/chunk_receiver.rs +++ b/src/lib/core/src/chunks/chunk_receiver.rs @@ -30,9 +30,11 @@ impl ChunkReceiver { has_loaded: AtomicBool::new(false), } } - + pub fn queue_chunk_resend(&mut self, x: i32, z: i32, dimension: String) { - self.needed_chunks.insert((x, z, dimension), None); + if self.can_see.contains(&(x, z, dimension.clone())) { + self.needed_chunks.insert((x, z, dimension), None); + } } } diff --git a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs index 6d7f36d1..7d8d7695 100644 --- a/src/lib/net/src/packets/incoming/chunk_batch_ack.rs +++ b/src/lib/net/src/packets/incoming/chunk_batch_ack.rs @@ -3,11 +3,11 @@ use crate::packets::outgoing::synchronize_player_position::SynchronizePlayerPosi use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::transform::position::Position; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::ServerState; use std::sync::Arc; -use ferrumc_core::transform::position::Position; #[derive(NetDecode)] #[packet(packet_id = "chunk_batch_received", state = "play")] @@ -44,7 +44,6 @@ impl IncomingPacket for ChunkBatchAck { if head_block.name == "minecraft:air" { move_to_spawn = false; } - } if move_to_spawn { let mut conn = state.universe.get_mut::(conn_id)?; diff --git a/src/lib/net/src/packets/incoming/mod.rs b/src/lib/net/src/packets/incoming/mod.rs index 6701becf..3a9b8063 100644 --- a/src/lib/net/src/packets/incoming/mod.rs +++ b/src/lib/net/src/packets/incoming/mod.rs @@ -19,3 +19,5 @@ pub mod set_player_rotation; pub mod swing_arm; pub mod chunk_batch_ack; + +pub mod player_action; diff --git a/src/lib/net/src/packets/incoming/player_action.rs b/src/lib/net/src/packets/incoming/player_action.rs new file mode 100644 index 00000000..bc5962b7 --- /dev/null +++ b/src/lib/net/src/packets/incoming/player_action.rs @@ -0,0 +1,45 @@ +use crate::packets::IncomingPacket; +use crate::NetResult; +use ferrumc_macros::{packet, NetDecode}; +use ferrumc_net_codec::net_types::network_position::NetworkPosition; +use ferrumc_net_codec::net_types::var_int::VarInt; +use ferrumc_state::ServerState; +use std::sync::Arc; +use tracing::debug; + +#[derive(NetDecode)] +#[packet(packet_id = "player_action", state = "play")] +pub struct PlayerAction { + pub status: VarInt, + pub location: NetworkPosition, + pub face: u8, + pub sequence: VarInt, +} + +impl IncomingPacket for PlayerAction { + async fn handle(self, _: usize, state: Arc) -> NetResult<()> { + // https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol?oldid=2773393#Player_Action + match self.status.val { + 0 => { + state + .world + .set_block( + self.location.x, + self.location.y as i32, + self.location.z, + "overworld", + ferrumc_world::vanilla_chunk_format::BlockData { + name: "minecraft:air".to_string(), + properties: None, + }, + ) + .await?; + } + 1 => { + debug!("You shouldn't be seeing this in creative mode."); + } + _ => {} + }; + Ok(()) + } +} diff --git a/src/lib/net/src/packets/outgoing/set_render_distance.rs b/src/lib/net/src/packets/outgoing/set_render_distance.rs index 6257b741..6c038c10 100644 --- a/src/lib/net/src/packets/outgoing/set_render_distance.rs +++ b/src/lib/net/src/packets/outgoing/set_render_distance.rs @@ -8,7 +8,7 @@ pub struct SetRenderDistance { pub distance: VarInt, } -const DEFAULT_RENDER_DISTANCE: u8 = 18; +const DEFAULT_RENDER_DISTANCE: u8 = 5; impl Default for SetRenderDistance { fn default() -> Self { diff --git a/src/lib/utils/general_purpose/src/data_packing/u32.rs b/src/lib/utils/general_purpose/src/data_packing/u32.rs index 7e79db29..92175e21 100644 --- a/src/lib/utils/general_purpose/src/data_packing/u32.rs +++ b/src/lib/utils/general_purpose/src/data_packing/u32.rs @@ -66,7 +66,6 @@ pub fn write_nbit_u32( Ok(()) } - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index e5a08cf8..7c03f47a 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -4,6 +4,7 @@ use crate::errors::WorldError::InvalidBlockStateData; use crate::vanilla_chunk_format::BlockData; use crate::World; use std::cmp::max; +use tracing::debug; impl World { /// Asynchronously retrieves the block data at the specified coordinates in the given dimension. @@ -103,8 +104,10 @@ impl World { .get_mut(&old_block) .expect("Block not found"); *old_block_count -= 1; - if *old_block_count == 0 { + let mut remove_old_block = false; + if *old_block_count <= 0 { section.block_states.block_counts.remove(&old_block); + remove_old_block = true; } let block_id = BLOCK2ID .get(&block) @@ -115,30 +118,18 @@ impl World { } else { section.block_states.block_counts.insert(block, 1); } - // Check if we need to resize bits_per_block - let new_bits_per_block = max( - (section.block_states.block_counts.len() as f32) - .log2() - .ceil() as u8, - 4, - ); - if new_bits_per_block != bits_per_block { - section.block_states.resize(new_bits_per_block as usize)?; - bits_per_block = new_bits_per_block; - } // Get block index - let mut block_palette_index = -1i16; - for (index, palette) in section.block_states.palette.iter().enumerate() { - if palette.val == *block_id { - block_palette_index = index as i16; - break; - } - } - // Add block to palette if it doesn't exist - if block_palette_index == -1 { - block_palette_index = section.block_states.palette.len() as i16; - section.block_states.palette.push((*block_id).into()); - } + let block_palette_index = section + .block_states + .palette + .iter() + .position(|p| p.val == *block_id) + .unwrap_or_else(|| { + // Add block to palette if it doesn't exist + let index = section.block_states.palette.len() as i16; + section.block_states.palette.push((*block_id).into()); + index as usize + }); // Set block let blocks_per_i64 = (64f64 / bits_per_block as f64).floor() as usize; let index = ((y & 0xf) * 256 + (z & 0xf) * 16 + (x & 0xf)) as usize; @@ -164,9 +155,55 @@ impl World { e ))); } + // Remove empty palette entries + if remove_old_block { + debug!("Removing empty palette entry"); + // remove old block from palette + let old_block_id = BLOCK2ID.get(&old_block).unwrap(); + let old_block_palette_index = section + .block_states + .palette + .iter() + .position(|p| p.val == *old_block_id) + .expect("Old block not found in palette"); + section.block_states.palette.remove(old_block_palette_index); + // go through the block data and decrement the index of all blocks greater than the one we removed + for data in &mut section.block_states.data { + let mut i = 0; + while (i + bits_per_block as usize) < 64 { + let block_index = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( + data, + bits_per_block as u8, + i as u32, + )?; + if block_index > old_block_palette_index as u32 { + ferrumc_general_purpose::data_packing::u32::write_nbit_u32( + data, + i as u32, + block_index - 1, + bits_per_block, + )?; + } + i += bits_per_block as usize; + } + } + } + + // Check if we need to resize bits_per_block + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } + section.block_states.bits_per_block = bits_per_block; + // Save chunk self.save_chunk(chunk).await?; - // TODO: Remove empty palette entries Ok(()) } } From f8812249f1ad79bd41211ee287bcd93c859e3180 Mon Sep 17 00:00:00 2001 From: ReCore Date: Tue, 21 Jan 2025 19:07:48 +1030 Subject: [PATCH 12/13] clippy --- src/lib/world/src/edits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index 7c03f47a..a55dd88b 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -173,7 +173,7 @@ impl World { while (i + bits_per_block as usize) < 64 { let block_index = ferrumc_general_purpose::data_packing::u32::read_nbit_u32( data, - bits_per_block as u8, + bits_per_block, i as u32, )?; if block_index > old_block_palette_index as u32 { From d3f5bec06bc00777ec2b704a6896b5fffc7ab20e Mon Sep 17 00:00:00 2001 From: ReCore Date: Wed, 22 Jan 2025 14:59:56 +1030 Subject: [PATCH 13/13] Added collision checking and fixed double packet bug --- src/bin/src/packet_handlers/login_process.rs | 14 +- src/lib/core/Cargo.toml | 7 + src/lib/core/benches/collisions.rs | 56 ++++++ src/lib/core/benches/core_bench.rs | 9 + src/lib/core/src/collisions/bounds.rs | 170 ++++++++++++++++++ src/lib/core/src/collisions/mod.rs | 1 + src/lib/core/src/lib.rs | 1 + .../net/src/packets/incoming/place_block.rs | 110 ++++++++---- src/lib/world/src/edits.rs | 14 ++ 9 files changed, 349 insertions(+), 33 deletions(-) create mode 100644 src/lib/core/benches/collisions.rs create mode 100644 src/lib/core/benches/core_bench.rs create mode 100644 src/lib/core/src/collisions/bounds.rs create mode 100644 src/lib/core/src/collisions/mod.rs diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index ccf82297..9cf826bf 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -1,5 +1,6 @@ use ferrumc_config::statics::{get_global_config, get_whitelist}; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::collisions::bounds::CollisionBounds; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_core::transform::grounded::OnGround; use ferrumc_core::transform::position::Position; @@ -152,7 +153,18 @@ async fn handle_ack_finish_configuration( .add_component::(entity_id, Position::default())? .add_component::(entity_id, Rotation::default())? .add_component::(entity_id, OnGround::default())? - .add_component::(entity_id, ChunkReceiver::default())?; + .add_component::(entity_id, ChunkReceiver::default())? + .add_component::( + entity_id, + CollisionBounds { + x_offset_start: -0.3, + x_offset_end: 0.3, + y_offset_start: -0.5, + y_offset_end: 1.5, + z_offset_start: -0.3, + z_offset_end: 0.3, + }, + )?; let mut writer = state.universe.get_mut::(entity_id)?; diff --git a/src/lib/core/Cargo.toml b/src/lib/core/Cargo.toml index 435b049c..e01b29f0 100644 --- a/src/lib/core/Cargo.toml +++ b/src/lib/core/Cargo.toml @@ -12,3 +12,10 @@ dashmap = { workspace = true } ferrumc-world = { workspace = true } tracing = { workspace = true } log = "0.4.22" + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "core_bench" +harness = false diff --git a/src/lib/core/benches/collisions.rs b/src/lib/core/benches/collisions.rs new file mode 100644 index 00000000..3bac8865 --- /dev/null +++ b/src/lib/core/benches/collisions.rs @@ -0,0 +1,56 @@ +use criterion::{black_box, Criterion}; +use ferrumc_core::collisions::bounds::CollisionBounds; + +pub fn bench_collides(c: &mut Criterion) { + let mut g = c.benchmark_group("collisions"); + g.bench_function("Simple collides", |b| { + b.iter(|| { + let bounds1 = black_box(CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 2.0, + y_offset_start: 0.0, + y_offset_end: 2.0, + z_offset_start: 0.0, + z_offset_end: 2.0, + }); + let bounds2 = black_box(CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 3.0, + y_offset_start: 1.0, + y_offset_end: 3.0, + z_offset_start: 1.0, + z_offset_end: 3.0, + }); + bounds1.collides( + black_box((0.0, 0.0, 0.0)), + &bounds2, + black_box((0.0, 0.0, 0.0)), + ); + }) + }); + g.bench_function("Complex collides", |b| { + b.iter(|| { + let bounds1 = black_box(CollisionBounds { + x_offset_start: 64.2, + x_offset_end: -8.4, + y_offset_start: -12.0, + y_offset_end: 16.3, + z_offset_start: 99.55, + z_offset_end: 100.999, + }); + let bounds2 = black_box(CollisionBounds { + x_offset_start: 5.0, + x_offset_end: 6.0, + y_offset_start: 1.0, + y_offset_end: 0.0, + z_offset_start: 2.0, + z_offset_end: 3.0, + }); + bounds1.collides( + black_box((12.0, 66.0, -5.0)), + &bounds2, + black_box((4444.0, -300.0, 0.1)), + ); + }) + }); +} diff --git a/src/lib/core/benches/core_bench.rs b/src/lib/core/benches/core_bench.rs new file mode 100644 index 00000000..b055d612 --- /dev/null +++ b/src/lib/core/benches/core_bench.rs @@ -0,0 +1,9 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +mod collisions; + +fn bench(c: &mut Criterion) { + collisions::bench_collides(c); +} + +criterion_group!(benches, bench); +criterion_main!(benches); diff --git a/src/lib/core/src/collisions/bounds.rs b/src/lib/core/src/collisions/bounds.rs new file mode 100644 index 00000000..6a3d5330 --- /dev/null +++ b/src/lib/core/src/collisions/bounds.rs @@ -0,0 +1,170 @@ +pub struct CollisionBounds { + // Given a start position, where the bounding box starts on the x-axis. + pub x_offset_start: f64, + // Given a start position, where the bounding box ends on the x-axis. + pub x_offset_end: f64, + // Given a start position, where the bounding box starts on the y-axis. + pub y_offset_start: f64, + // Given a start position, where the bounding box ends on the y-axis. + pub y_offset_end: f64, + // Given a start position, where the bounding box starts on the z-axis. + pub z_offset_start: f64, + // Given a start position, where the bounding box ends on the z-axis. + pub z_offset_end: f64, +} + +impl Default for CollisionBounds { + fn default() -> Self { + CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 0.0, + y_offset_start: 0.0, + y_offset_end: 0.0, + z_offset_start: 0.0, + z_offset_end: 0.0, + } + } +} + +impl CollisionBounds { + #[inline] + pub fn collides( + &self, + own_pos: (f64, f64, f64), + other_bounds: &CollisionBounds, + other_pos: (f64, f64, f64), + ) -> bool { + let (own_x, own_y, own_z) = own_pos; + let (other_x, other_y, other_z) = other_pos; + + // Pre-calculate bounds + let own_x_start = own_x + self.x_offset_start; + let own_x_end = own_x + self.x_offset_end; + let own_y_start = own_y + self.y_offset_start; + let own_y_end = own_y + self.y_offset_end; + let own_z_start = own_z + self.z_offset_start; + let own_z_end = own_z + self.z_offset_end; + + let other_x_start = other_x + other_bounds.x_offset_start; + let other_x_end = other_x + other_bounds.x_offset_end; + let other_y_start = other_y + other_bounds.y_offset_start; + let other_y_end = other_y + other_bounds.y_offset_end; + let other_z_start = other_z + other_bounds.z_offset_start; + let other_z_end = other_z + other_bounds.z_offset_end; + + // Check collisions axis by axis + (own_x_start < other_x_end && own_x_end > other_x_start) + && (own_y_start < other_y_end && own_y_end > other_y_start) + && (own_z_start < other_z_end && own_z_end > other_z_start) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn collides_when_boxes_overlap() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 2.0, + y_offset_start: 0.0, + y_offset_end: 2.0, + z_offset_start: 0.0, + z_offset_end: 2.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 3.0, + y_offset_start: 1.0, + y_offset_end: 3.0, + z_offset_start: 1.0, + z_offset_end: 3.0, + }; + assert!(bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn does_not_collide_when_boxes_do_not_overlap() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 2.0, + x_offset_end: 3.0, + y_offset_start: 2.0, + y_offset_end: 3.0, + z_offset_start: 2.0, + z_offset_end: 3.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn collides_when_boxes_touch_edges() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 2.0, + y_offset_start: 1.0, + y_offset_end: 2.0, + z_offset_start: 1.0, + z_offset_end: 2.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn collides_when_one_box_inside_another() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 3.0, + y_offset_start: 0.0, + y_offset_end: 3.0, + z_offset_start: 0.0, + z_offset_end: 3.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 1.0, + x_offset_end: 2.0, + y_offset_start: 1.0, + y_offset_end: 2.0, + z_offset_start: 1.0, + z_offset_end: 2.0, + }; + assert!(bounds1.collides((0.0, 0.0, 0.0), &bounds2, (0.0, 0.0, 0.0))); + } + + #[test] + fn does_not_collide_when_positions_are_far_apart() { + let bounds1 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + let bounds2 = CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }; + assert!(!bounds1.collides((0.0, 0.0, 0.0), &bounds2, (10.0, 10.0, 10.0))); + } +} diff --git a/src/lib/core/src/collisions/mod.rs b/src/lib/core/src/collisions/mod.rs new file mode 100644 index 00000000..c919e66b --- /dev/null +++ b/src/lib/core/src/collisions/mod.rs @@ -0,0 +1 @@ +pub mod bounds; diff --git a/src/lib/core/src/lib.rs b/src/lib/core/src/lib.rs index 93d6b8d4..cb752268 100644 --- a/src/lib/core/src/lib.rs +++ b/src/lib/core/src/lib.rs @@ -2,6 +2,7 @@ pub mod errors; // Core structs/types. Usually used in ECS Components. pub mod chunks; +pub mod collisions; pub mod identity; pub mod state; pub mod transform; diff --git a/src/lib/net/src/packets/incoming/place_block.rs b/src/lib/net/src/packets/incoming/place_block.rs index e4128182..458ed200 100644 --- a/src/lib/net/src/packets/incoming/place_block.rs +++ b/src/lib/net/src/packets/incoming/place_block.rs @@ -1,13 +1,15 @@ use crate::packets::IncomingPacket; use crate::NetResult; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; +use ferrumc_core::collisions::bounds::CollisionBounds; +use ferrumc_core::transform::position::Position; use ferrumc_macros::{packet, NetDecode}; use ferrumc_net_codec::net_types::network_position::NetworkPosition; use ferrumc_net_codec::net_types::var_int::VarInt; use ferrumc_state::ServerState; use ferrumc_world::vanilla_chunk_format::BlockData; use std::sync::Arc; -use tracing::debug; +use tracing::{debug, trace}; #[derive(NetDecode, Debug)] #[packet(packet_id = "use_item_on", state = "play")] @@ -24,37 +26,81 @@ pub struct PlaceBlock { impl IncomingPacket for PlaceBlock { async fn handle(self, _conn_id: usize, state: Arc) -> NetResult<()> { - let block_clicked = state - .clone() - .world - .get_block( - self.position.x, - self.position.y as i32, - self.position.z, - "overworld", - ) - .await?; - debug!("Block clicked: {:?}", block_clicked); - state - .world - .set_block( - self.position.x, - self.position.y as i32, - self.position.z, - "overworld", - BlockData { - name: "minecraft:stone".to_string(), - properties: None, - }, - ) - .await?; - let q = state.universe.query::<&mut ChunkReceiver>(); - for (_, mut chunk_recv) in q { - chunk_recv.queue_chunk_resend( - self.position.x >> 4, - self.position.z >> 4, - "overworld".to_string(), - ); + match self.hand.val { + 0 => { + debug!("Placing block at {:?}", self.position); + let block_clicked = state + .clone() + .world + .get_block( + self.position.x, + self.position.y as i32, + self.position.z, + "overworld", + ) + .await?; + trace!("Block clicked: {:?}", block_clicked); + // Use the face to determine the offset of the block to place + let (x_block_offset, y_block_offset, z_block_offset) = match self.face.val { + 0 => (0, -1, 0), + 1 => (0, 1, 0), + 2 => (0, 0, -1), + 3 => (0, 0, 1), + 4 => (-1, 0, 0), + 5 => (1, 0, 0), + _ => (0, 0, 0), + }; + let (x, y, z) = ( + self.position.x + x_block_offset, + self.position.y + y_block_offset, + self.position.z + z_block_offset, + ); + // Check if the block collides with any entities + let does_collide = { + let q = state.universe.query::<(&Position, &CollisionBounds)>(); + q.into_iter().any(|(_, (pos, bounds))| { + bounds.collides( + (pos.x, pos.y, pos.z), + &CollisionBounds { + x_offset_start: 0.0, + x_offset_end: 1.0, + y_offset_start: 0.0, + y_offset_end: 1.0, + z_offset_start: 0.0, + z_offset_end: 1.0, + }, + (x as f64, y as f64, z as f64), + ) + }) + }; + if does_collide { + debug!("Block placement collided with entity"); + return Ok(()); + } + state + .world + .set_block( + x, + y as i32, + z, + "overworld", + BlockData { + name: "minecraft:stone".to_string(), + properties: None, + }, + ) + .await?; + let q = state.universe.query::<&mut ChunkReceiver>(); + for (_, mut chunk_recv) in q { + chunk_recv.queue_chunk_resend(x >> 4, z >> 4, "overworld".to_string()); + } + } + 1 => { + debug!("Offhand block placement not implemented"); + } + _ => { + debug!("Invalid hand"); + } } Ok(()) } diff --git a/src/lib/world/src/edits.rs b/src/lib/world/src/edits.rs index a55dd88b..6bf50825 100644 --- a/src/lib/world/src/edits.rs +++ b/src/lib/world/src/edits.rs @@ -98,6 +98,7 @@ impl World { let mut bits_per_block = section.block_states.bits_per_block; // Remove old block let old_block = self.get_block(x, y, z, dimension).await?; + debug!("Old block: {:?}", old_block); let old_block_count = section .block_states .block_counts @@ -116,8 +117,20 @@ impl World { if let Some(e) = section.block_states.block_counts.get(&block) { section.block_states.block_counts.insert(block, e + 1); } else { + debug!("Adding block to block counts"); section.block_states.block_counts.insert(block, 1); } + let new_bits_per_block = max( + (section.block_states.block_counts.len() as f32) + .log2() + .ceil() as u8, + 4, + ); + if new_bits_per_block != bits_per_block { + debug!("Resizing block states to {}", new_bits_per_block); + section.block_states.resize(new_bits_per_block as usize)?; + bits_per_block = new_bits_per_block; + } // Get block index let block_palette_index = section .block_states @@ -197,6 +210,7 @@ impl World { 4, ); if new_bits_per_block != bits_per_block { + debug!("Resizing block states to {}", new_bits_per_block); section.block_states.resize(new_bits_per_block as usize)?; bits_per_block = new_bits_per_block; }