1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

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
This commit is contained in:
mat 2023-03-07 22:09:56 -06:00 committed by GitHub
parent 719379a8a7
commit 5dd35c7ed8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 618 additions and 155 deletions

View file

@ -8,11 +8,11 @@ There's three block types, used for different things. You can (mostly) convert b
``` ```
# use azalea_block::BlockState; # use azalea_block::BlockState;
let block_state: BlockState = azalea_block::CobblestoneWallBlock { let block_state: BlockState = azalea_block::blocks::CobblestoneWall {
east: azalea_block::EastWall::Low, east: azalea_block::properties::EastWall::Low,
north: azalea_block::NorthWall::Low, north: azalea_block::properties::NorthWall::Low,
south: azalea_block::SouthWall::Low, south: azalea_block::properties::SouthWall::Low,
west: azalea_block::WestWall::Low, west: azalea_block::properties::WestWall::Low,
up: false, up: false,
waterlogged: false, waterlogged: false,
} }
@ -36,7 +36,7 @@ let block = Box::<dyn Block>::from(block_state);
``` ```
# use azalea_block::{Block, BlockState}; # use azalea_block::{Block, BlockState};
# let block_state: BlockState = azalea_registry::Block::Jukebox.into(); # 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>() {
// ... // ...
} }
``` ```

View file

