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/client_impl | |
| 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/client_impl')
| -rw-r--r-- | azalea/src/client_impl/entity_query.rs | 106 | ||||
| -rw-r--r-- | azalea/src/client_impl/mod.rs | 120 |
2 files changed, 101 insertions, 125 deletions
diff --git a/azalea/src/client_impl/entity_query.rs b/azalea/src/client_impl/entity_query.rs index 683d755e..8de48478 100644 --- a/azalea/src/client_impl/entity_query.rs +++ b/azalea/src/client_impl/entity_query.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ }; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; -use crate::Client; +use crate::{Client, entity_ref::EntityRef}; impl Client { /// Get a component from the client. @@ -117,7 +117,7 @@ impl Client { ) -> R { self.try_query_entity(entity, f).unwrap_or_else(|_| { panic!( - "Entity is missing a required component {:?}", + "Querying entity {entity} failed when getting {:?}", any::type_name::<D>() ) }) @@ -138,40 +138,44 @@ impl Client { qs.get_mut(&mut ecs, entity).map(f) } + /// Quickly returns an [`EntityRef`] for an arbitrary entity that + /// matches the given predicate function that is in the same + /// [`Instance`] as the client. + /// + /// [`Instance`]: azalea_world::Instance + 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)) + } /// Quickly returns a lightweight [`Entity`] for an arbitrary entity that /// matches the given predicate function that is in the same /// [`Instance`] as the client. /// - /// You can then use [`Self::entity_component`] to get components from this - /// entity. + /// To get an [`EntityRef`], consider using [`Self::any_entity_by`] + /// instead. /// /// If you want to find the nearest entity, consider using - /// [`Self::nearest_entity_by`] instead. If you want to find all entities - /// that match the predicate, use [`Self::nearest_entities_by`]. + /// [`Self::nearest_entity_id_by`] instead. If you want to find all entities + /// that match the predicate, use [`Self::nearest_entity_ids_by`]. /// /// # Example + /// /// ``` - /// use azalea::{ - /// Client, - /// entity::{Position, metadata::Player}, - /// player::GameProfileComponent, - /// }; + /// use azalea::{entity::metadata::Player, player::GameProfileComponent}; /// use bevy_ecs::query::With; /// - /// # fn example(mut bot: Client, sender_name: String) { - /// let entity = bot.any_entity_by::<&GameProfileComponent, With<Player>>( + /// # fn example(mut bot: azalea::Client, sender_name: String) { + /// let entity = bot.any_entity_id_by::<&GameProfileComponent, With<Player>>( /// |profile: &GameProfileComponent| profile.name == sender_name, /// ); - /// if let Some(entity) = entity { - /// let position = bot.entity_component::<Position>(entity); - /// // ... - /// } /// # } /// ``` /// - /// [`Entity`]: bevy_ecs::entity::Entity /// [`Instance`]: azalea_world::Instance - pub fn any_entity_by<Q: QueryData, F: QueryFilter>( + pub fn any_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<Entity> { @@ -179,43 +183,59 @@ impl Client { predicate.find_any(self.ecs.clone(), &instance_name) } - /// Return a lightweight [`Entity`] for the nearest entity that matches the + /// Return an [`EntityRef`] for the nearest entity that matches the /// given predicate function. /// - /// You can then use [`Self::entity_component`] to get components from this - /// entity. - /// /// If you don't need the entity to be the nearest one, it may be more /// efficient to use [`Self::any_entity_by`] instead. You can also use /// [`Self::nearest_entities_by`] to get all nearby entities. /// - /// ``` - /// use azalea_entity::{LocalEntity, Position, metadata::Player}; - /// use bevy_ecs::query::{With, Without}; + /// Also see [`Self::nearest_entity_id_by`] if you only need the lightweight + /// [`Entity`] identifier. + 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)) + } + /// Return a lightweight [`Entity`] for the nearest entity that matches the + /// given predicate function. /// - /// # fn example(mut bot: azalea::Client, sender_name: String) { - /// // get the position of the nearest player - /// if let Some(nearest_player) = - /// bot.nearest_entity_by::<(), (With<Player>, Without<LocalEntity>)>(|_: ()| true) - /// { - /// let nearest_player_pos = **bot.entity_component::<Position>(nearest_player); - /// bot.chat(format!("You are at {nearest_player_pos}")); - /// } - /// # } - /// ``` + /// To get an [`EntityRef`], consider using [`Self::nearest_entity_by`] + /// instead. /// - /// [`Entity`]: bevy_ecs::entity::Entity - pub fn nearest_entity_by<Q: QueryData, F: QueryFilter>( + /// If you don't need the entity to be the nearest one, it may be more + /// efficient to use [`Self::any_entity_id_by`] instead. You can also use + /// [`Self::nearest_entity_ids_by`] to get all nearby entities. + pub fn nearest_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<Entity> { - self.nearest_entities_by(predicate).first().copied() + self.nearest_entity_ids_by(predicate).first().copied() } - /// Similar to [`Self::nearest_entity_by`] but returns a `Vec<Entity>` of - /// all entities in our instance that match the predicate. + /// Returns an array of all [`EntityRef`]s in the instance that match the + /// predicate, sorted by nearest first. + /// + /// To only get the nearest entity, consider using + /// [`Self::nearest_entity_by`]. If you only need the [`Entity`] + /// identifiers, you can use [`Self::nearest_entity_ids_by`] instead. + pub fn nearest_entities_by<Q: QueryData, F: QueryFilter>( + &self, + predicate: impl EntityPredicate<Q, F>, + ) -> Box<[EntityRef]> { + self.nearest_entity_ids_by(predicate) + .into_iter() + .map(|e| self.entity_ref_for(e)) + .collect() + } + /// Returns an array of all [`Entity`]s in the instance that match the + /// predicate, sorted by nearest first. /// - /// The first entity is the nearest one. + /// To only get the nearest entity, consider using + /// [`Self::nearest_entity_id_by`]. To get the [`EntityRef`]s instead, you + /// can use [`Self::nearest_entities_by`]. /// /// ``` /// # use azalea_entity::{LocalEntity, Position, metadata::Player}; @@ -225,7 +245,7 @@ impl Client { /// bot.nearest_entities_by::<(), (With<Player>, Without<LocalEntity>)>(|_: ()| true); /// # } /// ``` - pub fn nearest_entities_by<Q: QueryData, F: QueryFilter>( + pub fn nearest_entity_ids_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Box<[Entity]> { diff --git a/azalea/src/client_impl/mod.rs b/azalea/src/client_impl/mod.rs index 419fc27e..e3876f2e 100644 --- a/azalea/src/client_impl/mod.rs +++ b/azalea/src/client_impl/mod.rs @@ -11,16 +11,8 @@ use azalea_client::{ player::{GameProfileComponent, PlayerInfo}, start_ecs_runner, }; -use azalea_core::{ - data_registry::{DataRegistryWithKey, ResolvableDataRegistry}, - position::Vec3, -}; -use azalea_entity::{ - Attributes, Position, - dimensions::EntityDimensions, - indexing::{EntityIdIndex, EntityUuidIndex}, - metadata::Health, -}; +use azalea_core::data_registry::{DataRegistryWithKey, ResolvableDataRegistry}; +use azalea_entity::indexing::{EntityIdIndex, EntityUuidIndex}; use azalea_protocol::{ address::{ResolvableAddr, ResolvedAddr}, connect::Proxy, @@ -39,7 +31,10 @@ use parking_lot::RwLock; use tokio::sync::mpsc; use uuid::Uuid; -use crate::events::{Event, LocalPlayerEvents}; +use crate::{ + entity_ref::EntityRef, + events::{Event, LocalPlayerEvents}, +}; pub mod attack; pub mod chat; @@ -309,47 +304,19 @@ impl Client { self.query_self::<Option<&InstanceName>, _>(|ins| ins.is_some()) } - /// Get the position of this client. - /// - /// This is a shortcut for `Vec3::from(&bot.component::<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. - pub fn position(&self) -> Vec3 { - Vec3::from( - &*self - .get_component::<Position>() - .expect("the client's position hasn't been initialized yet"), - ) - } - - /// Get the bounding box dimensions for our client, which contains our - /// width, height, and eye height. - /// - /// This is a shortcut for - /// `self.component::<EntityDimensions>()`. - pub fn dimensions(&self) -> EntityDimensions { - self.component::<EntityDimensions>().clone() - } - - /// Get the position of this client's eyes. - /// - /// This is a shortcut for - /// `bot.position().up(bot.dimensions().eye_height)`. - pub fn eye_position(&self) -> Vec3 { - self.query_self::<(&Position, &EntityDimensions), _>(|(pos, dim)| { - pos.up(dim.eye_height as f64) - }) + /// Returns the client as an [`EntityRef`], allowing you to treat it as any + /// other entity. + pub fn entity(&self) -> EntityRef { + self.entity_ref_for(self.entity) } - /// Get the health of this client. - /// - /// This is a shortcut for `*bot.component::<Health>()`. - pub fn health(&self) -> f32 { - **self.component::<Health>() + /// Create an [`EntityRef`] for the given ECS entity. + pub fn entity_ref_for(&self, entity: Entity) -> EntityRef { + EntityRef::new(self.clone(), entity) } +} +impl Client { /// Get the hunger level of this client, which includes both food and /// saturation. /// @@ -366,13 +333,6 @@ impl Client { self.profile().name.to_owned() } - /// Get the Minecraft UUID of this client. - /// - /// This is a shortcut for `bot.component::<GameProfileComponent>().uuid`. - pub fn uuid(&self) -> Uuid { - self.profile().uuid - } - /// Get a map of player UUIDs to their information in the tab list. /// /// This is a shortcut for `*bot.component::<TabList>()`. @@ -398,23 +358,6 @@ impl Client { self.component::<Account>().clone() } - /// Returns the attribute values of our player, which can be used to - /// determine things like our 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() - } - - /// 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"))] - pub fn instance_name(&self) -> InstanceName { - (*self.component::<InstanceName>()).clone() - } - /// A convenience function to get the Minecraft Uuid of a player by their /// username, if they're present in the tab list. /// @@ -427,23 +370,36 @@ impl Client { .map(|player| player.profile.uuid) } - /// Get an ECS `Entity` in the world by its Minecraft UUID, if it's within + /// Get an [`Entity`] in the world by its Minecraft UUID, if it's within /// render distance. - pub fn entity_by_uuid(&self, uuid: Uuid) -> Option<Entity> { + /// + /// Also see [`Self::entity_by_uuid`] and + /// [`Self::entity_id_by_minecraft_id`]. + pub fn entity_id_by_uuid(&self, uuid: Uuid) -> Option<Entity> { self.map_resource::<EntityUuidIndex, _>(|entity_uuid_index| entity_uuid_index.get(&uuid)) } + /// Get an [`EntityRef`] in the world by its Minecraft UUID, if it's within + /// render distance. + /// + /// Also see [`Self::entity_id_by_uuid`]. + pub fn entity_by_uuid(&self, uuid: Uuid) -> Option<EntityRef> { + self.entity_id_by_uuid(uuid).map(|e| self.entity_ref_for(e)) + } - /// Convert an ECS `Entity` to a [`MinecraftEntityId`]. - pub fn minecraft_entity_by_ecs_entity(&self, entity: Entity) -> Option<MinecraftEntityId> { + /// 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> { self.query_self::<&EntityIdIndex, _>(|entity_id_index| { - entity_id_index.get_by_ecs_entity(entity) + entity_id_index.get_by_minecraft_entity(id) }) } - /// Convert a [`MinecraftEntityId`] to an ECS `Entity`. - pub fn ecs_entity_by_minecraft_entity(&self, entity: MinecraftEntityId) -> Option<Entity> { - self.query_self::<&EntityIdIndex, _>(|entity_id_index| { - entity_id_index.get_by_minecraft_entity(entity) - }) + /// 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)) } /// Call the given function with the client's [`RegistryHolder`]. |
