aboutsummaryrefslogtreecommitdiff
path: root/azalea/src/entity_ref
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-12-28 21:54:12 -0600
committerGitHub <noreply@github.com>2025-12-28 21:54:12 -0600
commit39488a6585ce969af93f43ece1ffb1174dc95e1d (patch)
tree49b63b2321b974a7c6425e53b8602a0b4500f092 /azalea/src/entity_ref
parent25e441944412038da2be4e64854e59169d58305b (diff)
downloadazalea-drasl-39488a6585ce969af93f43ece1ffb1174dc95e1d.tar.xz
Implement `EntityRef` (#299)
* start implementing EntityRef struct * use EntityRef and impl more functions for it * fix doctests * typo * slightly reword some docs * update changelog
Diffstat (limited to 'azalea/src/entity_ref')
-rw-r--r--azalea/src/entity_ref/mod.rs123
-rw-r--r--azalea/src/entity_ref/shared_impls.rs178
2 files changed, 301 insertions, 0 deletions
diff --git a/azalea/src/entity_ref/mod.rs b/azalea/src/entity_ref/mod.rs
new file mode 100644
index 00000000..3e09d336
--- /dev/null
+++ b/azalea/src/entity_ref/mod.rs
@@ -0,0 +1,123 @@
+pub mod shared_impls;
+
+use std::fmt::Debug;
+
+use azalea_entity::EntityKindComponent;
+use azalea_registry::builtin::EntityKind;
+use bevy_ecs::{
+ component::Component,
+ entity::Entity,
+ query::{QueryData, QueryItem},
+};
+use parking_lot::MappedRwLockReadGuard;
+
+use crate::Client;
+
+/// A reference to an entity in a world.
+///
+/// This is different from [`Entity`], since you can perform actions with just
+/// an `EntityRef` instead of it only being an identifier.
+///
+/// Most functions on `EntityRef` that return a value will result in a panic if
+/// the client has despawned, so if your code involves waiting, you should check
+/// [`Self::is_alive`] before calling those functions.
+///
+/// Also, since `EntityRef` stores the [`Client`] alongside the entity, this
+/// means that it supports interactions such as [`Self::attack`].
+///
+/// Not to be confused with Bevy's [`EntityRef`](bevy_ecs::world::EntityRef).
+#[derive(Clone)]
+pub struct EntityRef {
+ client: Client,
+ entity: Entity,
+}
+
+impl EntityRef {
+ pub fn new(client: Client, entity: Entity) -> Self {
+ Self { client, entity }
+ }
+
+ /// Returns the ECS identifier for the entity.
+ pub fn id(&self) -> Entity {
+ self.entity
+ }
+
+ /// Get a component on the entity.
+ ///
+ /// This allows you to access certain data stored about the entity that
+ /// isn't accessible in a simpler way.
+ ///
+ /// See [`Client::component`] for more details.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if the component doesn't exist on the client. Use
+ /// [`Self::get_component`] to avoid this.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use azalea_world::InstanceName;
+ /// # fn example(client: &azalea::Client) {
+ /// let world_name = client.component::<InstanceName>();
+ /// # }
+ pub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T> {
+ self.client.entity_component(self.entity)
+ }
+
+ /// 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`].
+ ///
+ /// See [`Client::component`] for more details.
+ pub fn get_component<T: Component>(&self) -> Option<MappedRwLockReadGuard<'_, T>> {
+ self.client.get_entity_component(self.entity)
+ }
+
+ /// Query the ECS for data from the entity.
+ ///
+ /// You can use this to mutate data on the entity.
+ ///
+ /// Also see [`Client::query_self`] and [`Client::query_entity`].
+ ///
+ /// # Panics
+ ///
+ /// This will panic if the entity is missing a component required by the
+ /// query.
+ pub fn query_self<D: QueryData, R>(&self, f: impl FnOnce(QueryItem<D>) -> R) -> R {
+ self.client.query_entity(self.entity, f)
+ }
+}
+
+impl Debug for EntityRef {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("EntityRef")
+ .field("client", &self.client.entity)
+ .field("entity", &self.entity)
+ .finish()
+ }
+}
+
+impl EntityRef {
+ /// Returns the type of entity that this is.
+ pub fn kind(&self) -> EntityKind {
+ **self.component::<EntityKindComponent>()
+ }
+}
+
+impl EntityRef {
+ /// Attack this entity from the client that created this `EntityRef`.
+ ///
+ /// Also see [`Client::attack`].
+ pub fn attack(&self) {
+ self.client.attack(self.entity);
+ }
+
+ /// Right-click this entity from the client that created this `EntityRef`.
+ ///
+ /// See [`Client::entity_interact`] for more information.
+ pub fn interact(&self) {
+ self.client.entity_interact(self.entity);
+ }
+}
diff --git a/azalea/src/entity_ref/shared_impls.rs b/azalea/src/entity_ref/shared_impls.rs
new file mode 100644
index 00000000..3c24b836
--- /dev/null
+++ b/azalea/src/entity_ref/shared_impls.rs
@@ -0,0 +1,178 @@
+use azalea_core::position::Vec3;
+use azalea_entity::{
+ Attributes, Dead, EntityUuid, Position, dimensions::EntityDimensions, metadata::Health,
+};
+use azalea_world::{InstanceName, MinecraftEntityId};
+use uuid::Uuid;
+
+use super::EntityRef;
+use crate::Client;
+
+macro_rules! impl_entity_functions {
+ ( $(
+ Client:
+ $(#[$client_doc:meta])*
+ EntityRef:
+ $(#[$entityref_doc:meta])*
+ pub fn $fn_name:ident (&$fn_self:ident) -> $fn_returns:ty $fn_impl:block
+ )* ) => {
+ $(
+ impl Client {
+ $(#[$client_doc])*
+ pub fn $fn_name(&$fn_self) -> $fn_returns $fn_impl
+ }
+ impl EntityRef {
+ $(#[$entityref_doc])*
+ pub fn $fn_name(&$fn_self) -> $fn_returns $fn_impl
+ }
+ )*
+ };
+}
+
+// these functions are defined this way because we want them to be duplicated
+// for both Client and EntityRef but still have their own documentation
+impl_entity_functions! {
+ Client:
+ /// Get the client's position in the world, which is the same as its feet
+ /// position.
+ ///
+ /// This is a shortcut for `**bot.component::<Position>()`.
+ ///
+ /// To get the client's eye position, use [`Self::eye_position`].
+ ///
+ /// Note that this value is given a default of [`Vec3::ZERO`] when it
+ /// receives the login packet, its true position may be set ticks
+ /// later.
+ EntityRef:
+ /// Get the entity's position in the world, which is the same as its feet
+ /// position.
+ ///
+ /// To get the client's eye position, use [`Self::eye_position`].
+ ///
+ /// Also see [`Client::position`].
+ pub fn position(&self) -> Vec3 {
+ **self.component::<Position>()
+ }
+
+ Client:
+ /// Get the bounding box dimensions for our client, which contains our
+ /// width, height, and eye height.
+ ///
+ /// This is a shortcut for
+ /// `self.component::<EntityDimensions>()`.
+ EntityRef:
+ /// Get the bounding box dimensions for the entity, which contains its
+ /// width, height, and eye height.
+ ///
+ /// Also see [`Client::dimensions`]
+ pub fn dimensions(&self) -> EntityDimensions {
+ self.component::<EntityDimensions>().clone()
+ }
+
+ Client:
+ /// Get the position of this client's eyes.
+ ///
+ /// Also see [`Self::position`].
+ ///
+ /// This is a shortcut for
+ /// `bot.position().up(bot.dimensions().eye_height)`.
+ EntityRef:
+ /// Get the position of this entity's eyes.
+ ///
+ /// Also see [`Client::eye_position`].
+ pub fn eye_position(&self) -> Vec3 {
+ self.query_self::<(&Position, &EntityDimensions), _>(|(pos, dim)| {
+ pos.up(dim.eye_height as f64)
+ })
+ }
+
+ Client:
+ /// Get the health of this client.
+ ///
+ /// This is a shortcut for `*bot.component::<Health>()`.
+ EntityRef:
+ /// Get the health of this entity.
+ ///
+ /// 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>()
+ }
+
+ Client:
+ /// Get the Minecraft ID of this client.
+ ///
+ /// See [`MinecraftEntityId`] for more details. For persistent identifiers,
+ /// consider using [`Self::uuid`] instead.
+ ///
+ /// This is a shortcut for `**self.component::<MinecraftEntityId>()`.
+ EntityRef:
+ /// Get the Minecraft UUID of this entity.
+ ///
+ /// See [`MinecraftEntityId`] for more details. For persistent identifiers,
+ /// consider using [`Self::uuid`] instead.
+ ///
+ /// Also see [`Client::minecraft_id`].
+ pub fn minecraft_id(&self) -> MinecraftEntityId {
+ *self.component::<MinecraftEntityId>()
+ }
+
+ Client:
+ /// Returns the attribute values of our player, which can be used to
+ /// determine things like our movement speed.
+ EntityRef:
+ /// Returns the attribute values of the entity, which can be used to
+ /// determine things like its movement speed.
+ pub fn attributes(&self) -> 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()
+ }
+
+ Client:
+ /// Get the name of the instance (world) that the bot is in.
+ ///
+ /// This can be used to check if the client is in the same world as another
+ /// entity.
+ #[doc(alias("world_name", "dimension_name"))]
+ EntityRef:
+ /// Get the name of the instance (world) that the entity is in.
+ ///
+ /// This can be used to check if the entity is in the same world as another
+ /// entity.
+ ///
+ /// Also see [`Client::instance_name`],
+ #[doc(alias("world_name", "dimension_name"))]
+ pub fn instance_name(&self) -> InstanceName {
+ (*self.component::<InstanceName>()).clone()
+ }
+
+ Client:
+ /// Returns whether the client is alive and in the world.
+ ///
+ /// You should avoid using this if you have auto-respawn enabled (which is
+ /// the default), instead consider watching for
+ /// [`Event::Death`](crate::Event::Death) instead.
+ EntityRef:
+ /// Returns whether the entity is alive and hasn't despawned.
+ ///
+ /// Unlike most functions in `EntityRef`, this one will not panic if the
+ /// entity is despawned. Because of this, it may be useful to check `is_alive`
+ /// before calling functions that request data from the world.
+ ///
+ /// Also see [`Client::is_alive`].
+ pub fn is_alive(&self) -> bool {
+ self.query_self::<Option<&Dead>, _>(|dead| dead.is_none())
+ }
+}