@ -38,7 +38,7 @@ struct PropertyDefinitions {
properties: Vec<PropertyDefinition>, properties: Vec<PropertyDefinition>,
} }
/// `snowy: false` or `axis: Axis::Y` /// `snowy: false` or `axis: properties::Axis::Y`
#[derive(Debug)] #[derive(Debug)]
struct PropertyWithNameAndDefault { struct PropertyWithNameAndDefault {
name: Ident, name: Ident,
@ -59,7 +59,7 @@ struct BlockDefinition {
} }
impl Parse for PropertyWithNameAndDefault { impl Parse for PropertyWithNameAndDefault {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
// `snowy: false` or `axis: Axis::Y` // `snowy: false` or `axis: properties::Axis::Y`
let property_name = input.parse()?; let property_name = input.parse()?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;
@ -74,7 +74,7 @@ impl Parse for PropertyWithNameAndDefault {
is_enum = true; is_enum = true;
property_type = first_ident; property_type = first_ident;
let variant = input.parse::<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" { } else if first_ident_string == "true" || first_ident_string == "false" {
property_type = Ident::new("bool", first_ident.span()); property_type = Ident::new("bool", first_ident.span());
} else { } else {
@ -310,6 +310,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut from_state_to_block_match = quote! {}; let mut from_state_to_block_match = quote! {};
let mut from_registry_block_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_blockstate_match = quote! {};
let mut from_registry_block_to_blockstates_match = quote! {};
for block in &input.block_definitions.blocks { for block in &input.block_definitions.blocks {
let block_property_names = &block let block_property_names = &block
@ -386,13 +387,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
for PropertyWithNameAndDefault { for PropertyWithNameAndDefault {
property_type: struct_name, property_type: struct_name,
name, name,
is_enum,
.. ..
} in &properties_with_name } in &properties_with_name
{ {
// let property_name_snake = // let property_name_snake =
// Ident::new(&property.to_string(), proc_macro2::Span::call_site()); // Ident::new(&property.to_string(), proc_macro2::Span::call_site());
block_struct_fields.extend(quote! { block_struct_fields.extend(if *is_enum {
pub #name: #struct_name, 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()), &to_pascal_case(&block.name.to_string()),
proc_macro2::Span::call_site(), proc_macro2::Span::call_site(),
); );
let block_struct_name = Ident::new( let block_struct_name = Ident::new(&block_name_pascal_case.to_string(), proc_macro2::Span::call_site());
&format!("{block_name_pascal_case}Block"),
proc_macro2::Span::call_site(),
);
let mut from_block_to_state_match_inner = quote! {}; 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 { let property_type = if property.is_enum {
quote! {#property_struct_name_ident::#variant} quote! {properties::#property_struct_name_ident::#variant}
} else { } else {
quote! {#variant} quote! {#variant}
}; };
@ -476,9 +477,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// 7035..=7058 => { // 7035..=7058 => {
// let b = b - 7035; // let b = b - 7035;
// &AcaciaButtonBlock { // &AcaciaButtonBlock {
// powered: Powered::from((b / 1) % 2), // powered: properties::Powered::from((b / 1) % 2),
// facing: Facing::from((b / 2) % 4), // facing: properties::Facing::from((b / 2) % 4),
// face: Face::from((b / 8) % 3), // face: properties::Face::from((b / 8) % 3),
// } // }
// } // }
let mut from_state_to_block_inner = quote! {}; 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 // this is not a mistake, it starts with true for some reason
quote! {(b / #division) % #property_variants_count == 0} quote! {(b / #division) % #property_variants_count == 0}
} else { } 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! { 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! { from_registry_block_to_blockstate_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => BlockState { id: #default_state_id }, 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! {}; let mut block_default_fields = quote! {};
for PropertyWithNameAndDefault { for PropertyWithNameAndDefault {
@ -560,14 +564,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
fn id(&self) -> &'static str { fn id(&self) -> &'static str {
#block_id #block_id
} }
fn as_blockstate(&self) -> BlockState { fn as_block_state(&self) -> BlockState {
#from_block_to_state_match #from_block_to_state_match
} }
} }
impl From<#block_struct_name> for BlockState { impl From<#block_struct_name> for BlockState {
fn from(b: #block_struct_name) -> Self { 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 last_state_id = state_id - 1;
let mut generated = quote! { 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 { impl BlockState {
pub const AIR: BlockState = BlockState { id: 0 };
/// Returns the highest possible state ID. /// Returns the highest possible state ID.
#[inline] #[inline]
pub fn max_state() -> u32 { pub fn max_state() -> u32 {
@ -607,14 +597,17 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
} }
} }
impl std::fmt::Debug for BlockState { pub mod properties {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use super::*;
write!(f, "BlockState(id: {}, {:?})", self.id, Box::<dyn Block>::from(*self))
} #property_enums
} }
}; };
generated.extend(quote! { generated.extend(quote! {
pub mod blocks {
use super::*;
#block_structs #block_structs
impl From<BlockState> for Box<dyn Block> { impl From<BlockState> for Box<dyn Block> {
@ -642,6 +635,15 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
} }
} }
} }
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")
}
}
}
}
}); });
generated.into() generated.into()

View file

@ -1,20 +1,7 @@
use std::any::Any; use crate::{Block, BlockBehavior, BlockState, BlockStates};
use crate::BlockBehavior;
use azalea_block_macros::make_block_states; use azalea_block_macros::make_block_states;
use std::fmt::Debug; 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! { make_block_states! {
Properties => { Properties => {
"snowy" => bool, "snowy" => bool,

View file

@ -2,14 +2,49 @@
#![feature(trait_upcasting)] #![feature(trait_upcasting)]
mod behavior; mod behavior;
mod blocks; mod generated;
mod range;
pub use generated::{blocks, properties};
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable}; use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
pub use behavior::BlockBehavior; pub use behavior::BlockBehavior;
pub use blocks::*; use core::fmt::Debug;
use std::io::{Cursor, Write}; 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 { impl BlockState {
pub const AIR: BlockState = BlockState { id: 0 };
/// Transmutes a u32 to a block state. /// Transmutes a u32 to a block state.
/// ///
/// # Safety /// # 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -80,18 +126,14 @@ mod tests {
"{:?}", "{:?}",
BlockState::from(azalea_registry::Block::FloweringAzalea) BlockState::from(azalea_registry::Block::FloweringAzalea)
); );
assert!( assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
formatted.ends_with(", FloweringAzaleaBlock)"),
"{}",
formatted
);
let formatted = format!( let formatted = format!(
"{:?}", "{:?}",
BlockState::from(azalea_registry::Block::BigDripleafStem) BlockState::from(azalea_registry::Block::BigDripleafStem)
); );
assert!( assert!(
formatted.ends_with(", BigDripleafStemBlock { facing: North, waterlogged: false })"), formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
"{}", "{}",
formatted formatted
); );

33
azalea-block/src/range.rs Normal file
View file

@ -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)
}
}

View file

@ -12,6 +12,8 @@ macro_rules! vec3_impl {
Self { x, y, z } 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 { pub fn length_sqr(&self) -> $type {
self.x * self.x + self.y * self.y + self.z * self.z 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, 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 /// 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 x: i32,
pub z: i32, pub z: i32,
} }
impl ChunkPos { impl ChunkPos {
pub fn new(x: i32, z: i32) -> Self { pub fn new(x: i32, z: i32) -> Self {
ChunkPos { x, z } 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. /// The coordinates of a chunk section in the world.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]

View file

@ -467,8 +467,8 @@ mod tests {
.id(); .id();
let block_state = partial_world.chunks.set_block_state( let block_state = partial_world.chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 }, &BlockPos { x: 0, y: 69, z: 0 },
azalea_block::StoneSlabBlock { azalea_block::blocks::StoneSlab {
kind: azalea_block::Type::Bottom, kind: azalea_block::properties::Type::Bottom,
waterlogged: false, waterlogged: false,
} }
.into(), .into(),
@ -521,8 +521,8 @@ mod tests {
.id(); .id();
let block_state = world_lock.write().chunks.set_block_state( let block_state = world_lock.write().chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 }, &BlockPos { x: 0, y: 69, z: 0 },
azalea_block::StoneSlabBlock { azalea_block::blocks::StoneSlab {
kind: azalea_block::Type::Top, kind: azalea_block::properties::Type::Top,
waterlogged: false, waterlogged: false,
} }
.into(), .into(),
@ -574,11 +574,11 @@ mod tests {
.id(); .id();
let block_state = world_lock.write().chunks.set_block_state( let block_state = world_lock.write().chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 }, &BlockPos { x: 0, y: 69, z: 0 },
azalea_block::CobblestoneWallBlock { azalea_block::blocks::CobblestoneWall {
east: azalea_block::EastWall::Low, east: azalea_block::properties::EastWall::Low,
north: azalea_block::NorthWall::Low, north: azalea_block::properties::NorthWall::Low,
south: azalea_block::SouthWall::Low, south: azalea_block::properties::SouthWall::Low,
west: azalea_block::WestWall::Low, west: azalea_block::properties::WestWall::Low,
up: false, up: false,
waterlogged: false, waterlogged: false,
} }
@ -636,11 +636,11 @@ mod tests {
y: 69, y: 69,
z: -8, z: -8,
}, },
azalea_block::CobblestoneWallBlock { azalea_block::blocks::CobblestoneWall {
east: azalea_block::EastWall::Low, east: azalea_block::properties::EastWall::Low,
north: azalea_block::NorthWall::Low, north: azalea_block::properties::NorthWall::Low,
south: azalea_block::SouthWall::Low, south: azalea_block::properties::SouthWall::Low,
west: azalea_block::WestWall::Low, west: azalea_block::properties::WestWall::Low,
up: false, up: false,
waterlogged: false, waterlogged: false,
} }

View file

@ -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!(),
}

View file

@ -158,13 +158,13 @@ impl BitStorage {
.unwrap() .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 { 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!( assert!(
index < self.size, index < self.size,
"Index {} out of bounds (must be less than {})", "Index {} out of bounds (must be less than {})",

View file

@ -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)
}
}

View file

@ -7,6 +7,7 @@ mod bit_storage;
mod chunk_storage; mod chunk_storage;
mod container; mod container;
pub mod entity; pub mod entity;
pub mod iterators;
pub mod palette; pub mod palette;
mod world; mod world;

View file

@ -12,6 +12,11 @@ pub enum PalettedContainerType {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PalettedContainer { pub struct PalettedContainer {
pub bits_per_entry: u8, 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, pub palette: Palette,
/// Compacted list of indices pointing to entry IDs in the Palette. /// Compacted list of indices pointing to entry IDs in the Palette.
pub storage: BitStorage, pub storage: BitStorage,
@ -37,7 +42,7 @@ impl PalettedContainer {
container_type: &'static PalettedContainerType, container_type: &'static PalettedContainerType,
) -> Result<Self, BufReadError> { ) -> Result<Self, BufReadError> {
let bits_per_entry = u8::read_from(buf)?; 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 palette = palette_type.read(buf)?;
let size = container_type.size(); let size = container_type.size();
@ -57,15 +62,33 @@ impl PalettedContainer {
} }
/// Calculates the index of the given coordinates. /// 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(); let size_bits = self.container_type.size_bits();
(((y << size_bits) | z) << size_bits) | x (((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. /// 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 { pub fn get_at_index(&self, index: usize) -> u32 {
// first get the pallete id
let paletted_value = self.storage.get(index); let paletted_value = self.storage.get(index);
// and then get the value from that id
self.palette.value_for(paletted_value as usize) 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 { pub fn get(&self, x: usize, y: usize, z: usize) -> u32 {
// let paletted_value = self.storage.get(self.get_index(x, y, z)); // let paletted_value = self.storage.get(self.get_index(x, y, z));
// self.palette.value_for(paletted_value as usize) // 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 /// 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 { pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: u32) -> u32 {
let paletted_value = self.id_for(value); let paletted_value = self.id_for(value);
self.storage 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 /// 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 /// 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) { 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 { fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer {
let new_palette_type = 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 // 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 // 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 // i don't know how to fix this properly so glhf
@ -188,13 +211,14 @@ impl McBufWritable for PalettedContainer {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaletteType { pub enum PaletteKind {
SingleValue, SingleValue,
Linear, Linear,
Hashmap, Hashmap,
Global, Global,
} }
/// A representation of the different types of chunk palettes Minecraft uses.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Palette { pub enum Palette {
/// ID of the corresponding entry in its global palette /// ID of the corresponding entry in its global palette
@ -211,13 +235,7 @@ impl Palette {
match self { match self {
Palette::SingleValue(v) => *v, Palette::SingleValue(v) => *v,
Palette::Linear(v) => v[id], Palette::Linear(v) => v[id],
Palette::Hashmap(v) => { Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(),
if id >= v.len() {
0
} else {
v[id]
}
}
Palette::Global => id as u32, 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 { pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self {
match container_type { match container_type {
PalettedContainerType::BlockStates => match bits_per_entry { PalettedContainerType::BlockStates => match bits_per_entry {
0 => PaletteType::SingleValue, 0 => PaletteKind::SingleValue,
1..=4 => PaletteType::Linear, 1..=4 => PaletteKind::Linear,
5..=8 => PaletteType::Hashmap, 5..=8 => PaletteKind::Hashmap,
_ => PaletteType::Global, _ => PaletteKind::Global,
}, },
PalettedContainerType::Biomes => match bits_per_entry { PalettedContainerType::Biomes => match bits_per_entry {
0 => PaletteType::SingleValue, 0 => PaletteKind::SingleValue,
1..=3 => PaletteType::Linear, 1..=3 => PaletteKind::Linear,
_ => PaletteType::Global, _ => PaletteKind::Global,
}, },
} }
} }
pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result<Palette, BufReadError> { pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result<Palette, BufReadError> {
Ok(match self { Ok(match self {
PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?), PaletteKind::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?), PaletteKind::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?), PaletteKind::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
PaletteType::Global => Palette::Global, PaletteKind::Global => Palette::Global,
}) })
} }
pub fn as_empty_palette(&self) -> Palette { pub fn as_empty_palette(&self) -> Palette {
match self { match self {
PaletteType::SingleValue => Palette::SingleValue(0), PaletteKind::SingleValue => Palette::SingleValue(0),
PaletteType::Linear => Palette::Linear(Vec::new()), PaletteKind::Linear => Palette::Linear(Vec::new()),
PaletteType::Hashmap => Palette::Hashmap(Vec::new()), PaletteKind::Hashmap => Palette::Hashmap(Vec::new()),
PaletteType::Global => Palette::Global, PaletteKind::Global => Palette::Global,
} }
} }
} }
impl From<&Palette> for PaletteType { impl From<&Palette> for PaletteKind {
fn from(palette: &Palette) -> Self { fn from(palette: &Palette) -> Self {
match palette { match palette {
Palette::SingleValue(_) => PaletteType::SingleValue, Palette::SingleValue(_) => PaletteKind::SingleValue,
Palette::Linear(_) => PaletteType::Linear, Palette::Linear(_) => PaletteKind::Linear,
Palette::Hashmap(_) => PaletteType::Hashmap, Palette::Hashmap(_) => PaletteKind::Hashmap,
Palette::Global => PaletteType::Global, Palette::Global => PaletteKind::Global,
} }
} }
} }
@ -313,14 +331,14 @@ mod tests {
assert_eq!(palette_container.bits_per_entry, 0); assert_eq!(palette_container.bits_per_entry, 0);
assert_eq!(palette_container.get_at_index(0), 0); assert_eq!(palette_container.get_at_index(0), 0);
assert_eq!( assert_eq!(
PaletteType::from(&palette_container.palette), PaletteKind::from(&palette_container.palette),
PaletteType::SingleValue PaletteKind::SingleValue
); );
palette_container.set_at_index(0, 1); palette_container.set_at_index(0, 1);
assert_eq!(palette_container.get_at_index(0), 1); assert_eq!(palette_container.get_at_index(0), 1);
assert_eq!( assert_eq!(
PaletteType::from(&palette_container.palette), PaletteKind::from(&palette_container.palette),
PaletteType::Linear PaletteKind::Linear
); );
} }
@ -359,4 +377,22 @@ mod tests {
palette_container.set_at_index(16, 16); // 5 bits palette_container.set_at_index(16, 16); // 5 bits
assert_eq!(palette_container.bits_per_entry, 5); 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)
);
}
}
}
}
} }

View file

@ -2,9 +2,12 @@ use crate::{
entity::{ entity::{
EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName, EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName,
}, },
iterators::ChunkIterator,
palette::Palette,
ChunkStorage, PartialChunkStorage, WorldContainer, ChunkStorage, PartialChunkStorage, WorldContainer,
}; };
use azalea_core::ChunkPos; use azalea_block::{BlockState, BlockStates};
use azalea_core::{BlockPos, ChunkPos};
use bevy_ecs::{ use bevy_ecs::{
entity::Entity, entity::Entity,
query::{Changed, With, Without}, query::{Changed, With, Without},
@ -187,6 +190,76 @@ impl Instance {
pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> { pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> {
self.entity_by_id.get(entity_id).copied() 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 { impl Debug for PartialWorld {

View file

@ -52,17 +52,17 @@ async fn main() -> anyhow::Result<()> {
} }
loop { loop {
// let e = SwarmBuilder::new() let e = SwarmBuilder::new()
// .add_accounts(accounts.clone()) .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()
.set_handler(handle) .set_handler(handle)
.start(Account::offline("bot"), "localhost") .set_swarm_handler(swarm_handle)
.join_delay(Duration::from_millis(1000))
.start("localhost")
.await; .await;
// let e = azalea::ClientBuilder::new()
// .set_handler(handle)
// .start(Account::offline("bot"), "localhost")
// .await;
eprintln!("{e:?}"); eprintln!("{e:?}");
} }
} }
@ -140,6 +140,25 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
"lag" => { "lag" => {
std::thread::sleep(Duration::from_millis(1000)); 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");
}
}
_ => {} _ => {}
} }
} }

View file

@ -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).

View file

@ -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))) bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)))
.await; .await;
let chest = bot 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 .await
.unwrap(); .unwrap();
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks") 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 let crafting_table = bot
.open_crafting_table( .open_crafting_table(
&bot.world &bot.world.find_block(azalea_registry::Block::CraftingTable),
.find_one_block(|b| b.id == "minecraft:crafting_table"),
) )
.await .await
.unwrap(); .unwrap();

0
azalea/examples/pvp.rs → azalea/examples/todo/pvp.rs Executable file → Normal file
View file