aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2026-05-06 03:36:16 -0100
committermat <git@matdoes.dev>2026-05-07 08:05:58 -1200
commit9ffd0e80bbb3feace231553d6539124585b03e3c (patch)
treead8df7d07df9f8f542e288c263d76b8ffd637416
parent4d1f430408dc6854c1af5fb14b2641e293a9cf43 (diff)
downloadazalea-drasl-9ffd0e80bbb3feace231553d6539124585b03e3c.tar.xz
change panicking functions in Client and EntityRef to return an AzaleaResult instead
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock1
-rw-r--r--azalea-client/src/plugins/mining.rs1
-rw-r--r--azalea/Cargo.toml1
-rw-r--r--azalea/examples/testbot/commands.rs2
-rw-r--r--azalea/examples/testbot/killaura.rs4
-rw-r--r--azalea/examples/testbot/main.rs4
-rw-r--r--azalea/src/auto_tool.rs11
-rw-r--r--azalea/src/client_impl/attack.rs4
-rw-r--r--azalea/src/client_impl/client_information.rs11
-rw-r--r--azalea/src/client_impl/entity_query.rs159
-rw-r--r--azalea/src/client_impl/error.rs29
-rw-r--r--azalea/src/client_impl/interact.rs6
-rw-r--r--azalea/src/client_impl/inventory.rs10
-rw-r--r--azalea/src/client_impl/mining.rs2
-rw-r--r--azalea/src/client_impl/mod.rs106
-rw-r--r--azalea/src/client_impl/movement.rs26
-rw-r--r--azalea/src/container.rs59
-rw-r--r--azalea/src/entity_ref/mod.rs52
-rw-r--r--azalea/src/entity_ref/shared_impls.rs55
-rw-r--r--azalea/src/lib.rs10
-rw-r--r--azalea/src/pathfinder/mod.rs9
-rw-r--r--codegen/lib/code/tags.py1
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
diff --git a/Cargo.lock b/Cargo.lock
index 13313b18..244194dd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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])