diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-12-28 21:54:12 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-28 21:54:12 -0600 |
| commit | 39488a6585ce969af93f43ece1ffb1174dc95e1d (patch) | |
| tree | 49b63b2321b974a7c6425e53b8602a0b4500f092 /azalea/src/entity_ref | |
| parent | 25e441944412038da2be4e64854e59169d58305b (diff) | |
| download | azalea-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.rs | 123 | ||||
| -rw-r--r-- | azalea/src/entity_ref/shared_impls.rs | 178 |
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()) + } +} |
