diff --git a/azalea-block/README.md b/azalea-block/README.md index e4b6357b4..9be4c79bc 100755 --- a/azalea-block/README.md +++ b/azalea-block/README.md @@ -8,11 +8,11 @@ There's three block types, used for different things. You can (mostly) convert b ``` # use azalea_block::BlockState; -let block_state: BlockState = azalea_block::CobblestoneWallBlock { - east: azalea_block::EastWall::Low, - north: azalea_block::NorthWall::Low, - south: azalea_block::SouthWall::Low, - west: azalea_block::WestWall::Low, +let block_state: BlockState = azalea_block::blocks::CobblestoneWall { + east: azalea_block::properties::EastWall::Low, + north: azalea_block::properties::NorthWall::Low, + south: azalea_block::properties::SouthWall::Low, + west: azalea_block::properties::WestWall::Low, up: false, waterlogged: false, } @@ -36,7 +36,7 @@ let block = Box::::from(block_state); ``` # use azalea_block::{Block, BlockState}; # let block_state: BlockState = azalea_registry::Block::Jukebox.into(); -if let Some(jukebox) = Box::::from(block_state).downcast_ref::() { +if let Some(jukebox) = Box::::from(block_state).downcast_ref::() { // ... } ``` diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index b69ebd06a..a8739e7ca 100755 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -38,7 +38,7 @@ struct PropertyDefinitions { properties: Vec, } -/// `snowy: false` or `axis: Axis::Y` +/// `snowy: false` or `axis: properties::Axis::Y` #[derive(Debug)] struct PropertyWithNameAndDefault { name: Ident, @@ -59,7 +59,7 @@ struct BlockDefinition { } impl Parse for PropertyWithNameAndDefault { fn parse(input: ParseStream) -> Result { - // `snowy: false` or `axis: Axis::Y` + // `snowy: false` or `axis: properties::Axis::Y` let property_name = input.parse()?; input.parse::()?; @@ -74,7 +74,7 @@ impl Parse for PropertyWithNameAndDefault { is_enum = true; property_type = first_ident; let variant = input.parse::()?; - property_default.extend(quote! { ::#variant }); + property_default = quote! { properties::#property_default::#variant }; } else if first_ident_string == "true" || first_ident_string == "false" { property_type = Ident::new("bool", first_ident.span()); } else { @@ -310,6 +310,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let mut from_state_to_block_match = quote! {}; let mut from_registry_block_to_block_match = quote! {}; let mut from_registry_block_to_blockstate_match = quote! {}; + let mut from_registry_block_to_blockstates_match = quote! {}; for block in &input.block_definitions.blocks { let block_property_names = &block @@ -386,13 +387,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { for PropertyWithNameAndDefault { property_type: struct_name, name, + is_enum, .. } in &properties_with_name { // let property_name_snake = // Ident::new(&property.to_string(), proc_macro2::Span::call_site()); - block_struct_fields.extend(quote! { - pub #name: #struct_name, + block_struct_fields.extend(if *is_enum { + quote! { pub #name: properties::#struct_name, } + } else { + quote! { pub #name: #struct_name, } }); } @@ -400,10 +404,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { &to_pascal_case(&block.name.to_string()), proc_macro2::Span::call_site(), ); - let block_struct_name = Ident::new( - &format!("{block_name_pascal_case}Block"), - proc_macro2::Span::call_site(), - ); + let block_struct_name = Ident::new(&block_name_pascal_case.to_string(), proc_macro2::Span::call_site()); let mut from_block_to_state_match_inner = quote! {}; @@ -445,7 +446,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } let property_type = if property.is_enum { - quote! {#property_struct_name_ident::#variant} + quote! {properties::#property_struct_name_ident::#variant} } else { quote! {#variant} }; @@ -476,9 +477,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // 7035..=7058 => { // let b = b - 7035; // &AcaciaButtonBlock { - // powered: Powered::from((b / 1) % 2), - // facing: Facing::from((b / 2) % 4), - // face: Face::from((b / 8) % 3), + // powered: properties::Powered::from((b / 1) % 2), + // facing: properties::Facing::from((b / 2) % 4), + // face: properties::Face::from((b / 8) % 3), // } // } let mut from_state_to_block_inner = quote! {}; @@ -498,7 +499,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { // this is not a mistake, it starts with true for some reason quote! {(b / #division) % #property_variants_count == 0} } else { - quote! {#property_struct_name_ident::from((b / #division) % #property_variants_count)} + quote! {properties::#property_struct_name_ident::from((b / #division) % #property_variants_count)} } }; from_state_to_block_inner.extend(quote! { @@ -523,6 +524,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { from_registry_block_to_blockstate_match.extend(quote! { azalea_registry::Block::#block_name_pascal_case => BlockState { id: #default_state_id }, }); + from_registry_block_to_blockstates_match.extend(quote! { + azalea_registry::Block::#block_name_pascal_case => BlockStates::from(#first_state_id..=#last_state_id), + }); let mut block_default_fields = quote! {}; for PropertyWithNameAndDefault { @@ -560,14 +564,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { fn id(&self) -> &'static str { #block_id } - fn as_blockstate(&self) -> BlockState { + fn as_block_state(&self) -> BlockState { #from_block_to_state_match } } impl From<#block_struct_name> for BlockState { fn from(b: #block_struct_name) -> Self { - b.as_blockstate() + b.as_block_state() } } @@ -585,21 +589,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let last_state_id = state_id - 1; let mut generated = quote! { - #property_enums - - /// A representation of a state a block can be in. (for example, a stone - /// block only has one state but each possible stair rotation is a - /// different state). - #[derive(Copy, Clone, PartialEq, Eq, Default)] - pub struct BlockState { - /// The protocol ID for the block state. IDs may change every - /// version, so you shouldn't hard-code them or store them in databases. - pub id: u32 - } - impl BlockState { - pub const AIR: BlockState = BlockState { id: 0 }; - /// Returns the highest possible state ID. #[inline] pub fn max_state() -> u32 { @@ -607,38 +597,50 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } } - impl std::fmt::Debug for BlockState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "BlockState(id: {}, {:?})", self.id, Box::::from(*self)) - } + pub mod properties { + use super::*; + + #property_enums } }; generated.extend(quote! { - #block_structs - - impl From for Box { - fn from(block_state: BlockState) -> Self { - let b = block_state.id; - match b { - #from_state_to_block_match - _ => panic!("Invalid block state: {}", b), + pub mod blocks { + use super::*; + + #block_structs + + impl From for Box { + fn from(block_state: BlockState) -> Self { + let b = block_state.id; + match b { + #from_state_to_block_match + _ => panic!("Invalid block state: {}", b), + } } } - } - impl From for Box { - fn from(block: azalea_registry::Block) -> Self { - match block { - #from_registry_block_to_block_match - _ => unreachable!("There should always be a block struct for every azalea_registry::Block variant") + impl From for Box { + fn from(block: azalea_registry::Block) -> Self { + match block { + #from_registry_block_to_block_match + _ => unreachable!("There should always be a block struct for every azalea_registry::Block variant") + } } } - } - impl From for BlockState { - fn from(block: azalea_registry::Block) -> Self { - match block { - #from_registry_block_to_blockstate_match - _ => unreachable!("There should always be a block state for every azalea_registry::Block variant") + impl From for BlockState { + fn from(block: azalea_registry::Block) -> Self { + match block { + #from_registry_block_to_blockstate_match + _ => unreachable!("There should always be a block state for every azalea_registry::Block variant") + } + } + } + impl From for BlockStates { + fn from(block: azalea_registry::Block) -> Self { + match block { + #from_registry_block_to_blockstates_match + _ => unreachable!("There should always be a block state for every azalea_registry::Block variant") + } } } } diff --git a/azalea-block/src/blocks.rs b/azalea-block/src/generated.rs similarity index 99% rename from azalea-block/src/blocks.rs rename to azalea-block/src/generated.rs index e6923d59d..afe6dfdaf 100755 --- a/azalea-block/src/blocks.rs +++ b/azalea-block/src/generated.rs @@ -1,20 +1,7 @@ -use std::any::Any; - -use crate::BlockBehavior; +use crate::{Block, BlockBehavior, BlockState, BlockStates}; use azalea_block_macros::make_block_states; use std::fmt::Debug; -pub trait Block: Debug + Any { - fn behavior(&self) -> BlockBehavior; - fn id(&self) -> &'static str; - fn as_blockstate(&self) -> BlockState; -} -impl dyn Block { - pub fn downcast_ref(&self) -> Option<&T> { - (self as &dyn Any).downcast_ref::() - } -} - make_block_states! { Properties => { "snowy" => bool, diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index 7a62e5883..43099db58 100755 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -2,14 +2,49 @@ #![feature(trait_upcasting)] mod behavior; -mod blocks; +mod generated; +mod range; + +pub use generated::{blocks, properties}; use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable}; pub use behavior::BlockBehavior; -pub use blocks::*; -use std::io::{Cursor, Write}; +use core::fmt::Debug; +pub use range::BlockStates; +use std::{ + any::Any, + io::{Cursor, Write}, +}; + +pub trait Block: Debug + Any { + fn behavior(&self) -> BlockBehavior; + /// Get the Minecraft ID for this block. For example `stone` or + /// `grass_block`. + fn id(&self) -> &'static str; + /// Convert the block to a block state. This is lossless, as the block + /// contains all the state data. + fn as_block_state(&self) -> BlockState; +} +impl dyn Block { + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref::() + } +} + +/// A representation of a state a block can be in. +/// +/// For example, a stone block only has one state but each possible stair +/// rotation is a different state. +#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)] +pub struct BlockState { + /// The protocol ID for the block state. IDs may change every + /// version, so you shouldn't hard-code them or store them in databases. + pub id: u32, +} impl BlockState { + pub const AIR: BlockState = BlockState { id: 0 }; + /// Transmutes a u32 to a block state. /// /// # Safety @@ -52,6 +87,17 @@ impl McBufWritable for BlockState { } } +impl std::fmt::Debug for BlockState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BlockState(id: {}, {:?})", + self.id, + Box::::from(*self) + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -80,18 +126,14 @@ mod tests { "{:?}", BlockState::from(azalea_registry::Block::FloweringAzalea) ); - assert!( - formatted.ends_with(", FloweringAzaleaBlock)"), - "{}", - formatted - ); + assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted); let formatted = format!( "{:?}", BlockState::from(azalea_registry::Block::BigDripleafStem) ); assert!( - formatted.ends_with(", BigDripleafStemBlock { facing: North, waterlogged: false })"), + formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"), "{}", formatted ); diff --git a/azalea-block/src/range.rs b/azalea-block/src/range.rs new file mode 100644 index 000000000..6ccf4152e --- /dev/null +++ b/azalea-block/src/range.rs @@ -0,0 +1,33 @@ +use std::{collections::HashSet, ops::RangeInclusive}; + +use crate::BlockState; + +#[derive(Debug, Clone)] +pub struct BlockStates { + pub set: HashSet, +} + +impl From> for BlockStates { + fn from(range: RangeInclusive) -> Self { + let mut set = HashSet::with_capacity((range.end() - range.start() + 1) as usize); + for id in range { + set.insert(BlockState { id }); + } + Self { set } + } +} + +impl IntoIterator for BlockStates { + type Item = BlockState; + type IntoIter = std::collections::hash_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.set.into_iter() + } +} + +impl BlockStates { + pub fn contains(&self, state: &BlockState) -> bool { + self.set.contains(state) + } +} diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 5ba7143ed..3c452d3af 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -12,6 +12,8 @@ macro_rules! vec3_impl { Self { x, y, z } } + /// Get the distance of this vector to the origin by doing `x^2 + y^2 + + /// z^2`. pub fn length_sqr(&self) -> $type { self.x * self.x + self.y * self.y + self.z * self.z } @@ -139,6 +141,11 @@ impl BlockPos { z: self.z as f64 + 0.5, } } + + /// Get the distance of this vector from the origin by doing `x + y + z`. + pub fn length_manhattan(&self) -> u32 { + (self.x.abs() + self.y.abs() + self.z.abs()) as u32 + } } /// Chunk coordinates are used to represent where a chunk is in the world. You @@ -148,12 +155,21 @@ pub struct ChunkPos { pub x: i32, pub z: i32, } - impl ChunkPos { pub fn new(x: i32, z: i32) -> Self { ChunkPos { x, z } } } +impl Add for ChunkPos { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + z: self.z + rhs.z, + } + } +} /// The coordinates of a chunk section in the world. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index e39540612..1736b8fb8 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -467,8 +467,8 @@ mod tests { .id(); let block_state = partial_world.chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, - azalea_block::StoneSlabBlock { - kind: azalea_block::Type::Bottom, + azalea_block::blocks::StoneSlab { + kind: azalea_block::properties::Type::Bottom, waterlogged: false, } .into(), @@ -521,8 +521,8 @@ mod tests { .id(); let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, - azalea_block::StoneSlabBlock { - kind: azalea_block::Type::Top, + azalea_block::blocks::StoneSlab { + kind: azalea_block::properties::Type::Top, waterlogged: false, } .into(), @@ -574,11 +574,11 @@ mod tests { .id(); let block_state = world_lock.write().chunks.set_block_state( &BlockPos { x: 0, y: 69, z: 0 }, - azalea_block::CobblestoneWallBlock { - east: azalea_block::EastWall::Low, - north: azalea_block::NorthWall::Low, - south: azalea_block::SouthWall::Low, - west: azalea_block::WestWall::Low, + azalea_block::blocks::CobblestoneWall { + east: azalea_block::properties::EastWall::Low, + north: azalea_block::properties::NorthWall::Low, + south: azalea_block::properties::SouthWall::Low, + west: azalea_block::properties::WestWall::Low, up: false, waterlogged: false, } @@ -636,11 +636,11 @@ mod tests { y: 69, z: -8, }, - azalea_block::CobblestoneWallBlock { - east: azalea_block::EastWall::Low, - north: azalea_block::NorthWall::Low, - south: azalea_block::SouthWall::Low, - west: azalea_block::WestWall::Low, + azalea_block::blocks::CobblestoneWall { + east: azalea_block::properties::EastWall::Low, + north: azalea_block::properties::NorthWall::Low, + south: azalea_block::properties::SouthWall::Low, + west: azalea_block::properties::WestWall::Low, up: false, waterlogged: false, } diff --git a/azalea-protocol/src/packets/game/clientbound_chunks_biomes_packet.rs b/azalea-protocol/src/packets/game/clientbound_chunks_biomes_packet.rs new file mode 100644 index 000000000..9ad242a4a --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_chunks_biomes_packet.rs @@ -0,0 +1,7 @@ +use azalea_protocol_macros::ClientboundGamePacket; +use azalea_buf::McBuf; + +#[derive(Clone, Debug, McBuf, ClientboundGamePacket)] +pub struct ClientboundChunksBiomesPacket { +pub chunk_biome_data: todo!(), +} \ No newline at end of file diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index f6ca4cd67..09b68fae9 100755 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -158,13 +158,13 @@ impl BitStorage { .unwrap() } + /// Get the data at the given index. + /// + /// # Panics + /// + /// This function will panic if the given index is greater than or equal to + /// the size of this storage. pub fn get(&self, index: usize) -> u64 { - // Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1); - // int var2 = this.cellIndex(var1); - // long var3 = this.data[var2]; - // int var5 = (var1 - var2 * this.valuesPerLong) * this.bits; - // return (int)(var3 >> var5 & this.mask); - assert!( index < self.size, "Index {} out of bounds (must be less than {})", diff --git a/azalea-world/src/iterators.rs b/azalea-world/src/iterators.rs new file mode 100644 index 000000000..53a94898c --- /dev/null +++ b/azalea-world/src/iterators.rs @@ -0,0 +1,247 @@ +//! Iterators for iterating over Minecraft blocks and chunks, based on +//! [prismarine-world's iterators](https://github.com/PrismarineJS/prismarine-world/blob/master/src/iterators.js). + +use azalea_core::{BlockPos, ChunkPos}; + +/// An octahedron iterator, useful for iterating over blocks in a world. +/// +/// ``` +/// # use azalea_core::BlockPos; +/// # use azalea_world::iterators::BlockIterator; +/// +/// let mut iter = BlockIterator::new(BlockPos::default(), 4); +/// for block_pos in iter { +/// println!("{:?}", block_pos); +/// } +/// ``` +pub struct BlockIterator { + start: BlockPos, + max_distance: u32, + + pos: BlockPos, + apothem: u32, + left: i32, + right: i32, +} +impl BlockIterator { + pub fn new(start: BlockPos, max_distance: u32) -> Self { + Self { + start, + max_distance, + + pos: BlockPos { + x: -1, + y: -1, + z: -1, + }, + apothem: 1, + left: 1, + right: 2, + } + } +} + +impl Iterator for BlockIterator { + type Item = BlockPos; + + fn next(&mut self) -> Option { + if self.apothem > self.max_distance { + return None; + } + + self.right -= 1; + if self.right < 0 { + self.left -= 1; + if self.left < 0 { + self.pos.z += 2; + if self.pos.z > 1 { + self.pos.y += 2; + if self.pos.y > 1 { + self.pos.x += 2; + if self.pos.x > 1 { + self.apothem += 1; + self.pos.x = -1; + } + self.pos.y = -1; + } + self.pos.z = -1; + } + self.left = self.apothem as i32; + } + self.right = self.left; + } + let x = self.pos.x * self.right; + let y = self.pos.y * ((self.apothem as i32) - self.left); + let z = self.pos.z * ((self.apothem as i32) - (i32::abs(x) + i32::abs(y))); + Some(BlockPos { x: x, y, z } + self.start) + } +} + +/// A spiral iterator, useful for iterating over chunks in a world. Use +/// `ChunkIterator` to sort by x+y+z (Manhattan) distance. +/// +/// ``` +/// # use azalea_core::ChunkPos; +/// # use azalea_world::iterators::SquareChunkIterator; +/// +/// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 4); +/// for chunk_pos in iter { +/// println!("{:?}", chunk_pos); +/// } +/// ``` +pub struct SquareChunkIterator { + start: ChunkPos, + number_of_points: u32, + + dir: ChunkPos, + + segment_len: u32, + pos: ChunkPos, + segment_passed: u32, + current_iter: u32, +} +impl SquareChunkIterator { + pub fn new(start: ChunkPos, max_distance: u32) -> Self { + Self { + start, + number_of_points: u32::pow(max_distance * 2 - 1, 2), + + dir: ChunkPos { x: 1, z: 0 }, + + segment_len: 1, + pos: ChunkPos::default(), + segment_passed: 0, + current_iter: 0, + } + } + + /// Change the distance that this iterator won't go past. + /// + /// ``` + /// # use azalea_core::ChunkPos; + /// # use azalea_world::iterators::SquareChunkIterator; + /// + /// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 2); + /// while let Some(chunk_pos) = iter.next() { + /// println!("{:?}", chunk_pos); + /// } + /// iter.set_max_distance(4); + /// while let Some(chunk_pos) = iter.next() { + /// println!("{:?}", chunk_pos); + /// } + /// ``` + pub fn set_max_distance(&mut self, max_distance: u32) { + self.number_of_points = u32::pow(max_distance * 2 - 1, 2); + } +} +impl Iterator for SquareChunkIterator { + type Item = ChunkPos; + + fn next(&mut self) -> Option { + if self.current_iter > self.number_of_points { + return None; + } + + let output = self.start + self.dir; + + // make a step, add the direction to the current position + self.pos.x += self.dir.x; + self.pos.z += self.dir.z; + self.segment_passed += 1; + + if self.segment_passed == self.segment_len { + // done with current segment + self.segment_passed = 0; + + // rotate directions + (self.dir.x, self.dir.z) = (-self.dir.z, self.dir.x); + + // increase segment length if necessary + if self.dir.z == 0 { + self.segment_len += 1; + } + } + self.current_iter += 1; + Some(output) + } +} + +/// A diagonal spiral iterator, useful for iterating over chunks in a world. +/// +/// ``` +/// # use azalea_core::ChunkPos; +/// # use azalea_world::iterators::ChunkIterator; +/// +/// let mut iter = ChunkIterator::new(ChunkPos::default(), 4); +/// for chunk_pos in iter { +/// println!("{:?}", chunk_pos); +/// } +/// ``` +pub struct ChunkIterator { + pub max_distance: u32, + pub start: ChunkPos, + pub pos: ChunkPos, + pub layer: i32, + pub leg: i32, +} +impl ChunkIterator { + pub fn new(start: ChunkPos, max_distance: u32) -> Self { + Self { + max_distance, + start, + pos: ChunkPos { x: 2, z: -1 }, + layer: 1, + leg: -1, + } + } +} +impl Iterator for ChunkIterator { + type Item = ChunkPos; + + fn next(&mut self) -> Option { + match self.leg { + -1 => { + self.leg = 0; + return Some(self.start); + } + 0 => { + if self.max_distance == 1 { + return None; + } + self.pos.x -= 1; + self.pos.z += 1; + if self.pos.x == 0 { + self.leg = 1; + } + } + 1 => { + self.pos.x -= 1; + self.pos.z -= 1; + if self.pos.z == 0 { + self.leg = 2; + } + } + 2 => { + self.pos.x += 1; + self.pos.z -= 1; + if self.pos.x == 0 { + self.leg = 3; + } + } + 3 => { + self.pos.x += 1; + self.pos.z += 1; + if self.pos.z == 0 { + self.pos.x += 1; + self.leg = 0; + self.layer += 1; + if self.layer == self.max_distance as i32 { + return None; + } + } + } + _ => unreachable!(), + } + Some(self.start + self.pos) + } +} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 1a419c3ac..77498efd4 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -7,6 +7,7 @@ mod bit_storage; mod chunk_storage; mod container; pub mod entity; +pub mod iterators; pub mod palette; mod world; diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index d97f61a35..d10357ad3 100755 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -12,6 +12,11 @@ pub enum PalettedContainerType { #[derive(Clone, Debug)] pub struct PalettedContainer { pub bits_per_entry: u8, + /// This is usually a list of unique values that appear in the container so + /// they can be indexed by the bit storage. + /// + /// Sometimes it doesn't contain anything if there's too many unique items + /// in the bit storage, though. pub palette: Palette, /// Compacted list of indices pointing to entry IDs in the Palette. pub storage: BitStorage, @@ -37,7 +42,7 @@ impl PalettedContainer { container_type: &'static PalettedContainerType, ) -> Result { let bits_per_entry = u8::read_from(buf)?; - let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type); + let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type); let palette = palette_type.read(buf)?; let size = container_type.size(); @@ -57,15 +62,33 @@ impl PalettedContainer { } /// Calculates the index of the given coordinates. - pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize { + pub fn index_from_coords(&self, x: usize, y: usize, z: usize) -> usize { let size_bits = self.container_type.size_bits(); (((y << size_bits) | z) << size_bits) | x } + pub fn coords_from_index(&self, index: usize) -> (usize, usize, usize) { + let size_bits = self.container_type.size_bits(); + let mask = (1 << size_bits) - 1; + ( + index & mask, + (index >> size_bits >> size_bits) & mask, + (index >> size_bits) & mask, + ) + } + /// Returns the value at the given index. + /// + /// # Panics + /// + /// This function panics if the index is greater than or equal to the number + /// of things in the storage. (So for block states, it must be less than + /// 4096). pub fn get_at_index(&self, index: usize) -> u32 { + // first get the pallete id let paletted_value = self.storage.get(index); + // and then get the value from that id self.palette.value_for(paletted_value as usize) } @@ -73,14 +96,14 @@ impl PalettedContainer { pub fn get(&self, x: usize, y: usize, z: usize) -> u32 { // let paletted_value = self.storage.get(self.get_index(x, y, z)); // self.palette.value_for(paletted_value as usize) - self.get_at_index(self.get_index(x, y, z)) + self.get_at_index(self.index_from_coords(x, y, z)) } /// Sets the id at the given coordinates and return the previous id pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: u32) -> u32 { let paletted_value = self.id_for(value); self.storage - .get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32 + .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64) as u32 } /// Sets the id at the given index and return the previous id. You probably @@ -92,12 +115,12 @@ impl PalettedContainer { /// Sets the id at the given coordinates and return the previous id pub fn set(&mut self, x: usize, y: usize, z: usize, value: u32) { - self.set_at_index(self.get_index(x, y, z), value); + self.set_at_index(self.index_from_coords(x, y, z), value); } fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { let new_palette_type = - PaletteType::from_bits_and_type(bits_per_entry, &self.container_type); + PaletteKind::from_bits_and_type(bits_per_entry, &self.container_type); // note for whoever is trying to optimize this: vanilla has this // but it causes a stack overflow since it's not changing the bits per entry // i don't know how to fix this properly so glhf @@ -188,13 +211,14 @@ impl McBufWritable for PalettedContainer { } #[derive(Clone, Debug, PartialEq, Eq)] -pub enum PaletteType { +pub enum PaletteKind { SingleValue, Linear, Hashmap, Global, } +/// A representation of the different types of chunk palettes Minecraft uses. #[derive(Clone, Debug)] pub enum Palette { /// ID of the corresponding entry in its global palette @@ -211,13 +235,7 @@ impl Palette { match self { Palette::SingleValue(v) => *v, Palette::Linear(v) => v[id], - Palette::Hashmap(v) => { - if id >= v.len() { - 0 - } else { - v[id] - } - } + Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), Palette::Global => id as u32, } } @@ -241,49 +259,49 @@ impl McBufWritable for Palette { } } -impl PaletteType { +impl PaletteKind { pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self { match container_type { PalettedContainerType::BlockStates => match bits_per_entry { - 0 => PaletteType::SingleValue, - 1..=4 => PaletteType::Linear, - 5..=8 => PaletteType::Hashmap, - _ => PaletteType::Global, + 0 => PaletteKind::SingleValue, + 1..=4 => PaletteKind::Linear, + 5..=8 => PaletteKind::Hashmap, + _ => PaletteKind::Global, }, PalettedContainerType::Biomes => match bits_per_entry { - 0 => PaletteType::SingleValue, - 1..=3 => PaletteType::Linear, - _ => PaletteType::Global, + 0 => PaletteKind::SingleValue, + 1..=3 => PaletteKind::Linear, + _ => PaletteKind::Global, }, } } pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result { Ok(match self { - PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?), - PaletteType::Linear => Palette::Linear(Vec::::var_read_from(buf)?), - PaletteType::Hashmap => Palette::Hashmap(Vec::::var_read_from(buf)?), - PaletteType::Global => Palette::Global, + PaletteKind::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?), + PaletteKind::Linear => Palette::Linear(Vec::::var_read_from(buf)?), + PaletteKind::Hashmap => Palette::Hashmap(Vec::::var_read_from(buf)?), + PaletteKind::Global => Palette::Global, }) } pub fn as_empty_palette(&self) -> Palette { match self { - PaletteType::SingleValue => Palette::SingleValue(0), - PaletteType::Linear => Palette::Linear(Vec::new()), - PaletteType::Hashmap => Palette::Hashmap(Vec::new()), - PaletteType::Global => Palette::Global, + PaletteKind::SingleValue => Palette::SingleValue(0), + PaletteKind::Linear => Palette::Linear(Vec::new()), + PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), + PaletteKind::Global => Palette::Global, } } } -impl From<&Palette> for PaletteType { +impl From<&Palette> for PaletteKind { fn from(palette: &Palette) -> Self { match palette { - Palette::SingleValue(_) => PaletteType::SingleValue, - Palette::Linear(_) => PaletteType::Linear, - Palette::Hashmap(_) => PaletteType::Hashmap, - Palette::Global => PaletteType::Global, + Palette::SingleValue(_) => PaletteKind::SingleValue, + Palette::Linear(_) => PaletteKind::Linear, + Palette::Hashmap(_) => PaletteKind::Hashmap, + Palette::Global => PaletteKind::Global, } } } @@ -313,14 +331,14 @@ mod tests { assert_eq!(palette_container.bits_per_entry, 0); assert_eq!(palette_container.get_at_index(0), 0); assert_eq!( - PaletteType::from(&palette_container.palette), - PaletteType::SingleValue + PaletteKind::from(&palette_container.palette), + PaletteKind::SingleValue ); palette_container.set_at_index(0, 1); assert_eq!(palette_container.get_at_index(0), 1); assert_eq!( - PaletteType::from(&palette_container.palette), - PaletteType::Linear + PaletteKind::from(&palette_container.palette), + PaletteKind::Linear ); } @@ -359,4 +377,22 @@ mod tests { palette_container.set_at_index(16, 16); // 5 bits assert_eq!(palette_container.bits_per_entry, 5); } + + #[test] + fn test_coords_from_index() { + let palette_container = + PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(); + + for x in 0..15 { + for y in 0..15 { + for z in 0..15 { + assert_eq!( + palette_container + .coords_from_index(palette_container.index_from_coords(x, y, z)), + (x, y, z) + ); + } + } + } + } } diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 41d830822..5bb9b0b77 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -2,9 +2,12 @@ use crate::{ entity::{ EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName, }, + iterators::ChunkIterator, + palette::Palette, ChunkStorage, PartialChunkStorage, WorldContainer, }; -use azalea_core::ChunkPos; +use azalea_block::{BlockState, BlockStates}; +use azalea_core::{BlockPos, ChunkPos}; use bevy_ecs::{ entity::Entity, query::{Changed, With, Without}, @@ -187,6 +190,76 @@ impl Instance { pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option { self.entity_by_id.get(entity_id).copied() } + + /// Find the coordinates of a block in the world. + /// + /// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2`, for + /// optimization purposes. + pub fn find_block( + &self, + nearest_to: impl Into, + block_states: &BlockStates, + ) -> Option { + // iterate over every chunk in a 3d spiral pattern + // and then check the palette for the block state + + let nearest_to: BlockPos = nearest_to.into(); + let start_chunk: ChunkPos = (&nearest_to).into(); + let iter = ChunkIterator::new(start_chunk, 32); + + for chunk_pos in iter { + let chunk = self.chunks.get(&chunk_pos).unwrap(); + + let mut nearest_found_pos: Option = None; + let mut nearest_found_distance = 0; + + for (section_index, section) in chunk.read().sections.iter().enumerate() { + let maybe_has_block = match §ion.states.palette { + Palette::SingleValue(id) => block_states.contains(&BlockState { id: *id }), + Palette::Linear(ids) => ids + .iter() + .any(|&id| block_states.contains(&BlockState { id })), + Palette::Hashmap(ids) => ids + .iter() + .any(|&id| block_states.contains(&BlockState { id })), + Palette::Global => true, + }; + if !maybe_has_block { + continue; + } + + for i in 0..4096 { + let block_state = section.states.get_at_index(i); + let block_state = BlockState { id: block_state }; + + if block_states.contains(&block_state) { + let (section_x, section_y, section_z) = section.states.coords_from_index(i); + let (x, y, z) = ( + chunk_pos.x * 16 + (section_x as i32), + self.chunks.min_y + (section_index * 16) as i32 + section_y as i32, + chunk_pos.z * 16 + (section_z as i32), + ); + let this_block_pos = BlockPos { x, y, z }; + let this_block_distance = (nearest_to - this_block_pos).length_manhattan(); + // only update if it's closer + if !nearest_found_pos.is_some() + || this_block_distance < nearest_found_distance + { + nearest_found_pos = Some(this_block_pos); + nearest_found_distance = this_block_distance; + } + } + } + } + + // if we found the position, return it + if nearest_found_pos.is_some() { + return nearest_found_pos; + } + } + + None + } } impl Debug for PartialWorld { diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 7b7b32b08..a25b28e3d 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -52,17 +52,17 @@ async fn main() -> anyhow::Result<()> { } loop { - // let e = SwarmBuilder::new() - // .add_accounts(accounts.clone()) - // .set_handler(handle) - // .set_swarm_handler(swarm_handle) - // .join_delay(Duration::from_millis(1000)) - // .start("localhost") - // .await; - let e = azalea::ClientBuilder::new() + let e = SwarmBuilder::new() + .add_accounts(accounts.clone()) .set_handler(handle) - .start(Account::offline("bot"), "localhost") + .set_swarm_handler(swarm_handle) + .join_delay(Duration::from_millis(1000)) + .start("localhost") .await; + // let e = azalea::ClientBuilder::new() + // .set_handler(handle) + // .start(Account::offline("bot"), "localhost") + // .await; eprintln!("{e:?}"); } } @@ -140,6 +140,25 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< "lag" => { std::thread::sleep(Duration::from_millis(1000)); } + "findblock" => { + let target_pos = bot.world().read().find_block( + bot.component::(), + &azalea_registry::Block::DiamondBlock.into(), + ); + bot.chat(&format!("target_pos: {target_pos:?}",)); + } + "gotoblock" => { + let target_pos = bot.world().read().find_block( + bot.component::(), + &azalea_registry::Block::DiamondBlock.into(), + ); + if let Some(target_pos) = target_pos { + // +1 to stand on top of the block + bot.goto(BlockPosGoal::from(target_pos.up(1))); + } else { + bot.chat("no diamond block found"); + } + } _ => {} } } diff --git a/azalea/examples/todo/README.md b/azalea/examples/todo/README.md new file mode 100644 index 000000000..ab31cf22d --- /dev/null +++ b/azalea/examples/todo/README.md @@ -0,0 +1 @@ +These examples don't work yet and were only written to help design APIs. They will work in the future (probably with minor changes). diff --git a/azalea/examples/craft_dig_straight_down.rs b/azalea/examples/todo/craft_dig_straight_down.rs old mode 100755 new mode 100644 similarity index 90% rename from azalea/examples/craft_dig_straight_down.rs rename to azalea/examples/todo/craft_dig_straight_down.rs index 0632776e2..4c980ccff --- a/azalea/examples/craft_dig_straight_down.rs +++ b/azalea/examples/todo/craft_dig_straight_down.rs @@ -38,7 +38,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))) .await; let chest = bot - .open_container(&bot.world().find_one_block(|b| b.id == "minecraft:chest")) + .open_container(&bot.world().find_block(azalea_registry::Block::Chest)) .await .unwrap(); bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks") @@ -47,8 +47,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { let crafting_table = bot .open_crafting_table( - &bot.world - .find_one_block(|b| b.id == "minecraft:crafting_table"), + &bot.world.find_block(azalea_registry::Block::CraftingTable), ) .await .unwrap(); diff --git a/azalea/examples/mine_a_chunk.rs b/azalea/examples/todo/mine_a_chunk.rs similarity index 100% rename from azalea/examples/mine_a_chunk.rs rename to azalea/examples/todo/mine_a_chunk.rs diff --git a/azalea/examples/pvp.rs b/azalea/examples/todo/pvp.rs old mode 100755 new mode 100644 similarity index 100% rename from azalea/examples/pvp.rs rename to azalea/examples/todo/pvp.rs