diff options
| author | mat <git@matdoes.dev> | 2026-05-06 03:36:16 -0100 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2026-05-07 08:05:58 -1200 |
| commit | 9ffd0e80bbb3feace231553d6539124585b03e3c (patch) | |
| tree | ad8df7d07df9f8f542e288c263d76b8ffd637416 | |
| parent | 4d1f430408dc6854c1af5fb14b2641e293a9cf43 (diff) | |
| download | azalea-drasl-9ffd0e80bbb3feace231553d6539124585b03e3c.tar.xz | |
change panicking functions in Client and EntityRef to return an AzaleaResult instead
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | azalea-client/src/plugins/mining.rs | 1 | ||||
| -rw-r--r-- | azalea/Cargo.toml | 1 | ||||
| -rw-r--r-- | azalea/examples/testbot/commands.rs | 2 | ||||
| -rw-r--r-- | azalea/examples/testbot/killaura.rs | 4 | ||||
| -rw-r--r-- | azalea/examples/testbot/main.rs | 4 | ||||
| -rw-r--r-- | azalea/src/auto_tool.rs | 11 | ||||
| -rw-r--r-- | azalea/src/client_impl/attack.rs | 4 | ||||
| -rw-r--r-- | azalea/src/client_impl/client_information.rs | 11 | ||||
| -rw-r--r-- | azalea/src/client_impl/entity_query.rs | 159 | ||||
| -rw-r--r-- | azalea/src/client_impl/error.rs | 29 | ||||
| -rw-r--r-- | azalea/src/client_impl/interact.rs | 6 | ||||
| -rw-r--r-- | azalea/src/client_impl/inventory.rs | 10 | ||||
| -rw-r--r-- | azalea/src/client_impl/mining.rs | 2 | ||||
| -rw-r--r-- | azalea/src/client_impl/mod.rs | 106 | ||||
| -rw-r--r-- | azalea/src/client_impl/movement.rs | 26 | ||||
| -rw-r--r-- | azalea/src/container.rs | 59 | ||||
| -rw-r--r-- | azalea/src/entity_ref/mod.rs | 52 | ||||
| -rw-r--r-- | azalea/src/entity_ref/shared_impls.rs | 55 | ||||
| -rw-r--r-- | azalea/src/lib.rs | 10 | ||||
| -rw-r--r-- | azalea/src/pathfinder/mod.rs | 9 | ||||
| -rw-r--r-- | codegen/lib/code/tags.py | 1 |
23 files changed, 323 insertions, 241 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f5371705..37696906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ is breaking anyways, semantic versioning is not followed. ### Changed - Re-export `azalea-chat` from `azalea`, and move the other `azalea::chat` module to `azalea::client_chat`. +- Many functions in `Client` and `EntityRef` now return an `AzaleaResult` instead of panicking when getting entity data fails. ### Fixed @@ -268,6 +268,7 @@ dependencies = [ "rand 0.10.0", "rustc-hash", "serde", + "thiserror 2.0.18", "tokio", "tracing", "uuid", diff --git a/azalea-client/src/plugins/mining.rs b/azalea-client/src/plugins/mining.rs index bcbad747..c5dcb8e7 100644 --- a/azalea-client/src/plugins/mining.rs +++ b/azalea-client/src/plugins/mining.rs @@ -130,6 +130,7 @@ fn handle_auto_mine( /// /// This is only present if we're currently mining a block. #[derive(Clone, Component, Debug)] +#[component(storage = "SparseSet")] pub struct Mining { pub pos: BlockPos, pub dir: Direction, diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 595018e0..d67bfe0b 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -39,6 +39,7 @@ parking_lot.workspace = true radix-heap.workspace = true rustc-hash.workspace = true serde = { workspace = true, optional = true } +thiserror.workspace = true tokio.workspace = true tracing.workspace = true uuid.workspace = true diff --git a/azalea/examples/testbot/commands.rs b/azalea/examples/testbot/commands.rs index e8a9855f..03705617 100644 --- a/azalea/examples/testbot/commands.rs +++ b/azalea/examples/testbot/commands.rs @@ -37,6 +37,8 @@ impl CommandSource { .any_entity_by::<&GameProfileComponent, With<Player>>( |profile: &GameProfileComponent| profile.name == username, ) + .ok() + .flatten() } } diff --git a/azalea/examples/testbot/killaura.rs b/azalea/examples/testbot/killaura.rs index 136ae7dd..37f65ab2 100644 --- a/azalea/examples/testbot/killaura.rs +++ b/azalea/examples/testbot/killaura.rs @@ -13,7 +13,7 @@ pub fn tick(bot: Client, state: State) -> eyre::Result<()> { if bot.has_attack_cooldown() { return Ok(()); } - let bot_position = bot.eye_position(); + let bot_position = bot.eye_position()?; let nearest_entity = bot.nearest_entity_by::<&Position, ( With<AbstractMonster>, @@ -22,7 +22,7 @@ pub fn tick(bot: Client, state: State) -> eyre::Result<()> { )>(|position: &Position| { let distance = bot_position.distance_to(**position); distance < 4. - }); + })?; if let Some(nearest_entity) = nearest_entity { println!("attacking {nearest_entity:?}"); diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs index 034a80ad..806780b7 100644 --- a/azalea/examples/testbot/main.rs +++ b/azalea/examples/testbot/main.rs @@ -189,10 +189,10 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result if let Some(following) = &*state.following_entity.lock() && following.is_alive() { - let goal = RadiusGoal::new(following.position(), 3.); + let goal = RadiusGoal::new(following.position()?, 3.); if bot.is_calculating_path() { // keep waiting - } else if !goal.success(bot.position().into()) || bot.is_executing_path() { + } else if !goal.success(bot.position()?.into()) || bot.is_executing_path() { bot.start_goto_with_opts( goal, PathfinderOpts::new() diff --git a/azalea/src/auto_tool.rs b/azalea/src/auto_tool.rs index 4f59d4a9..8ab64d6b 100644 --- a/azalea/src/auto_tool.rs +++ b/azalea/src/auto_tool.rs @@ -4,7 +4,7 @@ use azalea_entity::{ActiveEffects, Attributes, FluidOnEyes, Physics, inventory:: use azalea_inventory::{ItemStack, Menu, components}; use azalea_registry::builtin::{BlockKind, EntityKind}; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; #[derive(Debug)] pub struct BestToolResult { @@ -13,7 +13,7 @@ pub struct BestToolResult { } impl Client { - pub fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult { + pub fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> AzaleaResult<BestToolResult> { self.query_self::<( &Inventory, &Physics, @@ -35,15 +35,16 @@ impl Client { ) } - pub async fn mine_with_auto_tool(&self, block_pos: BlockPos) { + pub async fn mine_with_auto_tool(&self, block_pos: BlockPos) -> AzaleaResult<()> { let block_state = self - .world() + .world()? .read() .get_block_state(block_pos) .unwrap_or_default(); - let best_tool_result = self.best_tool_in_hotbar_for_block(block_state); + let best_tool_result = self.best_tool_in_hotbar_for_block(block_state)?; self.set_selected_hotbar_slot(best_tool_result.index as u8); self.mine(block_pos).await; + Ok(()) } } diff --git a/azalea/src/client_impl/attack.rs b/azalea/src/client_impl/attack.rs index 16721c1c..cdb4c0b1 100644 --- a/azalea/src/client_impl/attack.rs +++ b/azalea/src/client_impl/attack.rs @@ -22,9 +22,9 @@ impl Client { /// /// Also see [`Client::attack_cooldown_remaining_ticks`]. pub fn has_attack_cooldown(&self) -> bool { - let Some(attack_strength_scale) = self.get_component::<AttackStrengthScale>() else { + let Ok(attack_strength_scale) = self.component::<AttackStrengthScale>() else { // they don't even have an AttackStrengthScale so they probably can't even - // attack? whatever, just return false + // attack. whatever, just return false return false; }; **attack_strength_scale < 1.0 diff --git a/azalea/src/client_impl/client_information.rs b/azalea/src/client_impl/client_information.rs index b3cf7927..3b408d58 100644 --- a/azalea/src/client_impl/client_information.rs +++ b/azalea/src/client_impl/client_information.rs @@ -2,7 +2,7 @@ use azalea_client::ClientInformation; use azalea_protocol::packets::game; use tracing::debug; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; impl Client { /// Tell the server we changed our game options (i.e. render distance, main @@ -20,10 +20,13 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub fn set_client_information(&self, client_information: ClientInformation) { + pub fn set_client_information( + &self, + client_information: ClientInformation, + ) -> AzaleaResult<()> { self.query_self::<&mut ClientInformation, _>(|mut ci| { *ci = client_information.clone(); - }); + })?; if self.logged_in() { debug!( @@ -34,5 +37,7 @@ impl Client { client_information, }); } + + Ok(()) } } diff --git a/azalea/src/client_impl/entity_query.rs b/azalea/src/client_impl/entity_query.rs index 0c123b78..c71cd180 100644 --- a/azalea/src/client_impl/entity_query.rs +++ b/azalea/src/client_impl/entity_query.rs @@ -9,11 +9,13 @@ use bevy_ecs::{ query::{QueryData, QueryEntityError, QueryFilter, QueryItem, ROQueryItem, With, Without}, world::World, }; -use parking_lot::{ - MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, -}; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; -use crate::{Client, entity_ref::EntityRef}; +use crate::{ + Client, + client_impl::error::{AzaleaResult, MissingComponentError}, + entity_ref::EntityRef, +}; impl Client { /// Get a component from the client. @@ -27,9 +29,6 @@ impl Client { /// it may be simpler for you to immediately clone the component after /// accessing it. /// - /// If the component isn't guaranteed to be present, consider using - /// [`Self::get_component`] instead. - /// /// To do more complex queries or to mutate data, see [`Self::query_self`]. /// /// To access data about other entities, you can use @@ -38,11 +37,6 @@ impl Client { /// You may also use [`Self::ecs`] directly if you need more control over /// when the ECS is locked. /// - /// # Panics - /// - /// This will panic if the component doesn't exist on the client. Use - /// [`Self::get_component`] to avoid this. - /// /// # Examples /// /// ``` @@ -50,21 +44,19 @@ impl Client { /// # fn example(client: &azalea::Client) { /// let world_name = client.component::<WorldName>(); /// # } - pub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T> { - self.get_component::<T>().unwrap_or_else(|| { - panic!( - "Our client is missing a required component: {:?}", - any::type_name::<&T>() - ) + pub fn component<T: Component>( + &self, + ) -> Result<MappedRwLockReadGuard<'_, T>, MissingComponentError> { + self.entity_component::<T>(self.entity).map_err(|mut err| { + err.entity_description = "Player"; + err }) } - /// Get a component on this client, or `None` if it doesn't exist. - /// - /// If the component is guaranteed to be present, consider using - /// [`Self::component`]. Also see that function for more details. + #[doc(hidden)] + #[deprecated = "replaced with `Self::component`."] pub fn get_component<T: Component>(&self) -> Option<MappedRwLockReadGuard<'_, T>> { - self.get_entity_component::<T>(self.entity) + self.component().ok() } /// Query the ECS for data from our client entity. @@ -87,19 +79,18 @@ impl Client { /// /// This will panic if the client is missing a component required by the /// query. Consider using [`Self::try_query_self`] to avoid this. - pub fn query_self<D: QueryData, R>(&self, f: impl FnOnce(QueryItem<D>) -> R) -> R { - self.try_query_self::<D, R>(f).unwrap_or_else(|_| { - panic!( - "`Client::query_self` failed when querying for {:?}", - any::type_name::<D>() - ) + pub fn query_self<D: QueryData, R>( + &self, + f: impl FnOnce(QueryItem<D>) -> R, + ) -> AzaleaResult<R> { + self.query_entity(self.entity, f).map_err(|mut err| { + err.entity_description = "Player"; + err }) } - /// Query the ECS for data from our client entity, or return `None` if the - /// query failed. - /// - /// Also see [`Self::query_self`]. + #[doc(hidden)] + #[deprecated = "replaced with `Self::query_self`."] pub fn try_query_self<D: QueryData, R>( &self, f: impl FnOnce(QueryItem<D>) -> R, @@ -111,35 +102,33 @@ impl Client { /// Query the ECS for data from an entity. /// - /// Note that it is often simpler to use [`Self::entity_component`]. - /// - /// To query the client, you should use [`Self::query_self`]. - /// /// You can also use this to mutate data on an entity. /// - /// # Panics + /// Note that it is often simpler to use [`Self::entity_component`]. To + /// query the client, you should use [`Self::query_self`]. + /// + /// # Errors /// - /// This will panic if the entity doesn't exist or if the query isn't valid - /// for the entity. For a non-panicking version, you may use - /// [`Self::try_query_entity`]. + /// This will return an error if the entity doesn't exist or if the query + /// isn't valid for the entity. pub fn query_entity<D: QueryData, R>( &self, entity: Entity, f: impl FnOnce(QueryItem<D>) -> R, - ) -> R { - self.try_query_entity(entity, f).unwrap_or_else(|_| { - panic!( - "Querying entity {entity} failed when getting {:?}", - any::type_name::<D>() - ) - }) + ) -> AzaleaResult<R> { + let mut ecs = self.ecs.write(); + let mut qs = ecs.query::<D>(); + qs.get_mut(&mut ecs, entity) + .map(f) + .map_err(|_| MissingComponentError { + entity_description: "Entity", + entity, + component: any::type_name::<D>(), + }) } - /// A convenience function for getting components from any entity, or None - /// if the query fails. - /// - /// If you're sure that the entity exists and that the query will succeed, - /// you can use [`Self::query_entity`]. + #[doc(hidden)] + #[deprecated = "replaced with `Self::query_entity`."] pub fn try_query_entity<D: QueryData, R>( &self, entity: Entity, @@ -158,9 +147,10 @@ impl Client { pub fn any_entity_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Option<EntityRef> { - self.any_entity_id_by(predicate) - .map(|e| self.entity_ref_for(e)) + ) -> AzaleaResult<Option<EntityRef>> { + Ok(self + .any_entity_id_by(predicate)? + .map(|e| self.entity_ref_for(e))) } /// Quickly returns a lightweight [`Entity`] for an arbitrary entity that /// matches the given predicate function that is in the same @@ -190,9 +180,9 @@ impl Client { pub fn any_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Option<Entity> { - let world_name = self.get_component::<WorldName>()?.clone(); - predicate.find_any(self.ecs.clone(), &world_name) + ) -> AzaleaResult<Option<Entity>> { + let world_name = self.component::<WorldName>()?.clone(); + Ok(predicate.find_any(self.ecs.clone(), &world_name)) } /// Return an [`EntityRef`] for the nearest entity that matches the @@ -207,9 +197,10 @@ impl Client { pub fn nearest_entity_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Option<EntityRef> { - self.nearest_entity_id_by(predicate) - .map(|e| self.entity_ref_for(e)) + ) -> AzaleaResult<Option<EntityRef>> { + Ok(self + .nearest_entity_id_by(predicate)? + .map(|e| self.entity_ref_for(e))) } /// Return a lightweight [`Entity`] for the nearest entity that matches the /// given predicate function. @@ -223,8 +214,8 @@ impl Client { pub fn nearest_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Option<Entity> { - self.nearest_entity_ids_by(predicate).first().copied() + ) -> AzaleaResult<Option<Entity>> { + Ok(self.nearest_entity_ids_by(predicate)?.first().copied()) } /// Returns an array of all [`EntityRef`]s in the world that match the @@ -236,17 +227,18 @@ impl Client { pub fn nearest_entities_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Box<[EntityRef]> { - self.nearest_entity_ids_by(predicate) + ) -> AzaleaResult<Box<[EntityRef]>> { + Ok(self + .nearest_entity_ids_by(predicate)? .into_iter() .map(|e| self.entity_ref_for(e)) - .collect() + .collect()) } /// Returns an array of [`EntityRef`] for all known entities in the world /// that match the given filter, sorted by nearest first. /// /// Also see [`Self::nearest_entities_by`]. - pub fn nearest_entities<F: QueryFilter>(&self) -> Box<[EntityRef]> { + pub fn nearest_entities<F: QueryFilter>(&self) -> AzaleaResult<Box<[EntityRef]>> { self.nearest_entities_by::<(), F>(|_| true) } @@ -258,7 +250,7 @@ impl Client { /// /// If you're in a swarm, this includes all players that are visible by at /// least one client. - pub fn nearby_players(&self) -> Box<[EntityRef]> { + pub fn nearby_players(&self) -> AzaleaResult<Box<[EntityRef]>> { self.nearest_entities::<(With<metadata::Player>, Without<LocalEntity>)>() } @@ -280,19 +272,15 @@ impl Client { pub fn nearest_entity_ids_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, - ) -> Box<[Entity]> { + ) -> AzaleaResult<Box<[Entity]>> { let (world_name, position) = { - let Some(world_name) = self.get_component::<WorldName>() else { - return Box::new([]); - }; - let Some(position) = self.get_component::<Position>() else { - return Box::new([]); - }; + let world_name = self.component::<WorldName>()?; + let position = self.component::<Position>()?; (world_name.clone(), **position) }; - predicate.find_all_sorted(self.ecs.clone(), &world_name, position) + Ok(predicate.find_all_sorted(self.ecs.clone(), &world_name, position)) } /// Get a component from an entity. @@ -315,13 +303,16 @@ impl Client { /// /// This will panic if the component doesn't exist on the entity. Use /// [`Self::get_entity_component`] to avoid this. - pub fn entity_component<T: Component>(&self, entity: Entity) -> MappedRwLockReadGuard<'_, T> { - self.get_entity_component::<T>(entity).unwrap_or_else(|| { - panic!( - "Entity {entity} is missing a required component: {:?}", - any::type_name::<&T>() - ) - }) + pub fn entity_component<T: Component>( + &self, + entity: Entity, + ) -> Result<MappedRwLockReadGuard<'_, T>, MissingComponentError> { + self.get_entity_component::<T>(entity) + .ok_or_else(|| MissingComponentError { + entity_description: "Entity", + entity, + component: any::type_name::<T>(), + }) } /// Get a component from an entity, if it exists. diff --git a/azalea/src/client_impl/error.rs b/azalea/src/client_impl/error.rs new file mode 100644 index 00000000..ce3ecab4 --- /dev/null +++ b/azalea/src/client_impl/error.rs @@ -0,0 +1,29 @@ +use bevy_ecs::entity::Entity; +use thiserror::Error; + +/// An error that occurs when we tried to access data from an entity that it +/// doesn't have. +/// +/// This could happen because the data does not occur for this type of entity, +/// or because the entity is not currently loaded. +/// +/// If this error happened when trying to access data on a client, then it may +/// be because the client isn't currently in a world. +/// +/// As an alias for `Result<T, MissingComponentError>`, you may use +/// [`AzaleaResult`]. Using the `eyre` or `anyhow` crates may also simplify +/// error handling in your bots. +#[derive(Error, Debug)] +#[error("{entity_description} {entity} is missing a required component: '{component}'")] +pub struct MissingComponentError { + /// Should be "Entity" or "Client" + pub entity_description: &'static str, + pub entity: Entity, + pub component: &'static str, +} + +/// An error that occurs when we tried to access data from an entity that it +/// doesn't have. +/// +/// See [`MissingComponentError`] for more details. +pub type AzaleaResult<T> = Result<T, MissingComponentError>; diff --git a/azalea/src/client_impl/interact.rs b/azalea/src/client_impl/interact.rs index b3cda1e2..bec62c20 100644 --- a/azalea/src/client_impl/interact.rs +++ b/azalea/src/client_impl/interact.rs @@ -3,13 +3,13 @@ use azalea_core::{hit_result::HitResult, position::BlockPos}; use azalea_protocol::packets::game::s_interact::InteractionHand; use bevy_ecs::entity::Entity; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; impl Client { /// Returns the current [`HitResult`], which is the block or entity in the /// client's crosshair. - pub fn hit_result(&self) -> HitResult { - (**self.component::<HitResultComponent>()).clone() + pub fn hit_result(&self) -> AzaleaResult<HitResult> { + Ok((**self.component::<HitResultComponent>()?).clone()) } /// Right-click a block. diff --git a/azalea/src/client_impl/inventory.rs b/azalea/src/client_impl/inventory.rs index 72a05136..93e599aa 100644 --- a/azalea/src/client_impl/inventory.rs +++ b/azalea/src/client_impl/inventory.rs @@ -2,7 +2,7 @@ use azalea_client::inventory::SetSelectedHotbarSlotEvent; use azalea_entity::inventory::Inventory; use azalea_inventory::Menu; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; impl Client { /// Return the menu that is currently open, or the player's inventory if no @@ -10,8 +10,8 @@ impl Client { /// /// If you need to interact with the menu, consider using /// [`Self::open_inventory`] instead. - pub fn menu(&self) -> Menu { - self.component::<Inventory>().menu().clone() + pub fn menu(&self) -> AzaleaResult<Menu> { + Ok(self.component::<Inventory>()?.menu().clone()) } /// Returns the index of the hotbar slot that's currently selected. @@ -21,8 +21,8 @@ impl Client { /// the start of [`azalea_inventory::Menu::hotbar_slots_range`]. /// /// You can use [`Self::set_selected_hotbar_slot`] to change it. - pub fn selected_hotbar_slot(&self) -> u8 { - self.component::<Inventory>().selected_hotbar_slot + pub fn selected_hotbar_slot(&self) -> AzaleaResult<u8> { + Ok(self.component::<Inventory>()?.selected_hotbar_slot) } /// Update the selected hotbar slot index. diff --git a/azalea/src/client_impl/mining.rs b/azalea/src/client_impl/mining.rs index 11c74480..daaf2b3f 100644 --- a/azalea/src/client_impl/mining.rs +++ b/azalea/src/client_impl/mining.rs @@ -16,7 +16,7 @@ impl Client { /// Returns true if the client is currently trying to mine a block. pub fn is_mining(&self) -> bool { - self.get_component::<Mining>().is_some() + self.component::<Mining>().is_ok() } /// When enabled, the bot will mine any block that it is looking at if it is diff --git a/azalea/src/client_impl/mod.rs b/azalea/src/client_impl/mod.rs index be711ef1..ba78fee4 100644 --- a/azalea/src/client_impl/mod.rs +++ b/azalea/src/client_impl/mod.rs @@ -34,6 +34,7 @@ use uuid::Uuid; use crate::{ bot::DefaultBotPlugins, + client_impl::error::AzaleaResult, entity_ref::EntityRef, events::{Event, LocalPlayerEvents}, swarm::DefaultSwarmPlugins, @@ -43,6 +44,7 @@ pub mod attack; pub mod chat; pub mod client_information; pub mod entity_query; +pub mod error; pub mod interact; pub mod inventory; pub mod mining; @@ -307,10 +309,13 @@ impl Client { self.ecs.write().write_message(AppExit::Success); } - pub fn with_raw_connection<R>(&self, f: impl FnOnce(&RawConnection) -> R) -> R { + pub fn with_raw_connection<R>(&self, f: impl FnOnce(&RawConnection) -> R) -> AzaleaResult<R> { self.query_self::<&RawConnection, _>(f) } - pub fn with_raw_connection_mut<R>(&self, f: impl FnOnce(Mut<'_, RawConnection>) -> R) -> R { + pub fn with_raw_connection_mut<R>( + &self, + f: impl FnOnce(Mut<'_, RawConnection>) -> R, + ) -> AzaleaResult<R> { self.query_self::<&mut RawConnection, _>(f) } @@ -340,9 +345,9 @@ impl Client { /// component. If it's a normal client, then it'll be the same as the /// world the client has loaded. If the client is using a shared world, /// then the shared world will be a superset of the client's world. - pub fn world(&self) -> Arc<RwLock<World>> { - let world_holder = self.component::<WorldHolder>(); - world_holder.shared.clone() + pub fn world(&self) -> AzaleaResult<Arc<RwLock<World>>> { + let world_holder = self.component::<WorldHolder>()?; + Ok(world_holder.shared.clone()) } /// Get an `RwLock` with a reference to the world that this client has @@ -354,15 +359,15 @@ impl Client { /// let world = client.partial_world(); /// let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some(); /// # } - pub fn partial_world(&self) -> Arc<RwLock<PartialWorld>> { - let world_holder = self.component::<WorldHolder>(); - world_holder.partial.clone() + pub fn partial_world(&self) -> AzaleaResult<Arc<RwLock<PartialWorld>>> { + let world_holder = self.component::<WorldHolder>()?; + Ok(world_holder.partial.clone()) } /// Returns whether we have a received the login packet yet. pub fn logged_in(&self) -> bool { // the login packet tells us the world name - self.query_self::<Option<&WorldName>, _>(|ins| ins.is_some()) + self.query_self::<&WorldName, _>(|_| {}).is_ok() } /// Returns the client as an [`EntityRef`], allowing you to treat it as any @@ -381,31 +386,47 @@ impl Client { /// Get the hunger level of this client, which includes both food and /// saturation. /// - /// This is a shortcut for `self.component::<Hunger>().to_owned()`. - pub fn hunger(&self) -> Hunger { - self.component::<Hunger>().to_owned() + /// This is a shortcut for `self.component::<Hunger>()?.to_owned()`. + pub fn hunger(&self) -> AzaleaResult<Hunger> { + Ok(self.component::<Hunger>()?.to_owned()) } /// Get the experience of this client. /// /// This is a shortcut for `self.component::<Experience>().to_owned()`. - pub fn experience(&self) -> Experience { - self.component::<Experience>().to_owned() + pub fn experience(&self) -> AzaleaResult<Experience> { + Ok(self.component::<Experience>()?.to_owned()) } - /// Get the username of this client. + /// Get the username of this client's account. /// - /// This is a shortcut for - /// `bot.component::<GameProfileComponent>().name.to_owned()`. + /// This is a shortcut for `bot.account().username().to_owned()`. pub fn username(&self) -> String { - self.profile().name.to_owned() + self.account().username().to_owned() + } + /// Get the username of this client, as sent to us by the server. + /// + /// This is a shortcut for `bot.profile()?.name.to_owned()`. + /// + /// In some cases, this may be different from [`Self::username`] if the + /// server sends us a [`GameProfile`] with a mismatching username. Using + /// [`Self::username`] is recommended if you're not sure which one to use. + pub fn server_username(&self) -> AzaleaResult<String> { + Ok(self.profile()?.name.to_owned()) + } + + /// Get the Minecraft UUID of this client's account. + /// + /// This is a shortcut for `**self.component::<EntityUuid>()`. + pub fn uuid(&self) -> Uuid { + self.account().uuid() } /// Get a map of player UUIDs to their information in the tab list. /// /// This is a shortcut for `*bot.component::<TabList>()`. - pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo> { - (**self.component::<TabList>()).clone() + pub fn tab_list(&self) -> AzaleaResult<HashMap<Uuid, PlayerInfo>> { + Ok((**self.component::<TabList>()?).clone()) } /// Returns the [`GameProfile`] for our client. This contains your username, @@ -417,13 +438,17 @@ impl Client { /// the tab list, which you can get from [`Self::tab_list`]. /// /// This as also available from the ECS as [`GameProfileComponent`]. - pub fn profile(&self) -> GameProfile { - (**self.component::<GameProfileComponent>()).clone() + pub fn profile(&self) -> AzaleaResult<GameProfile> { + Ok((**self.component::<GameProfileComponent>()?).clone()) } /// Returns the [`Account`] for our client. pub fn account(&self) -> Account { - self.component::<Account>().clone() + self.component::<Account>() + .expect( + "clients cannot exist without an Account, and Account isn't removed from clients", + ) + .clone() } /// A convenience function to get the Minecraft Uuid of a player by their @@ -431,11 +456,12 @@ impl Client { /// /// You can chain this with [`Client::entity_by_uuid`] to get the ECS /// `Entity` for the player. - pub fn player_uuid_by_username(&self, username: &str) -> Option<Uuid> { - self.tab_list() + pub fn player_uuid_by_username(&self, username: &str) -> AzaleaResult<Option<Uuid>> { + Ok(self + .tab_list()? .values() .find(|player| player.profile.name == username) - .map(|player| player.profile.uuid) + .map(|player| player.profile.uuid)) } /// Get an [`Entity`] in the world by its Minecraft UUID, if it's within @@ -457,7 +483,7 @@ impl Client { /// Get an [`Entity`] in the world by its [`MinecraftEntityId`]. /// /// Also see [`Self::entity_by_uuid`] and [`Self::entity_id_by_uuid`]. - pub fn entity_id_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<Entity> { + pub fn entity_id_by_minecraft_id(&self, id: MinecraftEntityId) -> AzaleaResult<Option<Entity>> { self.query_self::<&EntityIdIndex, _>(|entity_id_index| { entity_id_index.get_by_minecraft_entity(id) }) @@ -465,9 +491,10 @@ impl Client { /// Get an [`EntityRef`] in the world by its [`MinecraftEntityId`]. /// /// Also see [`Self::entity_id_by_uuid`]. - pub fn entity_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<EntityRef> { - self.entity_id_by_minecraft_id(id) - .map(|e| EntityRef::new(self.clone(), e)) + pub fn entity_by_minecraft_id(&self, id: MinecraftEntityId) -> AzaleaResult<Option<EntityRef>> { + Ok(self + .entity_id_by_minecraft_id(id)? + .map(|e| EntityRef::new(self.clone(), e))) } /// Call the given function with the client's [`RegistryHolder`]. @@ -480,10 +507,10 @@ impl Client { pub fn with_registry_holder<R>( &self, f: impl FnOnce(&azalea_core::registry_holder::RegistryHolder) -> R, - ) -> R { - let world = self.world(); + ) -> AzaleaResult<R> { + let world = self.world()?; let registries = &world.read().registries; - f(registries) + Ok(f(registries)) } /// Resolve the given registry to its name. @@ -495,7 +522,7 @@ impl Client { pub fn resolve_registry_name( &self, registry: &impl ResolvableDataRegistry, - ) -> Option<Identifier> { + ) -> AzaleaResult<Option<Identifier>> { self.with_registry_holder(|registries| registry.key(registries).map(|r| r.into_ident())) } @@ -508,7 +535,10 @@ impl Client { /// `.map(|r| r.into_ident())`. /// /// [`Enchantment`]: azalea_registry::data::Enchantment - pub fn resolve_registry_key<R: ResolvableDataRegistry>(&self, registry: &R) -> Option<R::Key> { + pub fn resolve_registry_key<R: ResolvableDataRegistry>( + &self, + registry: &R, + ) -> AzaleaResult<Option<R::Key>> { self.with_registry_holder(|registries| registry.key_owned(registries)) } @@ -525,7 +555,7 @@ impl Client { &self, registry: R, f: impl FnOnce(&Identifier, &R::DeserializesTo) -> Ret, - ) -> Option<Ret> { + ) -> AzaleaResult<Option<Ret>> { self.with_registry_holder(|registries| { registry .resolve(registries) @@ -538,8 +568,6 @@ impl Client { /// /// This is a shortcut for getting the [`TicksConnected`] component. pub fn ticks_connected(&self) -> u64 { - self.get_component::<TicksConnected>() - .map(|c| c.0) - .unwrap_or(0) + self.component::<TicksConnected>().map(|c| c.0).unwrap_or(0) } } diff --git a/azalea/src/client_impl/movement.rs b/azalea/src/client_impl/movement.rs index 9645f09e..4eec15d2 100644 --- a/azalea/src/client_impl/movement.rs +++ b/azalea/src/client_impl/movement.rs @@ -2,9 +2,8 @@ use azalea_client::{ ClientMovementState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection, }; use azalea_entity::{Jumping, LookDirection}; -use parking_lot::MappedRwLockReadGuard; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; impl Client { /// Set whether we're jumping. This acts as if you held space in @@ -14,17 +13,17 @@ impl Client { /// /// If you're making a realistic client, calling this function every tick is /// recommended. - pub fn set_jumping(&self, jumping: bool) { - self.query_self::<&mut Jumping, _>(|mut j| **j = jumping); + pub fn set_jumping(&self, jumping: bool) -> AzaleaResult<()> { + self.query_self::<&mut Jumping, _>(|mut j| **j = jumping) } /// Returns whether the player will try to jump next tick. pub fn jumping(&self) -> bool { - **self.component::<Jumping>() + self.component::<Jumping>().map(|j| **j).unwrap_or_default() } - pub fn set_crouching(&self, crouching: bool) { - self.query_self::<&mut ClientMovementState, _>(|mut p| p.trying_to_crouch = crouching); + pub fn set_crouching(&self, crouching: bool) -> AzaleaResult<()> { + self.query_self::<&mut ClientMovementState, _>(|mut p| p.trying_to_crouch = crouching) } /// Whether the client is currently trying to sneak. @@ -32,6 +31,7 @@ impl Client { /// You may want to check the [`Pose`](azalea_entity::Pose) instead. pub fn crouching(&self) -> bool { self.query_self::<&ClientMovementState, _>(|p| p.trying_to_crouch) + .unwrap_or(false) } /// Sets the direction the client is looking. @@ -40,17 +40,17 @@ impl Client { /// is pitch (looking up and down, between -90 to 90). /// /// You can get these numbers from the vanilla f3 screen. - pub fn set_direction(&self, y_rot: f32, x_rot: f32) { + pub fn set_direction(&self, y_rot: f32, x_rot: f32) -> AzaleaResult<()> { self.query_self::<&mut LookDirection, _>(|mut ld| { ld.update(LookDirection::new(y_rot, x_rot)); - }); + }) } /// Returns the direction the client is looking. /// /// See [`Self::set_direction`] for more details. - pub fn direction(&self) -> LookDirection { - *self.component::<LookDirection>() + pub fn direction(&self) -> AzaleaResult<LookDirection> { + Ok(*self.component::<LookDirection>()?) } /// Start walking in the given direction. @@ -82,8 +82,8 @@ impl Client { /// /// This includes the direction that we're walking/sprinting in, and whether /// we're trying to sprint or crouch. - pub fn movement_state(&self) -> ClientMovementState { - self.component::<ClientMovementState>().clone() + pub fn movement_state(&self) -> AzaleaResult<ClientMovementState> { + Ok(self.component::<ClientMovementState>()?.clone()) } /// Start sprinting in the given direction. diff --git a/azalea/src/container.rs b/azalea/src/container.rs index b4c98415..0a5c8c01 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -17,7 +17,7 @@ use bevy_app::{App, Plugin, Update}; use bevy_ecs::{component::Component, prelude::MessageReader, system::Commands}; use derive_more::Deref; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; pub struct ContainerPlugin; impl Plugin for ContainerPlugin { @@ -37,7 +37,7 @@ impl Client { /// /// ``` /// # use azalea::{prelude::*, registry::builtin::BlockKind}; - /// # async fn example(mut bot: azalea::Client) { + /// # async fn example(mut bot: azalea::Client) -> AzaleaResult<()> { /// let target_pos = bot /// .world() /// .read() @@ -46,10 +46,11 @@ impl Client { /// bot.chat("no chest found"); /// return; /// }; - /// let container = bot.open_container_at(target_pos).await; + /// let container = bot.open_container_at(target_pos).await?; + /// # Ok(()) /// # } /// ``` - pub async fn open_container_at(&self, pos: BlockPos) -> Option<ContainerHandle> { + pub async fn open_container_at(&self, pos: BlockPos) -> AzaleaResult<Option<ContainerHandle>> { self.open_container_at_with_timeout_ticks(pos, Some(20 * 5)) .await } @@ -69,11 +70,15 @@ impl Client { &self, pos: BlockPos, timeout_ticks: Option<usize>, - ) -> Option<ContainerHandle> { + ) -> AzaleaResult<Option<ContainerHandle>> { let mut ticks = self.get_tick_broadcaster(); // wait until it's not air (up to 10 ticks) for _ in 0..10 { - let block = self.world().read().get_block_state(pos).unwrap_or_default(); + let block = self + .world()? + .read() + .get_block_state(pos) + .unwrap_or_default(); if !block.is_collision_shape_empty() { break; } @@ -98,7 +103,7 @@ impl Client { pub async fn wait_for_container_open( &self, timeout_ticks: Option<usize>, - ) -> Option<ContainerHandle> { + ) -> AzaleaResult<Option<ContainerHandle>> { let mut ticks = self.get_tick_broadcaster(); let mut elapsed_ticks = 0; while ticks.recv().await.is_ok() { @@ -111,16 +116,15 @@ impl Client { if let Some(timeout_ticks) = timeout_ticks && elapsed_ticks >= timeout_ticks { - return None; + return Ok(None); } } - let ecs = self.ecs.read(); - let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory"); - if inventory.id == 0 { - None + let inventory_id = self.component::<Inventory>()?.id; + if inventory_id == 0 { + Ok(None) } else { - Some(ContainerHandle::new(inventory.id, self.clone())) + Ok(Some(ContainerHandle::new(inventory_id, self.clone()))) } } @@ -135,13 +139,13 @@ impl Client { /// If you just want to get the items in the player's inventory without /// sending any packets, use [`Client::menu`], [`Menu::player_slots_range`], /// and [`Menu::slots`]. - pub fn open_inventory(&self) -> Option<ContainerHandle> { - let inventory = self.component::<Inventory>(); - if inventory.id == 0 { + pub fn open_inventory(&self) -> AzaleaResult<Option<ContainerHandle>> { + let inventory = self.component::<Inventory>()?; + Ok(if inventory.id == 0 { Some(ContainerHandle::new(0, self.clone())) } else { None - } + }) } /// Returns a [`ContainerHandleRef`] to the client's currently open @@ -155,14 +159,17 @@ impl Client { /// won't trigger anticheats, use [`Client::open_inventory`]. /// /// To open a container in the world, use [`Client::open_container_at`]. - pub fn get_inventory(&self) -> ContainerHandleRef { - ContainerHandleRef::new(self.component::<Inventory>().id, self.clone()) + pub fn get_inventory(&self) -> AzaleaResult<ContainerHandleRef> { + Ok(ContainerHandleRef::new( + self.component::<Inventory>()?.id, + self.clone(), + )) } /// Get the item in the bot's hotbar that is currently being held in its /// main hand. - pub fn get_held_item(&self) -> ItemStack { - self.component::<Inventory>().held_item().clone() + pub fn get_held_item(&self) -> AzaleaResult<ItemStack> { + Ok(self.component::<Inventory>()?.held_item().clone()) } } @@ -209,7 +216,7 @@ impl ContainerHandleRef { /// Note that any modifications you make to the `Menu` you're given will not /// actually cause any packets to be sent. If you're trying to modify your /// inventory, use [`Self::click`] instead - pub fn menu(&self) -> Option<Menu> { + pub fn menu(&self) -> AzaleaResult<Option<Menu>> { self.map_inventory(|inv| { if self.id == 0 { inv.inventory_menu.clone() @@ -219,7 +226,7 @@ impl ContainerHandleRef { }) } - fn map_inventory<R>(&self, f: impl FnOnce(&Inventory) -> R) -> Option<R> { + fn map_inventory<R>(&self, f: impl FnOnce(&Inventory) -> R) -> AzaleaResult<Option<R>> { self.client.query_self::<&Inventory, _>(|inv| { if inv.id == self.id { Some(f(inv)) @@ -235,14 +242,14 @@ impl ContainerHandleRef { /// /// If the container is closed, this will return `None`. pub fn contents(&self) -> Option<Vec<ItemStack>> { - self.menu().map(|menu| menu.contents()) + Some(self.menu().ok()??.contents()) } /// Return the contents of the menu, including the player's inventory. /// /// If the container is closed, this will return `None`. pub fn slots(&self) -> Option<Vec<ItemStack>> { - self.menu().map(|menu| menu.slots()) + Some(self.menu().ok()??.slots()) } /// Returns the title of the container, or `None` if no container is open. @@ -258,7 +265,7 @@ impl ContainerHandleRef { /// ``` pub fn title(&self) -> Option<FormattedText> { self.map_inventory(|inv| inv.container_menu_title.clone()) - .flatten() + .ok()?? } /// A shortcut for [`Self::click`] with `PickupClick::Left`. diff --git a/azalea/src/entity_ref/mod.rs b/azalea/src/entity_ref/mod.rs index e09852c1..db22f555 100644 --- a/azalea/src/entity_ref/mod.rs +++ b/azalea/src/entity_ref/mod.rs @@ -2,7 +2,7 @@ pub mod shared_impls; use std::fmt::Debug; -use azalea_entity::EntityKindComponent; +use azalea_entity::{EntityKindComponent, EntityUuid}; use azalea_registry::builtin::EntityKind; use bevy_ecs::{ component::Component, @@ -10,8 +10,12 @@ use bevy_ecs::{ query::{QueryData, QueryEntityError, QueryItem}, }; use parking_lot::MappedRwLockReadGuard; +use uuid::Uuid; -use crate::Client; +use crate::{ + Client, + client_impl::error::{AzaleaResult, MissingComponentError}, +}; /// A reference to an entity in a world. /// @@ -61,7 +65,9 @@ impl EntityRef { /// # fn example(client: &azalea::Client) { /// let world_name = client.component::<WorldName>(); /// # } - pub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T> { + pub fn component<T: Component>( + &self, + ) -> Result<MappedRwLockReadGuard<'_, T>, MissingComponentError> { self.client.entity_component(self.entity) } @@ -81,23 +87,24 @@ impl EntityRef { /// /// Also see [`Client::query_self`] and [`Client::query_entity`]. /// - /// # Panics + /// # Errors /// - /// This will panic if the entity doesn't exist or is missing a component - /// required by the query. Consider using [`Self::try_query_self`] to - /// avoid this. - pub fn query_self<D: QueryData, R>(&self, f: impl FnOnce(QueryItem<D>) -> R) -> R { + /// This will return an error if the entity doesn't exist or is missing a + /// component required by the query. + pub fn query_self<D: QueryData, R>( + &self, + f: impl FnOnce(QueryItem<D>) -> R, + ) -> AzaleaResult<R> { self.client.query_entity(self.entity, f) } - /// Query the ECS for data from the entity, or return an error if the query - /// fails. - /// - /// Also see [`Self::query_self`]. + #[doc(hidden)] + #[deprecated = "replaced with `Self::query_self`."] pub fn try_query_self<D: QueryData, R>( &self, f: impl FnOnce(QueryItem<D>) -> R, ) -> Result<R, QueryEntityError> { + #[allow(deprecated)] self.client.try_query_entity(self.entity, f) } } @@ -113,8 +120,17 @@ impl Debug for EntityRef { impl EntityRef { /// Returns the type of entity that this is. - pub fn kind(&self) -> EntityKind { - **self.component::<EntityKindComponent>() + pub fn kind(&self) -> AzaleaResult<EntityKind> { + Ok(**self.component::<EntityKindComponent>()?) + } + + /// Get the Minecraft UUID of this entity. + /// + /// Also see [`Client::uuid`]. + pub fn uuid(&self) -> AzaleaResult<Uuid> { + // note: this isn't in shared_impls because the Client counterpart isn't + // fallible + Ok(**self.component::<EntityUuid>()?) } } @@ -134,13 +150,13 @@ impl EntityRef { } /// Look at this entity from the client that created the `EntityRef`. - pub fn look_at(&self) { - self.client.look_at(self.eye_position()); + pub fn look_at(&self) -> AzaleaResult<()> { + Ok(self.client.look_at(self.eye_position()?)) } /// Returns the distance between the client's feet position and this /// entity's feet position. - pub fn distance_to_client(&self) -> f64 { - self.position().distance_to(self.client.position()) + pub fn distance_to_client(&self) -> AzaleaResult<f64> { + Ok(self.position()?.distance_to(self.client.position()?)) } } diff --git a/azalea/src/entity_ref/shared_impls.rs b/azalea/src/entity_ref/shared_impls.rs index ae3ef42a..5841aba9 100644 --- a/azalea/src/entity_ref/shared_impls.rs +++ b/azalea/src/entity_ref/shared_impls.rs @@ -1,12 +1,11 @@ use azalea_core::{entity_id::MinecraftEntityId, position::Vec3}; use azalea_entity::{ - Attributes, Dead, EntityUuid, Physics, Position, dimensions::EntityDimensions, metadata::Health, + Attributes, Dead, Physics, Position, dimensions::EntityDimensions, metadata::Health, }; use azalea_world::WorldName; -use uuid::Uuid; use super::EntityRef; -use crate::Client; +use crate::{Client, client_impl::error::AzaleaResult}; macro_rules! impl_entity_functions { ( $( @@ -50,8 +49,8 @@ impl_entity_functions! { /// To get the client's eye position, use [`Self::eye_position`]. /// /// Also see [`Client::position`]. - pub fn position(&self) -> Vec3 { - **self.component::<Position>() + pub fn position(&self) -> AzaleaResult<Vec3> { + Ok(**self.component::<Position>()?) } Client: @@ -65,8 +64,8 @@ impl_entity_functions! { /// width, height, and eye height. /// /// Also see [`Client::dimensions`] - pub fn dimensions(&self) -> EntityDimensions { - self.component::<EntityDimensions>().clone() + pub fn dimensions(&self) -> AzaleaResult<EntityDimensions> { + Ok(self.component::<EntityDimensions>()?.clone()) } Client: @@ -80,7 +79,7 @@ impl_entity_functions! { /// Get the position of this entity's eyes. /// /// Also see [`Client::eye_position`]. - pub fn eye_position(&self) -> Vec3 { + pub fn eye_position(&self) -> AzaleaResult<Vec3> { self.query_self::<(&Position, &EntityDimensions), _>(|(pos, dim)| { pos.up(dim.eye_height as f64) }) @@ -94,20 +93,8 @@ impl_entity_functions! { /// Get the health of this entity, typically in the range `0..=20`. /// /// Also see [`Client::health`]. - pub fn health(&self) -> f32 { - **self.component::<Health>() - } - - Client: - /// Get the Minecraft UUID of this client. - /// - /// This is a shortcut for `**self.component::<EntityUuid>()`. - EntityRef: - /// Get the Minecraft UUID of this entity. - /// - /// Also see [`Client::uuid`]. - pub fn uuid(&self) -> Uuid { - **self.component::<EntityUuid>() + pub fn health(&self) -> AzaleaResult<f32> { + Ok(**self.component::<Health>()?) } Client: @@ -124,8 +111,8 @@ impl_entity_functions! { /// consider using [`Self::uuid`] instead. /// /// Also see [`Client::minecraft_id`]. - pub fn minecraft_id(&self) -> MinecraftEntityId { - *self.component::<MinecraftEntityId>() + pub fn minecraft_id(&self) -> AzaleaResult<MinecraftEntityId> { + Ok(*self.component::<MinecraftEntityId>()?) } Client: @@ -134,17 +121,19 @@ impl_entity_functions! { EntityRef: /// Returns the attribute values of the entity, which can be used to /// determine things like its movement speed. - pub fn attributes(&self) -> Attributes { + pub fn attributes(&self) -> AzaleaResult<Attributes> { // this *could* return a mapped read guard for performance but that rarely // matters and it's just easier for the user if it doesn't. - self.component::<Attributes>().clone() + Ok(self.component::<Attributes>()?.clone()) } Client: + #[doc(hidden)] #[deprecated = "renamed to `world_name`."] EntityRef: + #[doc(hidden)] #[deprecated = "renamed to `world_name`."] - pub fn instance_name(&self) -> WorldName { + pub fn instance_name(&self) -> AzaleaResult<WorldName> { self.world_name() } @@ -162,8 +151,8 @@ impl_entity_functions! { /// /// Also see [`Client::world_name`], #[doc(alias("dimension_name"))] - pub fn world_name(&self) -> WorldName { - (*self.component::<WorldName>()).clone() + pub fn world_name(&self) -> AzaleaResult<WorldName> { + Ok((*self.component::<WorldName>()?).clone()) } Client: @@ -183,7 +172,7 @@ impl_entity_functions! { /// /// Also see [`Client::is_alive`] and [`Self::exists`]. pub fn is_alive(&self) -> bool { - self.try_query_self::<Option<&Dead>, _>(|dead| dead.is_none()).unwrap_or(false) + self.query_self::<Option<&Dead>, _>(|dead| dead.is_none()).unwrap_or(false) } Client: @@ -197,7 +186,7 @@ impl_entity_functions! { /// /// Also see [`Client::exists`]. pub fn exists(&self) -> bool { - self.try_query_self::<Option<&MinecraftEntityId>, _>(|entity_id| entity_id.is_some()).unwrap_or(false) + self.query_self::<Option<&MinecraftEntityId>, _>(|entity_id| entity_id.is_some()).unwrap_or(false) } Client: @@ -208,7 +197,7 @@ impl_entity_functions! { /// collisions, etc. /// /// Also see [`Client::physics`]. - pub fn physics(&self) -> Physics { - self.component::<Physics>().clone() + pub fn physics(&self) -> AzaleaResult<Physics> { + Ok(self.component::<Physics>()?.clone()) } } diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index f70a77fa..3a2bc0cf 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -47,7 +47,15 @@ pub use azalea_registry as registry; #[doc(hidden)] #[deprecated(note = "renamed to `Identifier`.")] pub type ResourceLocation = azalea_registry::identifier::Identifier; -pub use azalea_chat as chat; + +// TODO: replace this mod with the commented line below +// pub use azalea_chat as chat; +pub mod chat { + pub use azalea_chat::*; + #[deprecated = "moved to `azalea::client_chat`."] + pub type ChatPacket = azalea_client::client_chat::ChatPacket; +} + pub use azalea_registry::identifier::Identifier; pub use azalea_world as world; pub use bevy_app as app; diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index c577b8e3..e5d1912d 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -281,15 +281,16 @@ impl PathfinderClientExt for Client { } } fn is_goto_target_reached(&self) -> bool { - self.get_component::<Pathfinder>() + self.component::<Pathfinder>() + .ok() .is_none_or(|p| p.goal.is_none() && !p.is_calculating) } fn is_executing_path(&self) -> bool { - self.get_component::<ExecutingPath>().is_some() + self.component::<ExecutingPath>().is_ok() } fn is_calculating_path(&self) -> bool { - self.get_component::<Pathfinder>() - .is_some_and(|p| p.is_calculating) + self.component::<Pathfinder>() + .is_ok_and(|p| p.is_calculating) } } diff --git a/codegen/lib/code/tags.py b/codegen/lib/code/tags.py index 2a44d35c..e5a21bdc 100644 --- a/codegen/lib/code/tags.py +++ b/codegen/lib/code/tags.py @@ -62,6 +62,7 @@ use crate::{{{registry_module}::{struct_name}, tags::RegistryTag}}; generated += f"pub static {static_set_name}: LazyLock<RegistryTag<{struct_name}>> = LazyLock::new(|| RegistryTag::new(vec![" if not is_data_registry: + assert protocol_ids is not None # this is important because we binary search registries in some cases # and they need to be sorted by their rust Ord order entries.sort(key=lambda e: protocol_ids[e]) |
