diff --git a/Cargo.lock b/Cargo.lock index f328bf2f..4d48f4d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,7 +222,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "azalea" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "anyhow", "azalea-auth", @@ -262,7 +262,7 @@ dependencies = [ [[package]] name = "azalea-auth" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-crypto", @@ -282,7 +282,7 @@ dependencies = [ [[package]] name = "azalea-block" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-block-macros", "azalea-buf", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "azalea-block-macros" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "proc-macro2", "quote", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "azalea-brigadier" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-chat", @@ -311,7 +311,7 @@ dependencies = [ [[package]] name = "azalea-buf" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf-macros", "byteorder", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "azalea-buf-macros" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "proc-macro2", "quote", @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "azalea-chat" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-language", @@ -346,7 +346,7 @@ dependencies = [ [[package]] name = "azalea-client" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "anyhow", "async-compat", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "azalea-core" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-chat", @@ -398,7 +398,7 @@ dependencies = [ [[package]] name = "azalea-crypto" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "aes", "azalea-buf", @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "azalea-entity" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-block", "azalea-buf", @@ -438,7 +438,7 @@ dependencies = [ [[package]] name = "azalea-inventory" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-chat", @@ -453,7 +453,7 @@ dependencies = [ [[package]] name = "azalea-inventory-macros" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "proc-macro2", "quote", @@ -462,7 +462,7 @@ dependencies = [ [[package]] name = "azalea-language" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "compact_str", "serde", @@ -471,7 +471,7 @@ dependencies = [ [[package]] name = "azalea-physics" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-block", "azalea-core", @@ -489,7 +489,7 @@ dependencies = [ [[package]] name = "azalea-protocol" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "anyhow", "async-recursion", @@ -525,7 +525,7 @@ dependencies = [ [[package]] name = "azalea-protocol-macros" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "proc-macro2", "quote", @@ -534,7 +534,7 @@ dependencies = [ [[package]] name = "azalea-registry" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-buf", "azalea-registry-macros", @@ -544,7 +544,7 @@ dependencies = [ [[package]] name = "azalea-registry-macros" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "quote", "syn", @@ -552,7 +552,7 @@ dependencies = [ [[package]] name = "azalea-world" -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" dependencies = [ "azalea-block", "azalea-buf", diff --git a/Cargo.toml b/Cargo.toml index 8b16fe06..18a97422 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ resolver = "2" # --- Workspace Settings --- [workspace.package] -version = "0.11.0+mc1.21.5" +version = "0.12.0+mc1.21.5" edition = "2024" license = "MIT" repository = "https://github.com/azalea-rs/azalea" @@ -81,6 +81,7 @@ indexmap = "2.9.0" paste = "1.0.15" compact_str = "0.9.0" crc32fast = "1.4.2" +async-compat = "0.2.4" # --- Profile Settings --- diff --git a/README.md b/README.md index 1eb2f6fc..9b00d099 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ _Currently supported Minecraft version: `1.21.5`._ - [Block interactions & building](https://azalea.matdoes.dev/azalea/struct.Client.html#method.block_interact) (this doesn't predict the block interactions/placement on the client yet but it's usually fine) - [Inventories](https://azalea.matdoes.dev/azalea/struct.Client.html#impl-ContainerClientExt-for-Client) - [Attacking entities](https://azalea.matdoes.dev/azalea/struct.Client.html#method.attack) (but you can't get the entity at the crosshair yet) +- [Plugins](#plugins) ## Docs diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml index d020cfe1..fb1b37c9 100644 --- a/azalea-auth/Cargo.toml +++ b/azalea-auth/Cargo.toml @@ -7,8 +7,8 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-crypto = { path = "../azalea-crypto", version = "0.11.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" } base64.workspace = true chrono = { workspace = true, features = ["serde"] } md-5.workspace = true diff --git a/azalea-block/Cargo.toml b/azalea-block/Cargo.toml index 7961901c..ad40aeaf 100644 --- a/azalea-block/Cargo.toml +++ b/azalea-block/Cargo.toml @@ -7,6 +7,6 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-block-macros = { path = "./azalea-block-macros", version = "0.11.0" } -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } +azalea-block-macros = { path = "./azalea-block-macros", version = "0.12.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } diff --git a/azalea-block/azalea-block-macros/src/lib.rs b/azalea-block/azalea-block-macros/src/lib.rs index 7952a8d1..f1b5c10f 100644 --- a/azalea-block/azalea-block-macros/src/lib.rs +++ b/azalea-block/azalea-block-macros/src/lib.rs @@ -552,7 +552,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { from_block_to_state_match_inner.extend(quote! { #block_struct_name { #from_block_to_state_combination_match_inner - } => BlockState { id: #state_id }, + } => BlockState::new_const(#state_id), }); if is_default { @@ -626,7 +626,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { azalea_registry::Block::#block_name_pascal_case => Box::new(#block_struct_name::default()), }); 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::new_const(#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), @@ -646,7 +646,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { let block_id = block.name.to_string(); let from_block_to_state_match = if block.properties_and_defaults.is_empty() { - quote! { BlockState { id: #first_state_id } } + quote! { BlockState::new_const(#first_state_id) } } else { quote! { match self { @@ -762,7 +762,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { type Value = #value; fn try_from_block_state(block_state: BlockState) -> Option { - match block_state.id { + match block_state.id() { #enum_inner_generated _ => None } @@ -788,7 +788,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { impl From for Box { fn from(block_state: BlockState) -> Self { - let b = block_state.id; + let b = block_state.id(); match b { #from_state_to_block_match _ => panic!("Invalid block state: {}", b), diff --git a/azalea-block/src/block_state.rs b/azalea-block/src/block_state.rs index 6a185ee4..9dd2e741 100644 --- a/azalea-block/src/block_state.rs +++ b/azalea-block/src/block_state.rs @@ -27,7 +27,7 @@ pub type BlockStateIntegerRepr = u16; 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: BlockStateIntegerRepr, + id: BlockStateIntegerRepr, } impl BlockState { @@ -35,12 +35,21 @@ impl BlockState { /// 0. pub const AIR: BlockState = BlockState { id: 0 }; + /// Create a new BlockState and panic if the block is not a valid state. + /// + /// You should probably use [`BlockState::try_from`] instead. + #[inline] + pub(crate) const fn new_const(id: BlockStateIntegerRepr) -> Self { + assert!(Self::is_valid_state(id)); + Self { id } + } + /// Whether the block state is possible to exist in vanilla Minecraft. /// /// It's equivalent to checking that the state ID is not greater than /// [`Self::MAX_STATE`]. #[inline] - pub fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool { + pub const fn is_valid_state(state_id: BlockStateIntegerRepr) -> bool { state_id <= Self::MAX_STATE } @@ -50,6 +59,13 @@ impl BlockState { pub fn is_air(&self) -> bool { self == &Self::AIR } + + /// Returns the protocol ID for the block state. IDs may change every + /// version, so you shouldn't hard-code them or store them in databases. + #[inline] + pub const fn id(&self) -> BlockStateIntegerRepr { + self.id + } } impl TryFrom for BlockState { diff --git a/azalea-block/src/range.rs b/azalea-block/src/range.rs index 18e74c88..cbe77284 100644 --- a/azalea-block/src/range.rs +++ b/azalea-block/src/range.rs @@ -14,7 +14,7 @@ impl From> for BlockStates { fn from(range: RangeInclusive) -> Self { let mut set = HashSet::with_capacity((range.end() - range.start() + 1) as usize); for id in range { - set.insert(BlockState { id }); + set.insert(BlockState::try_from(id).unwrap_or_default()); } Self { set } } diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml index bae31992..19c404c4 100644 --- a/azalea-brigadier/Cargo.toml +++ b/azalea-brigadier/Cargo.toml @@ -11,8 +11,8 @@ bevy_app.workspace = true bevy_ecs.workspace = true [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0", optional = true } -azalea-chat = { path = "../azalea-chat", version = "0.11.0", optional = true } +azalea-buf = { path = "../azalea-buf", version = "0.12.0", optional = true } +azalea-chat = { path = "../azalea-chat", version = "0.12.0", optional = true } parking_lot.workspace = true [features] diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index 1803e3c1..a5afe0da 100644 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -1,7 +1,7 @@ use std::{ cmp::Ordering, collections::{HashMap, HashSet}, - mem, + mem, ptr, rc::Rc, sync::Arc, }; @@ -349,7 +349,7 @@ impl CommandDispatcher { } match &node.redirect { Some(redirect) => { - let redirect = if std::ptr::eq(redirect.data_ptr(), self.root.data_ptr()) { + let redirect = if ptr::eq(redirect.data_ptr(), self.root.data_ptr()) { "...".to_string() } else { format!("-> {}", redirect.read().usage_text()) @@ -427,7 +427,7 @@ impl CommandDispatcher { } if let Some(redirect) = &node.redirect { - let redirect = if std::ptr::eq(redirect.data_ptr(), self.root.data_ptr()) { + let redirect = if ptr::eq(redirect.data_ptr(), self.root.data_ptr()) { "...".to_string() } else { format!("-> {}", redirect.read().usage_text()) diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml index d8a36044..50177e4c 100644 --- a/azalea-buf/Cargo.toml +++ b/azalea-buf/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.11.0" } +azalea-buf-macros = { path = "./azalea-buf-macros", version = "0.12.0" } byteorder.workspace = true serde_json = { workspace = true, optional = true } simdnbt.workspace = true diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml index 04505295..044d0a16 100644 --- a/azalea-chat/Cargo.toml +++ b/azalea-chat/Cargo.toml @@ -13,11 +13,11 @@ azalea-buf = ["dep:azalea-buf", "simdnbt"] numbers = ["dep:azalea-registry", "dep:simdnbt"] [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0", optional = true, features = [ +azalea-buf = { path = "../azalea-buf", version = "0.12.0", optional = true, features = [ "serde_json", ] } -azalea-language = { path = "../azalea-language", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0", optional = true } +azalea-language = { path = "../azalea-language", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0", optional = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true simdnbt = { workspace = true, optional = true } diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml index 2e1071c4..6ca17a94 100644 --- a/azalea-client/Cargo.toml +++ b/azalea-client/Cargo.toml @@ -7,19 +7,19 @@ license.workspace = true repository.workspace = true [dependencies] -async-compat = "0.2.4" -azalea-auth = { path = "../azalea-auth", version = "0.11.0" } -azalea-block = { path = "../azalea-block", version = "0.11.0" } -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-chat = { path = "../azalea-chat", version = "0.11.0" } -azalea-core = { path = "../azalea-core", version = "0.11.0" } -azalea-crypto = { path = "../azalea-crypto", version = "0.11.0" } -azalea-entity = { path = "../azalea-entity", version = "0.11.0" } -azalea-inventory = { path = "../azalea-inventory", version = "0.11.0" } -azalea-physics = { path = "../azalea-physics", version = "0.11.0" } -azalea-protocol = { path = "../azalea-protocol", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } -azalea-world = { path = "../azalea-world", version = "0.11.0" } +async-compat.workspace = true +azalea-auth = { path = "../azalea-auth", version = "0.12.0" } +azalea-block = { path = "../azalea-block", version = "0.12.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-chat = { path = "../azalea-chat", version = "0.12.0" } +azalea-core = { path = "../azalea-core", version = "0.12.0" } +azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" } +azalea-entity = { path = "../azalea-entity", version = "0.12.0" } +azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" } +azalea-physics = { path = "../azalea-physics", version = "0.12.0" } +azalea-protocol = { path = "../azalea-protocol", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } +azalea-world = { path = "../azalea-world", version = "0.12.0" } bevy_app.workspace = true bevy_ecs.workspace = true bevy_log = { workspace = true, optional = true } diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index ffe87f97..bef05a14 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -38,15 +38,14 @@ use bevy_ecs::{ component::Component, entity::Entity, schedule::{InternedScheduleLabel, IntoSystemConfigs, LogLevel, ScheduleBuildSettings}, - system::{Commands, ResMut, Resource}, + system::{Commands, Resource}, world::World, }; -use derive_more::Deref; use parking_lot::{Mutex, RwLock}; use simdnbt::owned::NbtCompound; use thiserror::Error; use tokio::{ - sync::{broadcast, mpsc}, + sync::mpsc::{self}, time, }; use tracing::{debug, error, info, warn}; @@ -827,39 +826,6 @@ async fn run_schedule_loop(ecs: Arc>, outer_schedule_label: Interne } } -/// A resource that contains a [`broadcast::Sender`] that will be sent every -/// Minecraft tick. -/// -/// This is useful for running code every schedule from async user code. -/// -/// ``` -/// use azalea_client::TickBroadcast; -/// # async fn example(client: azalea_client::Client) { -/// let mut receiver = { -/// let ecs = client.ecs.lock(); -/// let tick_broadcast = ecs.resource::(); -/// tick_broadcast.subscribe() -/// }; -/// while receiver.recv().await.is_ok() { -/// // do something -/// } -/// # } -/// ``` -#[derive(Resource, Deref)] -pub struct TickBroadcast(broadcast::Sender<()>); - -pub fn send_tick_broadcast(tick_broadcast: ResMut) { - let _ = tick_broadcast.0.send(()); -} -/// A plugin that makes the [`RanScheduleBroadcast`] resource available. -pub struct TickBroadcastPlugin; -impl Plugin for TickBroadcastPlugin { - fn build(&self, app: &mut App) { - app.insert_resource(TickBroadcast(broadcast::channel(1).0)) - .add_systems(GameTick, send_tick_broadcast); - } -} - pub struct AmbiguityLoggerPlugin; impl Plugin for AmbiguityLoggerPlugin { fn build(&self, app: &mut App) { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index 518d3dd0..b83c24a8 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -23,7 +23,7 @@ pub use account::{Account, AccountOpts}; pub use azalea_protocol::common::client_information::ClientInformation; pub use client::{ Client, InConfigState, InGameState, JoinError, JoinedClientBundle, LocalPlayerBundle, - StartClientOpts, TickBroadcast, start_ecs_runner, + StartClientOpts, start_ecs_runner, }; pub use events::Event; pub use local_player::{GameProfileComponent, Hunger, InstanceHolder, TabList}; diff --git a/azalea-client/src/plugins/connection.rs b/azalea-client/src/plugins/connection.rs index 276e45a3..b462535e 100644 --- a/azalea-client/src/plugins/connection.rs +++ b/azalea-client/src/plugins/connection.rs @@ -19,7 +19,7 @@ use tokio::{ net::tcp::OwnedWriteHalf, sync::mpsc::{self}, }; -use tracing::{debug, error, trace}; +use tracing::{debug, error, info, trace}; use super::packet::{ config::ReceiveConfigPacketEvent, game::ReceiveGamePacketEvent, login::ReceiveLoginPacketEvent, @@ -99,6 +99,18 @@ pub fn read_packets(ecs: &mut World) { } Err(err) => { log_for_error(&err); + + if matches!( + &*err, + ReadPacketError::IoError { .. } | ReadPacketError::ConnectionClosed + ) { + info!("Server closed connection"); + // ungraceful disconnect :( + conn.network = None; + // setting this will make us send a DisconnectEvent + conn.is_alive = false; + } + break; } } @@ -308,7 +320,8 @@ impl NetworkConnection { } pub fn poll_writer(&mut self) { - future::block_on(future::poll_once(&mut self.writer_task)); + let poll_once_res = future::poll_once(&mut self.writer_task); + future::block_on(poll_once_res); } pub fn set_compression_threshold(&mut self, threshold: Option) { diff --git a/azalea-client/src/plugins/mod.rs b/azalea-client/src/plugins/mod.rs index 1afe1729..16b34205 100644 --- a/azalea-client/src/plugins/mod.rs +++ b/azalea-client/src/plugins/mod.rs @@ -16,6 +16,7 @@ pub mod packet; pub mod pong; pub mod respawn; pub mod task_pool; +pub mod tick_broadcast; pub mod tick_end; /// This plugin group will add all the default plugins necessary for Azalea to @@ -45,7 +46,7 @@ impl PluginGroup for DefaultPlugins { .add(chunks::ChunksPlugin) .add(tick_end::TickEndPlugin) .add(brand::BrandPlugin) - .add(crate::client::TickBroadcastPlugin) + .add(tick_broadcast::TickBroadcastPlugin) .add(pong::PongPlugin) .add(connection::ConnectionPlugin) .add(login::LoginPlugin); diff --git a/azalea-client/src/plugins/tick_broadcast.rs b/azalea-client/src/plugins/tick_broadcast.rs new file mode 100644 index 00000000..424e1bf3 --- /dev/null +++ b/azalea-client/src/plugins/tick_broadcast.rs @@ -0,0 +1,49 @@ +use azalea_core::tick::GameTick; +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use derive_more::Deref; +use tokio::sync::broadcast; + +/// A resource that contains a [`broadcast::Sender`] that will be sent every +/// Minecraft tick. +/// +/// This is useful for running code every schedule from async user code. +/// +/// ``` +/// use azalea_client::tick_broadcast::TickBroadcast; +/// # async fn example(client: azalea_client::Client) { +/// let mut receiver = { +/// let ecs = client.ecs.lock(); +/// let tick_broadcast = ecs.resource::(); +/// tick_broadcast.subscribe() +/// }; +/// while receiver.recv().await.is_ok() { +/// // do something +/// } +/// # } +/// ``` +#[derive(Resource, Deref)] +pub struct TickBroadcast(broadcast::Sender<()>); +/// A resource that contains a [`broadcast::Sender`] that will be sent every +/// Azalea ECS Update. +/// +/// Also see [`TickBroadcast`]. +#[derive(Resource, Deref)] +pub struct UpdateBroadcast(broadcast::Sender<()>); + +pub fn send_tick_broadcast(tick_broadcast: ResMut) { + let _ = tick_broadcast.0.send(()); +} +pub fn send_update_broadcast(update_broadcast: ResMut) { + let _ = update_broadcast.0.send(()); +} +/// A plugin that makes the [`RanScheduleBroadcast`] resource available. +pub struct TickBroadcastPlugin; +impl Plugin for TickBroadcastPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(TickBroadcast(broadcast::channel(1).0)) + .insert_resource(UpdateBroadcast(broadcast::channel(1).0)) + .add_systems(GameTick, send_tick_broadcast) + .add_systems(Update, send_update_broadcast); + } +} diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index 85eef1f4..89442649 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -7,15 +7,15 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } bevy_ecs = { workspace = true, optional = true } nohash-hasher.workspace = true num-traits.workspace = true serde = { workspace = true, optional = true } simdnbt.workspace = true tracing.workspace = true -azalea-chat = { path = "../azalea-chat", version = "0.11.0" } +azalea-chat = { path = "../azalea-chat", version = "0.12.0" } indexmap.workspace = true [features] diff --git a/azalea-crypto/Cargo.toml b/azalea-crypto/Cargo.toml index 924ec99c..e055f6aa 100644 --- a/azalea-crypto/Cargo.toml +++ b/azalea-crypto/Cargo.toml @@ -11,7 +11,7 @@ criterion.workspace = true [dependencies] aes.workspace = true -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } cfb8.workspace = true num-bigint.workspace = true rand = { workspace = true, features = ["getrandom"] } diff --git a/azalea-entity/Cargo.toml b/azalea-entity/Cargo.toml index 3c5e12d6..e88907cc 100644 --- a/azalea-entity/Cargo.toml +++ b/azalea-entity/Cargo.toml @@ -7,15 +7,15 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-block = { path = "../azalea-block", version = "0.11.0" } -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-chat = { path = "../azalea-chat", version = "0.11.0", features = [ +azalea-block = { path = "../azalea-block", version = "0.12.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [ "azalea-buf", ] } -azalea-core = { path = "../azalea-core", version = "0.11.0" } -azalea-inventory = { path = "../azalea-inventory", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } -azalea-world = { path = "../azalea-world", version = "0.11.0" } +azalea-core = { path = "../azalea-core", version = "0.12.0" } +azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } +azalea-world = { path = "../azalea-world", version = "0.12.0" } bevy_app.workspace = true bevy_ecs.workspace = true derive_more.workspace = true diff --git a/azalea-inventory/Cargo.toml b/azalea-inventory/Cargo.toml index e56392b1..157d8cde 100644 --- a/azalea-inventory/Cargo.toml +++ b/azalea-inventory/Cargo.toml @@ -7,13 +7,13 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-chat = { path = "../azalea-chat", version = "0.11.0", features = [ +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [ "azalea-buf", ] } -azalea-core = { path = "../azalea-core", version = "0.11.0" } -azalea-inventory-macros = { path = "./azalea-inventory-macros", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } +azalea-core = { path = "../azalea-core", version = "0.12.0" } +azalea-inventory-macros = { path = "./azalea-inventory-macros", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } indexmap.workspace = true simdnbt.workspace = true diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index c75b8d17..c6e25882 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -11,12 +11,12 @@ bevy_time.workspace = true uuid.workspace = true [dependencies] -azalea-block = { path = "../azalea-block", version = "0.11.0" } -azalea-core = { path = "../azalea-core", version = "0.11.0" } -azalea-entity = { version = "0.11.0", path = "../azalea-entity" } -azalea-inventory = { version = "0.11.0", path = "../azalea-inventory" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } -azalea-world = { path = "../azalea-world", version = "0.11.0" } +azalea-block = { path = "../azalea-block", version = "0.12.0" } +azalea-core = { path = "../azalea-core", version = "0.12.0" } +azalea-entity = { version = "0.12.0", path = "../azalea-entity" } +azalea-inventory = { version = "0.12.0", path = "../azalea-inventory" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } +azalea-world = { path = "../azalea-world", version = "0.12.0" } bevy_app.workspace = true bevy_ecs.workspace = true tracing.workspace = true diff --git a/azalea-physics/src/collision/blocks.rs b/azalea-physics/src/collision/blocks.rs index 048ee067..ec1efc4d 100644 --- a/azalea-physics/src/collision/blocks.rs +++ b/azalea-physics/src/collision/blocks.rs @@ -4910,19 +4910,21 @@ static SHAPE760: LazyLock = impl BlockWithShape for BlockState { fn collision_shape(&self) -> &'static VoxelShape { COLLISION_SHAPES_MAP - .get(self.id as usize) + .get(self.id() as usize) .unwrap_or(&&SHAPE1) } fn outline_shape(&self) -> &'static VoxelShape { - OUTLINE_SHAPES_MAP.get(self.id as usize).unwrap_or(&&SHAPE1) + OUTLINE_SHAPES_MAP + .get(self.id() as usize) + .unwrap_or(&&SHAPE1) } fn is_collision_shape_empty(&self) -> bool { - matches!(self.id, 0|29..=42|45..=84|86..=117|1987..=2034|2047..=2050|2054..=2056|2109..=2136|2401..=2918|3042..=4337|4342..=4349|4366..=4589|4622..=4685|4758..=4777|4858..=4913|4922..=5385|5450..=5705|5802..=5827|5892..=5905|5908..=5911|5916..=5950|5978..=5993|6037..=6041|6043..=6044|7056..=7239|7368..=7369|7372..=7373|7376..=7377|7380..=7381|7384..=7385|7388..=7389|7392..=7393|7396..=7397|8169..=8172|8190|8305..=8448|8709|8712|9033|9036|9380..=9563|9588..=9635|9952..=9983|10129..=10152|11256..=11287|11636..=11967|12205..=12206|12209..=12210|12213..=12214|12217..=12218|12221..=12222|12225..=12226|12229..=12230|12233..=12234|12237..=12238|12241..=12242|12245..=12246|12249..=12250|12253..=12254|12257..=12258|12261..=12262|12265..=12266|12269..=12270|12273..=12274|12277..=12278|12281..=12282|12285..=12286|12289..=12290|12293..=12294|12297..=12298|12301..=12302|12305..=12306|12309..=12310|12313..=12314|12317..=12318|12321..=12322|12325..=12326|12329..=12330|12333..=12334|12337..=12338|12341..=12342|12345..=12346|12349..=12350|12353..=12354|12357..=12358|12361..=12362|12365..=12366|12369..=12370|12373..=12374|12377..=12378|12381..=12382|12385..=12386|12389..=12390|12393..=12394|12429..=12430|12433..=12434|12437..=12438|12441..=12442|12445..=12446|12449..=12450|12453..=12454|12457..=12458|12461..=12462|12465..=12466|12469..=12470|12473..=12474|12477..=12478|12481..=12482|12485..=12486|12489..=12490|13518..=13519|13522|13524|13526|13528|13530..=13535|13537|13572|13783..=13809|13836..=13955|13967|13981..=13984|15189|15192|15513|15516|15837|15840|16161|16164|16485|16488|16809|16812|17133|17136|17457|17460|17781|17784|18105|18108|18429|18432|18753|18756|19077|19080|19598..=19601|19615|19617..=19618|19632|19634..=19688|19703..=19706|19899..=19900|19903..=19904|19907..=19908|19911..=19912|19915..=19916|19919..=19920|19923..=19924|19927..=19928|19931..=19932|19935..=19936|19939..=19940|19943..=19944|19947..=19948|19951..=19952|19955..=19956|19959..=19960|20123..=20170|20299..=20378|20575|20578|20995|20998|21400..=21425|21432|21435|22202|22205|22613|22616|23025|23028|23346|23828..=23955|25797..=25851|25855..=25870|25910..=25911|25918..=25919|25926..=25927|25934..=25961|26060|26063|26471|26474|26882|26885|27293|27296|27632) + matches!(self.id(), 0|29..=42|45..=84|86..=117|1987..=2034|2047..=2050|2054..=2056|2109..=2136|2401..=2918|3042..=4337|4342..=4349|4366..=4589|4622..=4685|4758..=4777|4858..=4913|4922..=5385|5450..=5705|5802..=5827|5892..=5905|5908..=5911|5916..=5950|5978..=5993|6037..=6041|6043..=6044|7056..=7239|7368..=7369|7372..=7373|7376..=7377|7380..=7381|7384..=7385|7388..=7389|7392..=7393|7396..=7397|8169..=8172|8190|8305..=8448|8709|8712|9033|9036|9380..=9563|9588..=9635|9952..=9983|10129..=10152|11256..=11287|11636..=11967|12205..=12206|12209..=12210|12213..=12214|12217..=12218|12221..=12222|12225..=12226|12229..=12230|12233..=12234|12237..=12238|12241..=12242|12245..=12246|12249..=12250|12253..=12254|12257..=12258|12261..=12262|12265..=12266|12269..=12270|12273..=12274|12277..=12278|12281..=12282|12285..=12286|12289..=12290|12293..=12294|12297..=12298|12301..=12302|12305..=12306|12309..=12310|12313..=12314|12317..=12318|12321..=12322|12325..=12326|12329..=12330|12333..=12334|12337..=12338|12341..=12342|12345..=12346|12349..=12350|12353..=12354|12357..=12358|12361..=12362|12365..=12366|12369..=12370|12373..=12374|12377..=12378|12381..=12382|12385..=12386|12389..=12390|12393..=12394|12429..=12430|12433..=12434|12437..=12438|12441..=12442|12445..=12446|12449..=12450|12453..=12454|12457..=12458|12461..=12462|12465..=12466|12469..=12470|12473..=12474|12477..=12478|12481..=12482|12485..=12486|12489..=12490|13518..=13519|13522|13524|13526|13528|13530..=13535|13537|13572|13783..=13809|13836..=13955|13967|13981..=13984|15189|15192|15513|15516|15837|15840|16161|16164|16485|16488|16809|16812|17133|17136|17457|17460|17781|17784|18105|18108|18429|18432|18753|18756|19077|19080|19598..=19601|19615|19617..=19618|19632|19634..=19688|19703..=19706|19899..=19900|19903..=19904|19907..=19908|19911..=19912|19915..=19916|19919..=19920|19923..=19924|19927..=19928|19931..=19932|19935..=19936|19939..=19940|19943..=19944|19947..=19948|19951..=19952|19955..=19956|19959..=19960|20123..=20170|20299..=20378|20575|20578|20995|20998|21400..=21425|21432|21435|22202|22205|22613|22616|23025|23028|23346|23828..=23955|25797..=25851|25855..=25870|25910..=25911|25918..=25919|25926..=25927|25934..=25961|26060|26063|26471|26474|26882|26885|27293|27296|27632) } fn is_collision_shape_full(&self) -> bool { - matches!(self.id, 1..=21|26..=28|85|118..=156|160..=188|192..=245|249..=447|476..=1730|2041..=2046|2063..=2068|2093..=2108|2137..=2400|2919|4338..=4341|4358..=4365|5912..=5915|5958..=5959|5977|5994..=5995|6028|6030..=6036|6042|6045..=6052|6124..=6139|6780..=6983|7054..=7055|7640..=7641|8056|8199|8201..=8202|8295..=8296|8449|8690..=8702|10032..=10033|10044..=10048|10153..=10180|11253..=11255|11352..=11354|11599..=11600|11605..=11606|11611..=11616|11633..=11635|11968..=11970|12055..=12056|12061..=12062|12067..=12068|12073..=12074|12079..=12080|12085..=12086|12091..=12092|12103..=12104|12109..=12110|12115..=12116|12121..=12122|12127..=12128|12133..=12134|12139..=12140|12145..=12146|12151..=12152|12157..=12158|12163..=12164|12169..=12170|12175..=12176|12181..=12182|12187..=12188|12193..=12194|12199..=12204|13427..=13436|13517|13538..=13571|13573..=13782|13810|13826..=13835|13964|15109..=15110|15115..=15116|15121..=15122|15127..=15128|15133..=15134|15139..=15140|15145..=15146|15151..=15152|15157..=15158|15163..=15164|15169..=15170|15175..=15176|15181..=15182|19427..=19460|19489|19602..=19614|19616|19619..=19631|19633|19689..=19690|19695..=19696|19701..=19702|20379..=20394|20409..=20472|20474..=20482|20487..=20488|20897..=20902|20907..=20908|21313|21398..=21399|21750..=21752|22059..=22060|22109|22114..=22115|22520|22525..=22526|22931..=22932|22937..=22938|23343..=23345|23827|23956..=23957|23966..=23983|24308..=24309|24314..=24315|24320..=24321|24326..=24335|24660..=24661|24666..=24667|24672..=24673|24678..=24679|25704..=25751|25796|25903|25962|25964..=25967|26052..=26053|26378|26463..=26464|26789|26874..=26875|27200|27285..=27286|27611..=27620|27623..=27631|27633|27650..=27703) + matches!(self.id(), 1..=21|26..=28|85|118..=156|160..=188|192..=245|249..=447|476..=1730|2041..=2046|2063..=2068|2093..=2108|2137..=2400|2919|4338..=4341|4358..=4365|5912..=5915|5958..=5959|5977|5994..=5995|6028|6030..=6036|6042|6045..=6052|6124..=6139|6780..=6983|7054..=7055|7640..=7641|8056|8199|8201..=8202|8295..=8296|8449|8690..=8702|10032..=10033|10044..=10048|10153..=10180|11253..=11255|11352..=11354|11599..=11600|11605..=11606|11611..=11616|11633..=11635|11968..=11970|12055..=12056|12061..=12062|12067..=12068|12073..=12074|12079..=12080|12085..=12086|12091..=12092|12103..=12104|12109..=12110|12115..=12116|12121..=12122|12127..=12128|12133..=12134|12139..=12140|12145..=12146|12151..=12152|12157..=12158|12163..=12164|12169..=12170|12175..=12176|12181..=12182|12187..=12188|12193..=12194|12199..=12204|13427..=13436|13517|13538..=13571|13573..=13782|13810|13826..=13835|13964|15109..=15110|15115..=15116|15121..=15122|15127..=15128|15133..=15134|15139..=15140|15145..=15146|15151..=15152|15157..=15158|15163..=15164|15169..=15170|15175..=15176|15181..=15182|19427..=19460|19489|19602..=19614|19616|19619..=19631|19633|19689..=19690|19695..=19696|19701..=19702|20379..=20394|20409..=20472|20474..=20482|20487..=20488|20897..=20902|20907..=20908|21313|21398..=21399|21750..=21752|22059..=22060|22109|22114..=22115|22520|22525..=22526|22931..=22932|22937..=22938|23343..=23345|23827|23956..=23957|23966..=23983|24308..=24309|24314..=24315|24320..=24321|24326..=24335|24660..=24661|24666..=24667|24672..=24673|24678..=24679|25704..=25751|25796|25903|25962|25964..=25967|26052..=26053|26378|26463..=26464|26789|26874..=26875|27200|27285..=27286|27611..=27620|27623..=27631|27633|27650..=27703) } } diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index dec299fc..6e264da8 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -13,25 +13,25 @@ tracing-subscriber.workspace = true [dependencies] async-recursion.workspace = true -azalea-auth = { path = "../azalea-auth", version = "0.11.0" } -azalea-block = { path = "../azalea-block", version = "0.11.0", default-features = false } -azalea-brigadier = { path = "../azalea-brigadier", version = "0.11.0", features = [ +azalea-auth = { path = "../azalea-auth", version = "0.12.0" } +azalea-block = { path = "../azalea-block", version = "0.12.0", default-features = false } +azalea-brigadier = { path = "../azalea-brigadier", version = "0.12.0", features = [ "azalea-buf", ] } -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-chat = { path = "../azalea-chat", version = "0.11.0", features = [ +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-chat = { path = "../azalea-chat", version = "0.12.0", features = [ "numbers", "azalea-buf", ] } -azalea-core = { path = "../azalea-core", version = "0.11.0", optional = true, features = [ +azalea-core = { path = "../azalea-core", version = "0.12.0", optional = true, features = [ "serde", ] } -azalea-crypto = { path = "../azalea-crypto", version = "0.11.0" } -azalea-entity = { path = "../azalea-entity", version = "0.11.0" } -azalea-inventory = { path = "../azalea-inventory", version = "0.11.0" } -azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.11.0" } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } -azalea-world = { path = "../azalea-world", version = "0.11.0" } +azalea-crypto = { path = "../azalea-crypto", version = "0.12.0" } +azalea-entity = { path = "../azalea-entity", version = "0.12.0" } +azalea-inventory = { path = "../azalea-inventory", version = "0.12.0" } +azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "0.12.0" } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } +azalea-world = { path = "../azalea-world", version = "0.12.0" } bevy_ecs.workspace = true # byteorder.workspace = true flate2.workspace = true diff --git a/azalea-protocol/src/packets/game/c_section_blocks_update.rs b/azalea-protocol/src/packets/game/c_section_blocks_update.rs index 05ceb30c..2a7a867e 100644 --- a/azalea-protocol/src/packets/game/c_section_blocks_update.rs +++ b/azalea-protocol/src/packets/game/c_section_blocks_update.rs @@ -35,7 +35,7 @@ impl AzaleaRead for BlockStateWithPosition { impl AzaleaWrite for BlockStateWithPosition { fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - let data = ((self.state.id as u64) << 12) + let data = ((self.state.id() as u64) << 12) | ((u64::from(self.pos.x) << 8) | (u64::from(self.pos.z) << 4) | u64::from(self.pos.y)); u64::azalea_write_var(&data, buf)?; Ok(()) diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index de196984..038af319 100644 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -158,7 +158,7 @@ pub fn deserialize_packet( // this is always true in multiplayer, false in singleplayer static VALIDATE_DECOMPRESSED: bool = true; -pub static MAXIMUM_UNCOMPRESSED_LENGTH: u32 = 2_097_152; +pub static MAXIMUM_UNCOMPRESSED_LENGTH: u32 = 8_388_608; #[derive(Error, Debug)] pub enum DecompressionError { diff --git a/azalea-registry/Cargo.toml b/azalea-registry/Cargo.toml index 940c61b5..d5cdd3f3 100644 --- a/azalea-registry/Cargo.toml +++ b/azalea-registry/Cargo.toml @@ -7,8 +7,8 @@ license.workspace = true repository.workspace = true [dependencies] -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.11.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-registry-macros = { path = "./azalea-registry-macros", version = "0.12.0" } serde = { workspace = true, optional = true, features = ["derive"] } simdnbt.workspace = true diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 5354259e..4bb65265 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -11,10 +11,12 @@ azalea-client = { path = "../azalea-client" } criterion = "0.5.1" [dependencies] -azalea-block = { path = "../azalea-block", default-features = false, version = "0.11.0" } -azalea-buf = { path = "../azalea-buf", version = "0.11.0" } -azalea-core = { path = "../azalea-core", version = "0.11.0", features = ["bevy_ecs"] } -azalea-registry = { path = "../azalea-registry", version = "0.11.0" } +azalea-block = { path = "../azalea-block", default-features = false, version = "0.12.0" } +azalea-buf = { path = "../azalea-buf", version = "0.12.0" } +azalea-core = { path = "../azalea-core", version = "0.12.0", features = [ + "bevy_ecs", +] } +azalea-registry = { path = "../azalea-registry", version = "0.12.0" } bevy_ecs.workspace = true derive_more = { workspace = true, features = ["deref", "deref_mut"] } nohash-hasher.workspace = true diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index 39cbc84f..d8357a95 100644 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -476,25 +476,18 @@ impl AzaleaWrite for Section { impl Section { pub fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { - // TODO: use the unsafe method and do the check earlier - let state = self - .states - .get(pos.x as usize, pos.y as usize, pos.z as usize); - // if there's an unknown block assume it's air - BlockState::try_from(state).unwrap_or(BlockState::AIR) + self.states + .get(pos.x as usize, pos.y as usize, pos.z as usize) } pub fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState { - let previous_state = - self.states - .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state.id); - // if there's an unknown block assume it's air - BlockState::try_from(previous_state).unwrap_or(BlockState::AIR) + self.states + .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state) } pub fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { self.states - .set(pos.x as usize, pos.y as usize, pos.z as usize, state.id); + .set(pos.x as usize, pos.y as usize, pos.z as usize, state); } } diff --git a/azalea-world/src/find_blocks.rs b/azalea-world/src/find_blocks.rs index 068a5029..967f20ec 100644 --- a/azalea-world/src/find_blocks.rs +++ b/azalea-world/src/find_blocks.rs @@ -1,17 +1,13 @@ -use azalea_block::{BlockStates, block_state::BlockState}; +use azalea_block::BlockStates; use azalea_core::position::{BlockPos, ChunkPos}; use crate::{ChunkStorage, Instance, iterators::ChunkIterator, palette::Palette}; fn palette_maybe_has_block(palette: &Palette, block_states: &BlockStates) -> bool { match &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::SingleValue(id) => block_states.contains(id), + Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)), + Palette::Hashmap(ids) => ids.iter().any(|id| block_states.contains(id)), Palette::Global => true, } } @@ -62,7 +58,6 @@ impl Instance { 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); @@ -116,7 +111,10 @@ impl Instance { /// Find all the coordinates of a block in the world. /// /// This returns an iterator that yields the [`BlockPos`]s of blocks that - /// are in the given block states. It's sorted by `x+y+z`. + /// are in the given block states. + /// + /// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2` for + /// optimization purposes. pub fn find_blocks<'a>( &'a self, nearest_to: impl Into, @@ -187,7 +185,6 @@ impl Iterator for FindBlocks<'_> { for i in 0..4096 { let block_state = section.states.get_at_index(i); - let block_state = BlockState { id: block_state }; if self.block_states.contains(&block_state) { let (section_x, section_y, section_z) = section.states.coords_from_index(i); diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index 699ee2d5..37d43fac 100644 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -1,7 +1,7 @@ use std::io::{Cursor, Write}; -use azalea_block::block_state::BlockStateIntegerRepr; -use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; +use azalea_block::BlockState; +use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; use tracing::warn; use crate::BitStorage; @@ -28,7 +28,7 @@ pub struct PalettedContainer { impl PalettedContainer { pub fn new(container_type: PalettedContainerKind) -> Self { - let palette = Palette::SingleValue(0); + let palette = Palette::SingleValue(BlockState::AIR); let size = container_type.size(); let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap(); @@ -105,7 +105,7 @@ impl PalettedContainer { /// 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) -> BlockStateIntegerRepr { + pub fn get_at_index(&self, index: usize) -> BlockState { // first get the palette id let paletted_value = self.storage.get(index); // and then get the value from that id @@ -113,35 +113,39 @@ impl PalettedContainer { } /// Returns the value at the given coordinates. - pub fn get(&self, x: usize, y: usize, z: usize) -> BlockStateIntegerRepr { + pub fn get(&self, x: usize, y: usize, z: usize) -> BlockState { // 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.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: BlockStateIntegerRepr, - ) -> BlockStateIntegerRepr { + pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: BlockState) -> BlockState { let paletted_value = self.id_for(value); - self.storage - .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64) - as BlockStateIntegerRepr + let block_state_id = self + .storage + .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64); + // error in debug mode + #[cfg(debug_assertions)] + if block_state_id > BlockState::MAX_STATE.into() { + warn!( + "Old block state from get_and_set {block_state_id} was greater than max state {}", + BlockState::MAX_STATE + ); + } + + BlockState::try_from(block_state_id as u32).unwrap_or_default() } /// Sets the id at the given index and return the previous id. You probably /// want `.set` instead. - pub fn set_at_index(&mut self, index: usize, value: BlockStateIntegerRepr) { + pub fn set_at_index(&mut self, index: usize, value: BlockState) { let paletted_value = self.id_for(value); self.storage.set(index, paletted_value as u64); } /// Sets the id at the given coordinates and return the previous id - pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockStateIntegerRepr) { + pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockState) { self.set_at_index(self.index_from_coords(x, y, z), value); } @@ -170,7 +174,7 @@ impl PalettedContainer { } } - fn on_resize(&mut self, bits_per_entry: u8, value: BlockStateIntegerRepr) -> usize { + fn on_resize(&mut self, bits_per_entry: u8, value: BlockState) -> usize { // in vanilla this is always true, but it's sometimes false in purpur servers // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5"); let mut new_data = self.create_or_reuse_data(bits_per_entry); @@ -187,7 +191,7 @@ impl PalettedContainer { } } - pub fn id_for(&mut self, value: BlockStateIntegerRepr) -> usize { + pub fn id_for(&mut self, value: BlockState) -> usize { match &mut self.palette { Palette::SingleValue(v) => { if *v != value { @@ -209,7 +213,8 @@ impl PalettedContainer { } } Palette::Hashmap(palette) => { - // TODO? vanilla keeps this in memory as a hashmap, but also i don't care + // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked + // before changing it if let Some(index) = palette.iter().position(|v| *v == value) { return index; } @@ -221,7 +226,7 @@ impl PalettedContainer { self.on_resize(self.bits_per_entry + 1, value) } } - Palette::Global => value as usize, + Palette::Global => value.id() as usize, } } } @@ -247,21 +252,21 @@ pub enum PaletteKind { #[derive(Clone, Debug)] pub enum Palette { /// ID of the corresponding entry in its global palette - SingleValue(BlockStateIntegerRepr), + SingleValue(BlockState), // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure // it's actually needed? - Linear(Vec), - Hashmap(Vec), + Linear(Vec), + Hashmap(Vec), Global, } impl Palette { - pub fn value_for(&self, id: usize) -> BlockStateIntegerRepr { + pub fn value_for(&self, id: usize) -> BlockState { match self { Palette::SingleValue(v) => *v, - Palette::Linear(v) => v[id], + Palette::Linear(v) => v.get(id).copied().unwrap_or_default(), Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), - Palette::Global => id as BlockStateIntegerRepr, + Palette::Global => BlockState::try_from(id as u32).unwrap_or_default(), } } } @@ -270,13 +275,13 @@ impl AzaleaWrite for Palette { fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { match self { Palette::SingleValue(value) => { - value.azalea_write_var(buf)?; + value.azalea_write(buf)?; } Palette::Linear(values) => { - values.azalea_write_var(buf)?; + values.azalea_write(buf)?; } Palette::Hashmap(values) => { - values.azalea_write_var(buf)?; + values.azalea_write(buf)?; } Palette::Global => {} } @@ -305,22 +310,16 @@ impl PaletteKind { Ok(match self { // since they're read as varints it's actually fine to just use BlockStateIntegerRepr // instead of the correct type (u32) - PaletteKind::SingleValue => { - Palette::SingleValue(BlockStateIntegerRepr::azalea_read_var(buf)?) - } - PaletteKind::Linear => { - Palette::Linear(Vec::::azalea_read_var(buf)?) - } - PaletteKind::Hashmap => { - Palette::Hashmap(Vec::::azalea_read_var(buf)?) - } + PaletteKind::SingleValue => Palette::SingleValue(BlockState::azalea_read(buf)?), + PaletteKind::Linear => Palette::Linear(Vec::::azalea_read(buf)?), + PaletteKind::Hashmap => Palette::Hashmap(Vec::::azalea_read(buf)?), PaletteKind::Global => Palette::Global, }) } pub fn as_empty_palette(&self) -> Palette { match self { - PaletteKind::SingleValue => Palette::SingleValue(0), + PaletteKind::SingleValue => Palette::SingleValue(BlockState::AIR), PaletteKind::Linear => Palette::Linear(Vec::new()), PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), PaletteKind::Global => Palette::Global, @@ -361,13 +360,14 @@ mod tests { let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); 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), BlockState::AIR); assert_eq!( PaletteKind::from(&palette_container.palette), PaletteKind::SingleValue ); - palette_container.set_at_index(0, 1); - assert_eq!(palette_container.get_at_index(0), 1); + let block_state_1 = BlockState::try_from(1_u32).unwrap(); + palette_container.set_at_index(0, block_state_1); + assert_eq!(palette_container.get_at_index(0), block_state_1); assert_eq!( PaletteKind::from(&palette_container.palette), PaletteKind::Linear @@ -378,34 +378,38 @@ mod tests { fn test_resize_0_bits_to_5() { let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - palette_container.set_at_index(0, 0); // 0 bits + let set = |pc: &mut PalettedContainer, i, v: u32| { + pc.set_at_index(i, BlockState::try_from(v).unwrap()); + }; + + set(&mut palette_container, 0, 0); // 0 bits assert_eq!(palette_container.bits_per_entry, 0); - palette_container.set_at_index(1, 1); // 1 bit + set(&mut palette_container, 1, 1); // 1 bit assert_eq!(palette_container.bits_per_entry, 1); - palette_container.set_at_index(2, 2); // 2 bits + set(&mut palette_container, 2, 2); // 2 bits assert_eq!(palette_container.bits_per_entry, 2); - palette_container.set_at_index(3, 3); + set(&mut palette_container, 3, 3); - palette_container.set_at_index(4, 4); // 3 bits + set(&mut palette_container, 4, 4); // 3 bits assert_eq!(palette_container.bits_per_entry, 3); - palette_container.set_at_index(5, 5); - palette_container.set_at_index(6, 6); - palette_container.set_at_index(7, 7); + set(&mut palette_container, 5, 5); + set(&mut palette_container, 6, 6); + set(&mut palette_container, 7, 7); - palette_container.set_at_index(8, 8); // 4 bits + set(&mut palette_container, 8, 8); // 4 bits assert_eq!(palette_container.bits_per_entry, 4); - palette_container.set_at_index(9, 9); - palette_container.set_at_index(10, 10); - palette_container.set_at_index(11, 11); - palette_container.set_at_index(12, 12); - palette_container.set_at_index(13, 13); - palette_container.set_at_index(14, 14); - palette_container.set_at_index(15, 15); + set(&mut palette_container, 9, 9); + set(&mut palette_container, 10, 10); + set(&mut palette_container, 11, 11); + set(&mut palette_container, 12, 12); + set(&mut palette_container, 13, 13); + set(&mut palette_container, 14, 14); + set(&mut palette_container, 15, 15); assert_eq!(palette_container.bits_per_entry, 4); - palette_container.set_at_index(16, 16); // 5 bits + set(&mut palette_container, 16, 16); // 5 bits assert_eq!(palette_container.bits_per_entry, 5); } diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 33730361..7ec9635d 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -13,19 +13,19 @@ pre-release-replacements = [ [dependencies] #async-trait.workspace = true -azalea-auth = { version = "0.11.0", path = "../azalea-auth" } -azalea-block = { version = "0.11.0", path = "../azalea-block" } -azalea-brigadier = { version = "0.11.0", path = "../azalea-brigadier" } -azalea-buf = { version = "0.11.0", path = "../azalea-buf" } -azalea-chat = { version = "0.11.0", path = "../azalea-chat" } -azalea-client = { version = "0.11.0", path = "../azalea-client", default-features = false } -azalea-core = { version = "0.11.0", path = "../azalea-core" } -azalea-entity = { version = "0.11.0", path = "../azalea-entity" } -azalea-inventory = { version = "0.11.0", path = "../azalea-inventory" } -azalea-physics = { version = "0.11.0", path = "../azalea-physics" } -azalea-protocol = { version = "0.11.0", path = "../azalea-protocol" } -azalea-registry = { version = "0.11.0", path = "../azalea-registry" } -azalea-world = { version = "0.11.0", path = "../azalea-world" } +azalea-auth = { version = "0.12.0", path = "../azalea-auth" } +azalea-block = { version = "0.12.0", path = "../azalea-block" } +azalea-brigadier = { version = "0.12.0", path = "../azalea-brigadier" } +azalea-buf = { version = "0.12.0", path = "../azalea-buf" } +azalea-chat = { version = "0.12.0", path = "../azalea-chat" } +azalea-client = { version = "0.12.0", path = "../azalea-client", default-features = false } +azalea-core = { version = "0.12.0", path = "../azalea-core" } +azalea-entity = { version = "0.12.0", path = "../azalea-entity" } +azalea-inventory = { version = "0.12.0", path = "../azalea-inventory" } +azalea-physics = { version = "0.12.0", path = "../azalea-physics" } +azalea-protocol = { version = "0.12.0", path = "../azalea-protocol" } +azalea-registry = { version = "0.12.0", path = "../azalea-registry" } +azalea-world = { version = "0.12.0", path = "../azalea-world" } bevy_app.workspace = true bevy_ecs.workspace = true bevy_log.workspace = true diff --git a/azalea/README.md b/azalea/README.md index de11e202..ec936b3b 100644 --- a/azalea/README.md +++ b/azalea/README.md @@ -9,13 +9,12 @@ First, install Rust nightly with `rustup install nightly` and `rustup default ni Then, use one of the following commands to add Azalea to your project: -- Latest bleeding-edge version (recommended): `cargo add azalea --git=https://github.com/azalea-rs/azalea`\ +- Latest bleeding-edge version (recommended): `cargo add azalea --git=https://github.com/azalea-rs/azalea` - Latest "stable" release: `cargo add azalea` ## Optimization -For faster compile times, make a `.cargo/config.toml` file in your project -and copy +For faster compile times, create a `.cargo/config.toml` file in your project and copy [this file](https://github.com/azalea-rs/azalea/blob/main/.cargo/config_fast_builds) into it. You may have to install the LLD linker. @@ -42,9 +41,10 @@ You can just replace these with `azalea` in your code since everything from `aza ```rust,no_run //! A bot that logs chat messages sent in the server to the console. +use std::sync::Arc; + use azalea::prelude::*; use parking_lot::Mutex; -use std::sync::Arc; #[tokio::main] async fn main() { @@ -59,12 +59,15 @@ async fn main() { } #[derive(Default, Clone, Component)] -pub struct State {} +pub struct State { + pub messages_received: Arc> +} async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { match event { Event::Chat(m) => { println!("{}", m.message().to_ansi()); + *state.messages_received.lock() += 1; } _ => {} } diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs index 84f69e55..3fa87cc4 100644 --- a/azalea/examples/steal.rs +++ b/azalea/examples/steal.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use azalea::pathfinder::goals::RadiusGoal; use azalea::{BlockPos, prelude::*}; use azalea_inventory::ItemStack; use azalea_inventory::operations::QuickMoveClick; @@ -21,6 +22,7 @@ async fn main() { #[derive(Default, Clone, Component)] struct State { + pub is_stealing: Arc>, pub checked_chests: Arc>>, } @@ -32,43 +34,64 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { if m.content() != "go" { return Ok(()); } - { - state.checked_chests.lock().clear(); - } - let chest_block = bot - .world() - .read() - .find_block(bot.position(), &azalea::registry::Block::Chest.into()); - // TODO: update this when find_blocks is implemented - let Some(chest_block) = chest_block else { - bot.chat("No chest found"); - return Ok(()); - }; - // bot.goto(BlockPosGoal(chest_block)); - let Some(chest) = bot.open_container_at(chest_block).await else { - println!("Couldn't open chest"); - return Ok(()); - }; - - println!("Getting contents"); - for (index, slot) in chest - .contents() - .expect("we just opened the chest") - .iter() - .enumerate() - { - println!("Checking slot {index}: {slot:?}"); - if let ItemStack::Present(item) = slot { - if item.kind == azalea::registry::Item::Diamond { - println!("clicking slot ^"); - chest.click(QuickMoveClick::Left { slot: index as u16 }); - } - } - } - - println!("Done"); + steal(bot, state).await?; } Ok(()) } + +async fn steal(bot: Client, state: State) -> anyhow::Result<()> { + { + let mut is_stealing = state.is_stealing.lock(); + if *is_stealing { + bot.chat("Already stealing"); + return Ok(()); + } + *is_stealing = true; + } + + state.checked_chests.lock().clear(); + + loop { + let chest_block = bot + .world() + .read() + .find_blocks(bot.position(), &azalea::registry::Block::Chest.into()) + .filter( + // filter for chests that haven't been checked + |block_pos| !state.checked_chests.lock().contains(&block_pos), + ) + .next(); + let Some(chest_block) = chest_block else { + break; + }; + + state.checked_chests.lock().push(chest_block); + + bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await; + + let Some(chest) = bot.open_container_at(chest_block).await else { + println!("Couldn't open chest at {chest_block:?}"); + continue; + }; + + println!("Getting contents of chest at {chest_block:?}"); + for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() { + println!("Checking slot {index}: {slot:?}"); + let ItemStack::Present(item) = slot else { + continue; + }; + if item.kind == azalea::registry::Item::Diamond { + println!("clicking slot ^"); + chest.click(QuickMoveClick::Left { slot: index as u16 }); + } + } + } + + bot.chat("Done"); + + *state.is_stealing.lock() = false; + + Ok(()) +} diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs index a42fb93f..3428d117 100644 --- a/azalea/examples/testbot/commands/debug.rs +++ b/azalea/examples/testbot/commands/debug.rs @@ -25,6 +25,12 @@ pub fn register(commands: &mut CommandDispatcher>) { 1 })); + commands.register(literal("disconnect").executes(|ctx: &Ctx| { + let source = ctx.source.lock(); + source.bot.disconnect(); + 1 + })); + commands.register(literal("whereami").executes(|ctx: &Ctx| { let mut source = ctx.source.lock(); let Some(entity) = source.entity() else { diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs index a400809b..1596ce89 100644 --- a/azalea/examples/testbot/commands/movement.rs +++ b/azalea/examples/testbot/commands/movement.rs @@ -28,7 +28,9 @@ pub fn register(commands: &mut CommandDispatcher>) { return 0; }; source.reply("ok"); - source.bot.goto(BlockPosGoal(BlockPos::from(position))); + source + .bot + .start_goto(BlockPosGoal(BlockPos::from(position))); 1 }) .then(literal("xz").then(argument("x", integer()).then( @@ -38,7 +40,7 @@ pub fn register(commands: &mut CommandDispatcher>) { let z = get_integer(ctx, "z").unwrap(); println!("goto xz {x} {z}"); source.reply("ok"); - source.bot.goto(XZGoal { x, z }); + source.bot.start_goto(XZGoal { x, z }); 1 }), ))) @@ -52,7 +54,7 @@ pub fn register(commands: &mut CommandDispatcher>) { let z = get_integer(ctx, "z").unwrap(); println!("goto radius {radius}, position: {x} {y} {z}"); source.reply("ok"); - source.bot.goto(RadiusGoal { + source.bot.start_goto(RadiusGoal { pos: BlockPos::new(x, y, z).center(), radius, }); @@ -68,7 +70,7 @@ pub fn register(commands: &mut CommandDispatcher>) { let z = get_integer(ctx, "z").unwrap(); println!("goto xyz {x} {y} {z}"); source.reply("ok"); - source.bot.goto(BlockPosGoal(BlockPos::new(x, y, z))); + source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z))); 1 }), ))), diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 514cea1e..dd8e6209 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -1,8 +1,8 @@ use std::f64::consts::PI; -use azalea_client::TickBroadcast; use azalea_client::interact::SwingArmEvent; use azalea_client::mining::Mining; +use azalea_client::tick_broadcast::{TickBroadcast, UpdateBroadcast}; use azalea_core::position::{BlockPos, Vec3}; use azalea_core::tick::GameTick; use azalea_entity::{ @@ -86,6 +86,12 @@ pub trait BotClientExt { fn look_at(&self, pos: Vec3); /// Get a receiver that will receive a message every tick. fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; + /// Get a receiver that will receive a message every ECS Update. + fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()>; + /// Wait for one tick. + fn wait_one_tick(&self) -> impl Future + Send; + /// Wait for one ECS Update. + fn wait_one_update(&self) -> impl Future + Send; /// Mine a block. This won't turn the bot's head towards the block, so if /// that's necessary you'll have to do that yourself with [`look_at`]. /// @@ -133,6 +139,38 @@ impl BotClientExt for azalea_client::Client { tick_broadcast.subscribe() } + /// Returns a Receiver that receives a message every ECS Update. + /// + /// ECS Updates happen at least at the frequency of game ticks, usually + /// faster. + /// + /// This is useful if you're sending an ECS event and want to make sure it's + /// been handled before continuing. + fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> { + let ecs = self.ecs.lock(); + let update_broadcast = ecs.resource::(); + update_broadcast.subscribe() + } + + /// Wait for one tick using [`Self::get_tick_broadcaster`]. + /// + /// If you're going to run this in a loop, you may want to use that function + /// instead and use the `Receiver` from it as it'll be more efficient. + async fn wait_one_tick(&self) { + let mut receiver = self.get_tick_broadcaster(); + // wait for the next tick + let _ = receiver.recv().await; + } + /// Waits for one ECS Update using [`Self::get_update_broadcaster`]. + /// + /// If you're going to run this in a loop, you may want to use that function + /// instead and use the `Receiver` from it as it'll be more efficient. + async fn wait_one_update(&self) { + let mut receiver = self.get_update_broadcaster(); + // wait for the next tick + let _ = receiver.recv().await; + } + async fn mine(&self, position: BlockPos) { self.start_mining(position); // vanilla sends an extra swing arm packet when we start mining diff --git a/azalea/src/pathfinder/goals.rs b/azalea/src/pathfinder/goals.rs index 0fb72446..9052c8fd 100644 --- a/azalea/src/pathfinder/goals.rs +++ b/azalea/src/pathfinder/goals.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use super::costs::{COST_HEURISTIC, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST}; -pub trait Goal: Debug { +pub trait Goal: Debug + Send + Sync { #[must_use] fn heuristic(&self, n: BlockPos) -> f32; #[must_use] @@ -100,6 +100,11 @@ pub struct RadiusGoal { pub pos: Vec3, pub radius: f32, } +impl RadiusGoal { + pub fn new(pos: Vec3, radius: f32) -> Self { + Self { pos, radius } + } +} impl Goal for RadiusGoal { fn heuristic(&self, n: BlockPos) -> f32 { let n = n.center(); diff --git a/azalea/src/pathfinder/mining.rs b/azalea/src/pathfinder/mining.rs index 40cdf8a2..a985ca71 100644 --- a/azalea/src/pathfinder/mining.rs +++ b/azalea/src/pathfinder/mining.rs @@ -27,16 +27,16 @@ impl MiningCache { let mut water_block_state_range_min = BlockStateIntegerRepr::MAX; let mut water_block_state_range_max = BlockStateIntegerRepr::MIN; for state in water_block_states { - water_block_state_range_min = water_block_state_range_min.min(state.id); - water_block_state_range_max = water_block_state_range_max.max(state.id); + water_block_state_range_min = water_block_state_range_min.min(state.id()); + water_block_state_range_max = water_block_state_range_max.max(state.id()); } let water_block_state_range = water_block_state_range_min..=water_block_state_range_max; let mut lava_block_state_range_min = BlockStateIntegerRepr::MAX; let mut lava_block_state_range_max = BlockStateIntegerRepr::MIN; for state in lava_block_states { - lava_block_state_range_min = lava_block_state_range_min.min(state.id); - lava_block_state_range_max = lava_block_state_range_max.max(state.id); + lava_block_state_range_min = lava_block_state_range_min.min(state.id()); + lava_block_state_range_max = lava_block_state_range_max.max(state.id()); } let lava_block_state_range = lava_block_state_range_min..=lava_block_state_range_max; @@ -65,7 +65,7 @@ impl MiningCache { azalea_registry::Block::RedConcretePowder.into(), azalea_registry::Block::BlackConcretePowder.into(), ]; - falling_blocks.sort_unstable_by_key(|block| block.id); + falling_blocks.sort_unstable_by_key(|block| block.id()); Self { block_state_id_costs: UnsafeCell::new(IntMap::default()), @@ -84,7 +84,7 @@ impl MiningCache { // SAFETY: mining is single-threaded, so this is safe let block_state_id_costs = unsafe { &mut *self.block_state_id_costs.get() }; - if let Some(cost) = block_state_id_costs.get(&block.id) { + if let Some(cost) = block_state_id_costs.get(&block.id()) { *cost } else { let best_tool_result = best_tool_in_hotbar_for_block(block, inventory_menu); @@ -92,7 +92,7 @@ impl MiningCache { cost += BLOCK_BREAK_ADDITIONAL_PENALTY; - block_state_id_costs.insert(block.id, cost); + block_state_id_costs.insert(block.id(), cost); cost } } @@ -101,14 +101,14 @@ impl MiningCache { // this already runs in about 1 nanosecond, so if you wanna try optimizing it at // least run the benchmarks (in benches/checks.rs) - self.water_block_state_range.contains(&block.id) - || self.lava_block_state_range.contains(&block.id) + self.water_block_state_range.contains(&block.id()) + || self.lava_block_state_range.contains(&block.id()) || is_waterlogged(block) } pub fn is_falling_block(&self, block: BlockState) -> bool { self.falling_blocks - .binary_search_by_key(&block.id, |block| block.id) + .binary_search_by_key(&block.id(), |block| block.id()) .is_ok() } } diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 0db627ac..bd61c4eb 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -47,7 +47,6 @@ use self::debug::debug_render_path_with_particles; use self::goals::Goal; use self::mining::MiningCache; use self::moves::{ExecuteCtx, IsReachedCtx, SuccessorsFn}; -use crate::WalkDirection; use crate::app::{App, Plugin}; use crate::bot::{JumpEvent, LookAtEvent}; use crate::ecs::{ @@ -58,6 +57,7 @@ use crate::ecs::{ system::{Commands, Query, Res}, }; use crate::pathfinder::{astar::a_star, moves::PathfinderCtx, world::CachedWorld}; +use crate::{BotClientExt, WalkDirection}; #[derive(Clone, Default)] pub struct PathfinderPlugin; @@ -103,7 +103,7 @@ impl Plugin for PathfinderPlugin { /// A component that makes this client able to pathfind. #[derive(Component, Default, Clone)] pub struct Pathfinder { - pub goal: Option>, + pub goal: Option>, pub successors_fn: Option, pub is_calculating: bool, pub allow_mining: bool, @@ -134,7 +134,7 @@ pub struct ExecutingPath { pub struct GotoEvent { /// The local bot entity that will do the pathfinding and execute the path. pub entity: Entity, - pub goal: Arc, + pub goal: Arc, /// The function that's used for checking what moves are possible. Usually /// `pathfinder::moves::default_move` pub successors_fn: SuccessorsFn, @@ -180,22 +180,40 @@ pub fn add_default_pathfinder( } pub trait PathfinderClientExt { - fn goto(&self, goal: impl Goal + Send + Sync + 'static); - fn goto_without_mining(&self, goal: impl Goal + Send + Sync + 'static); + fn goto(&self, goal: impl Goal + 'static) -> impl Future; + fn start_goto(&self, goal: impl Goal + 'static); + fn start_goto_without_mining(&self, goal: impl Goal + 'static); fn stop_pathfinding(&self); + fn wait_until_goto_target_reached(&self) -> impl Future; + fn is_goto_target_reached(&self) -> bool; } impl PathfinderClientExt for azalea_client::Client { + /// Pathfind to the given goal and wait until either the target is reached + /// or the pathfinding is canceled. + /// + /// ``` + /// # use azalea::prelude::*; + /// # use azalea::{BlockPos, pathfinder::goals::BlockPosGoal}; + /// # async fn example(bot: &Client) { + /// bot.goto(BlockPosGoal(BlockPos::new(0, 70, 0))).await; + /// # } + /// ``` + async fn goto(&self, goal: impl Goal + 'static) { + self.start_goto(goal); + self.wait_until_goto_target_reached().await; + } + /// Start pathfinding to a given goal. /// /// ``` /// # use azalea::prelude::*; /// # use azalea::{BlockPos, pathfinder::goals::BlockPosGoal}; /// # fn example(bot: &Client) { - /// bot.goto(BlockPosGoal(BlockPos::new(0, 70, 0))); + /// bot.start_goto(BlockPosGoal(BlockPos::new(0, 70, 0))); /// # } /// ``` - fn goto(&self, goal: impl Goal + Send + Sync + 'static) { + fn start_goto(&self, goal: impl Goal + 'static) { self.ecs.lock().send_event(GotoEvent { entity: self.entity, goal: Arc::new(goal), @@ -206,9 +224,9 @@ impl PathfinderClientExt for azalea_client::Client { }); } - /// Same as [`goto`](Self::goto). but the bot won't break any blocks while - /// executing the path. - fn goto_without_mining(&self, goal: impl Goal + Send + Sync + 'static) { + /// Same as [`start_goto`](Self::start_goto). but the bot won't break any + /// blocks while executing the path. + fn start_goto_without_mining(&self, goal: impl Goal + 'static) { self.ecs.lock().send_event(GotoEvent { entity: self.entity, goal: Arc::new(goal), @@ -225,6 +243,26 @@ impl PathfinderClientExt for azalea_client::Client { force: false, }); } + + /// Waits forever until the bot no longer has a pathfinder goal. + async fn wait_until_goto_target_reached(&self) { + // we do this to make sure the event got handled before we start checking + // is_goto_target_reached + self.wait_one_update().await; + + let mut tick_broadcaster = self.get_tick_broadcaster(); + while !self.is_goto_target_reached() { + // check every tick + tick_broadcaster.recv().await.unwrap(); + } + } + + fn is_goto_target_reached(&self) -> bool { + self.map_get_component::(|p| { + p.map(|p| p.goal.is_none() && !p.is_calculating) + .unwrap_or(true) + }) + } } #[derive(Component)] @@ -331,7 +369,7 @@ pub fn goto_listener( pub struct CalculatePathOpts { pub entity: Entity, pub start: BlockPos, - pub goal: Arc, + pub goal: Arc, pub successors_fn: SuccessorsFn, pub world_lock: Arc>, pub goto_id_atomic: Arc, diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs index c50791b8..b89f0761 100644 --- a/azalea/src/pathfinder/world.rs +++ b/azalea/src/pathfinder/world.rs @@ -194,8 +194,7 @@ impl CachedWorld { let mut passable_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new(); let mut solid_bitset = FixedBitSet::<{ 4096_usize.div_ceil(8) }>::new(); for i in 0..4096 { - let block_state_id = section.get_at_index(i); - let block_state = BlockState::try_from(block_state_id).unwrap_or(BlockState::AIR); + let block_state = section.get_at_index(i); if is_block_state_passable(block_state) { passable_bitset.set(i); } @@ -304,9 +303,7 @@ impl CachedWorld { let west_is_in_same_section = section_block_pos.x != 0; let Some(mining_cost) = self.with_section(section_pos, |section| { - let block_state = - BlockState::try_from(section.get_at_index(u16::from(section_block_pos) as usize)) - .unwrap_or_default(); + let block_state = section.get_at_index(u16::from(section_block_pos) as usize); let mining_cost = mining_cache.cost_for(block_state); if mining_cost == f32::INFINITY { @@ -316,10 +313,7 @@ impl CachedWorld { // if there's a falling block or liquid above this block, abort if up_is_in_same_section { - let up_block = BlockState::try_from( - section.get_at_index(u16::from(section_block_pos.up(1)) as usize), - ) - .unwrap_or_default(); + let up_block = section.get_at_index(u16::from(section_block_pos.up(1)) as usize); if mining_cache.is_liquid(up_block) || mining_cache.is_falling_block(up_block) { return f32::INFINITY; } @@ -327,10 +321,8 @@ impl CachedWorld { // if there's a liquid to the north of this block, abort if north_is_in_same_section { - let north_block = BlockState::try_from( - section.get_at_index(u16::from(section_block_pos.north(1)) as usize), - ) - .unwrap_or_default(); + let north_block = + section.get_at_index(u16::from(section_block_pos.north(1)) as usize); if mining_cache.is_liquid(north_block) { return f32::INFINITY; } @@ -338,10 +330,8 @@ impl CachedWorld { // liquid to the east if east_is_in_same_section { - let east_block = BlockState::try_from( - section.get_at_index(u16::from(section_block_pos.east(1)) as usize), - ) - .unwrap_or_default(); + let east_block = + section.get_at_index(u16::from(section_block_pos.east(1)) as usize); if mining_cache.is_liquid(east_block) { return f32::INFINITY; } @@ -349,10 +339,8 @@ impl CachedWorld { // liquid to the south if south_is_in_same_section { - let south_block = BlockState::try_from( - section.get_at_index(u16::from(section_block_pos.south(1)) as usize), - ) - .unwrap_or_default(); + let south_block = + section.get_at_index(u16::from(section_block_pos.south(1)) as usize); if mining_cache.is_liquid(south_block) { return f32::INFINITY; } @@ -360,10 +348,8 @@ impl CachedWorld { // liquid to the west if west_is_in_same_section { - let west_block = BlockState::try_from( - section.get_at_index(u16::from(section_block_pos.west(1)) as usize), - ) - .unwrap_or_default(); + let west_block = + section.get_at_index(u16::from(section_block_pos.west(1)) as usize); if mining_cache.is_liquid(west_block) { return f32::INFINITY; } @@ -391,10 +377,7 @@ impl CachedWorld { let check_should_avoid_this_block = |pos: BlockPos, check: &dyn Fn(BlockState) -> bool| { let block_state = self .with_section(ChunkSectionPos::from(pos), |section| { - BlockState::try_from( - section.get_at_index(u16::from(ChunkSectionBlockPos::from(pos)) as usize), - ) - .unwrap_or_default() + section.get_at_index(u16::from(ChunkSectionBlockPos::from(pos)) as usize) }) .unwrap_or_default(); check(block_state) diff --git a/codegen/lib/code/shapes.py b/codegen/lib/code/shapes.py index 5d5f826d..0896bd82 100644 --- a/codegen/lib/code/shapes.py +++ b/codegen/lib/code/shapes.py @@ -151,18 +151,18 @@ pub trait BlockWithShape {{ impl BlockWithShape for BlockState {{ fn collision_shape(&self) -> &'static VoxelShape {{ - COLLISION_SHAPES_MAP.get(self.id as usize).unwrap_or(&&SHAPE1) + COLLISION_SHAPES_MAP.get(self.id() as usize).unwrap_or(&&SHAPE1) }} fn outline_shape(&self) -> &'static VoxelShape {{ - OUTLINE_SHAPES_MAP.get(self.id as usize).unwrap_or(&&SHAPE1) + OUTLINE_SHAPES_MAP.get(self.id() as usize).unwrap_or(&&SHAPE1) }} fn is_collision_shape_empty(&self) -> bool {{ - matches!(self.id, {empty_shape_match_code}) + matches!(self.id(), {empty_shape_match_code}) }} fn is_collision_shape_full(&self) -> bool {{ - matches!(self.id, {block_shape_match_code}) + matches!(self.id(), {block_shape_match_code}) }} }} diff --git a/codegen/lib/utils.py b/codegen/lib/utils.py index 162dd0fa..fd1e553b 100644 --- a/codegen/lib/utils.py +++ b/codegen/lib/utils.py @@ -10,7 +10,7 @@ def to_snake_case(name: str): def to_camel_case(name: str): - s = re.sub('[_ ](\w)', lambda m: m.group(1).upper(), + s = re.sub(r'[_ ](\w)', lambda m: m.group(1).upper(), name.replace('.', '_').replace('/', '_')) s = upper_first_letter(s) # if the first character is a number, we need to add an underscore