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:
parent
719379a8a7
commit
5dd35c7ed8
18 changed files with 618 additions and 155 deletions
|
@ -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>() {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -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,38 +597,50 @@ 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! {
|
||||||
#block_structs
|
pub mod blocks {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
impl From<BlockState> for Box<dyn Block> {
|
#block_structs
|
||||||
fn from(block_state: BlockState) -> Self {
|
|
||||||
let b = block_state.id;
|
impl From<BlockState> for Box<dyn Block> {
|
||||||
match b {
|
fn from(block_state: BlockState) -> Self {
|
||||||
#from_state_to_block_match
|
let b = block_state.id;
|
||||||
_ => panic!("Invalid block state: {}", b),
|
match b {
|
||||||
|
#from_state_to_block_match
|
||||||
|
_ => panic!("Invalid block state: {}", b),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
impl From<azalea_registry::Block> for Box<dyn Block> {
|
||||||
impl From<azalea_registry::Block> for Box<dyn Block> {
|
fn from(block: azalea_registry::Block) -> Self {
|
||||||
fn from(block: azalea_registry::Block) -> Self {
|
match block {
|
||||||
match block {
|
#from_registry_block_to_block_match
|
||||||
#from_registry_block_to_block_match
|
_ => unreachable!("There should always be a block struct for every azalea_registry::Block variant")
|
||||||
_ => unreachable!("There should always be a block struct for every azalea_registry::Block variant")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
impl From<azalea_registry::Block> for BlockState {
|
||||||
impl From<azalea_registry::Block> for BlockState {
|
fn from(block: azalea_registry::Block) -> Self {
|
||||||
fn from(block: azalea_registry::Block) -> Self {
|
match block {
|
||||||
match block {
|
#from_registry_block_to_blockstate_match
|
||||||
#from_registry_block_to_blockstate_match
|
_ => unreachable!("There should always be a block state for every azalea_registry::Block variant")
|
||||||
_ => 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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
@ -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
33
azalea-block/src/range.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(),
|
||||||
|
}
|
|
@ -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 {})",
|
||||||
|
|
247
azalea-world/src/iterators.rs
Normal file
247
azalea-world/src/iterators.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 §ion.states.palette {
|
||||||
|
Palette::SingleValue(id) => block_states.contains(&BlockState { id: *id }),
|
||||||
|
Palette::Linear(ids) => ids
|
||||||
|
.iter()
|
||||||
|
.any(|&id| block_states.contains(&BlockState { id })),
|
||||||
|
Palette::Hashmap(ids) => ids
|
||||||
|
.iter()
|
||||||
|
.any(|&id| block_states.contains(&BlockState { id })),
|
||||||
|
Palette::Global => true,
|
||||||
|
};
|
||||||
|
if !maybe_has_block {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..4096 {
|
||||||
|
let block_state = section.states.get_at_index(i);
|
||||||
|
let block_state = BlockState { id: block_state };
|
||||||
|
|
||||||
|
if block_states.contains(&block_state) {
|
||||||
|
let (section_x, section_y, section_z) = section.states.coords_from_index(i);
|
||||||
|
let (x, y, z) = (
|
||||||
|
chunk_pos.x * 16 + (section_x as i32),
|
||||||
|
self.chunks.min_y + (section_index * 16) as i32 + section_y as i32,
|
||||||
|
chunk_pos.z * 16 + (section_z as i32),
|
||||||
|
);
|
||||||
|
let this_block_pos = BlockPos { x, y, z };
|
||||||
|
let this_block_distance = (nearest_to - this_block_pos).length_manhattan();
|
||||||
|
// only update if it's closer
|
||||||
|
if !nearest_found_pos.is_some()
|
||||||
|
|| this_block_distance < nearest_found_distance
|
||||||
|
{
|
||||||
|
nearest_found_pos = Some(this_block_pos);
|
||||||
|
nearest_found_distance = this_block_distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we found the position, return it
|
||||||
|
if nearest_found_pos.is_some() {
|
||||||
|
return nearest_found_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PartialWorld {
|
impl Debug for PartialWorld {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
azalea/examples/todo/README.md
Normal file
1
azalea/examples/todo/README.md
Normal 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).
|
5
azalea/examples/craft_dig_straight_down.rs → azalea/examples/todo/craft_dig_straight_down.rs
Executable file → Normal file
5
azalea/examples/craft_dig_straight_down.rs → azalea/examples/todo/craft_dig_straight_down.rs
Executable file → Normal 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
0
azalea/examples/pvp.rs → azalea/examples/todo/pvp.rs
Executable file → Normal file
Loading…
Add table
Reference in a new issue