1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16: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;
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>() {
// ...
}
```

View file

@ -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,14 +597,17 @@ 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! {
pub mod blocks {
use super::*;
#block_structs
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()

View file

@ -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,

View file

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

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 }
}
/// 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)]

View file

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

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()
}
/// 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 {})",

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 container;
pub mod entity;
pub mod iterators;
pub mod palette;
mod world;

View file

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

View file

@ -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 {

View file

@ -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");
}
}
_ => {}
}
}

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)))
.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();

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