aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2023-03-07 22:09:56 -0600
committerGitHub <noreply@github.com>2023-03-07 22:09:56 -0600
commit5dd35c7ed82c38ef36ca28f630e8d05c5db2cbea (patch)
tree72719e46479e7884ea535c768ab7c244ce048063
parent719379a8a76ab0685f2bd14bebe2f0cd1e97f06b (diff)
downloadazalea-drasl-5dd35c7ed82c38ef36ca28f630e8d05c5db2cbea.tar.xz
Add World::find_block (#80)
* start adding World::find_block * keep working on find_block * BlockStates * fix sorting * update examples that use find_one_block * azalea_block::properties * fix tests * add a gotoblock command to testbot
-rwxr-xr-xazalea-block/README.md12
-rwxr-xr-xazalea-block/azalea-block-macros/src/lib.rs110
-rwxr-xr-xazalea-block/src/generated.rs (renamed from azalea-block/src/blocks.rs)15
-rwxr-xr-xazalea-block/src/lib.rs60
-rw-r--r--azalea-block/src/range.rs33
-rwxr-xr-xazalea-core/src/position.rs18
-rw-r--r--azalea-physics/src/lib.rs28
-rw-r--r--azalea-protocol/src/packets/game/clientbound_chunks_biomes_packet.rs7
-rwxr-xr-xazalea-world/src/bit_storage.rs12
-rw-r--r--azalea-world/src/iterators.rs247
-rw-r--r--azalea-world/src/lib.rs1
-rwxr-xr-xazalea-world/src/palette.rs114
-rw-r--r--azalea-world/src/world.rs75
-rw-r--r--azalea/examples/testbot.rs37
-rw-r--r--azalea/examples/todo/README.md1
-rw-r--r--[-rwxr-xr-x]azalea/examples/todo/craft_dig_straight_down.rs (renamed from azalea/examples/craft_dig_straight_down.rs)5
-rw-r--r--azalea/examples/todo/mine_a_chunk.rs (renamed from azalea/examples/mine_a_chunk.rs)0
-rw-r--r--[-rwxr-xr-x]azalea/examples/todo/pvp.rs (renamed from azalea/examples/pvp.rs)0
18 files changed, 619 insertions, 156 deletions
diff --git a/azalea-block/README.md b/azalea-block/README.md
index e4b6357b..9be4c79b 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::<dyn Block>::from(block_state);
```
# use azalea_block::{Block, BlockState};
# let block_state: BlockState = azalea_registry::Block::Jukebox.into();
-if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::JukeboxBlock>() {
+if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::blocks::Jukebox>() {
// ...
}
```
diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs
index b69ebd06..a8739e7c 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<PropertyDefinition>,
}
-/// `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<Self> {
- // `snowy: false` or `axis: Axis::Y`
+ // `snowy: false` or `axis: properties::Axis::Y`
let property_name = input.parse()?;
input.parse::<Token![:]>()?;
@@ -74,7 +74,7 @@ impl Parse for PropertyWithNameAndDefault {
is_enum = true;
property_type = first_ident;
let variant = input.parse::<Ident>()?;
- 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::<dyn Block>::from(*self))
- }
+ pub mod properties {
+ use super::*;
+
+ #property_enums
}
};
generated.extend(quote! {
- #block_structs
-
- impl From<BlockState> for Box<dyn Block> {
- 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<BlockState> for Box<dyn Block> {
+ 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<azalea_registry::Block> for Box<dyn Block> {
- 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<azalea_registry::Block> for Box<dyn Block> {
+ 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<azalea_registry::Block> 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<azalea_registry::Block> 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<azalea_registry::Block> 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
index e6923d59..afe6dfda 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<T: Block>(&self) -> Option<&T> {
- (self as &dyn Any).downcast_ref::<T>()
- }
-}
-
make_block_states! {
Properties => {
"snowy" => bool,
diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs
index 7a62e588..43099db5 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<T: Block>(&self) -> Option<&T> {
+ (self as &dyn Any).downcast_ref::<T>()
+ }
+}
+
+/// 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::<dyn Block>::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 00000000..6ccf4152
--- /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<BlockState>,
+}
+
+impl From<RangeInclusive<u32>> for BlockStates {
+ fn from(range: RangeInclusive<u32>) -> 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<BlockState>;
+
+ 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 5ba7143e..3c452d3a 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<ChunkPos> 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 e3954061..1736b8fb 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 00000000..9ad242a4
--- /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 f6ca4cd6..09b68fae 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 00000000..53a94898
--- /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<Self::Item> {
+ 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<Self::Item> {
+ 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<Self::Item> {
+ 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 1a419c3a..77498efd 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 d97f61a3..d10357ad 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<Self, BufReadError> {
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<Palette, BufReadError> {
Ok(match self {
- PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
- PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
- PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
- PaletteType::Global => Palette::Global,
+ PaletteKind::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
+ PaletteKind::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
+ PaletteKind::Hashmap => Palette::Hashmap(Vec::<u32>::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 41d83082..5bb9b0b7 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<Entity> {
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<BlockPos>,
+ block_states: &BlockStates,
+ ) -> Option<BlockPos> {
+ // 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<BlockPos> = None;
+ let mut nearest_found_distance = 0;
+
+ for (section_index, section) in chunk.read().sections.iter().enumerate() {
+ let maybe_has_block = match &section.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 7b7b32b0..a25b28e3 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::<Position>(),
+ &azalea_registry::Block::DiamondBlock.into(),
+ );
+ bot.chat(&format!("target_pos: {target_pos:?}",));
+ }
+ "gotoblock" => {
+ let target_pos = bot.world().read().find_block(
+ bot.component::<Position>(),
+ &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 00000000..ab31cf22
--- /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
index 0632776e..4c980ccf 100755..100644
--- 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
index 74ffacac..74ffacac 100644
--- a/azalea/examples/mine_a_chunk.rs
+++ b/azalea/examples/todo/mine_a_chunk.rs
diff --git a/azalea/examples/pvp.rs b/azalea/examples/todo/pvp.rs
index fb5a768d..fb5a768d 100755..100644
--- a/azalea/examples/pvp.rs
+++ b/azalea/examples/todo/pvp.rs