azalea/
nearest_entity.rs

1use azalea_entity::Position;
2use azalea_world::{InstanceName, MinecraftEntityId};
3use bevy_ecs::{
4    prelude::Entity,
5    query::{QueryFilter, With},
6    system::{Query, SystemParam},
7};
8
9/// This system parameter can be used as a shorthand for quickly finding an
10/// entity, (or several) close to a given position.
11///
12/// This system parameter allows for additional filtering of entities based off
13/// of ECS marker components, such as `With<>`, `Without<>`, or `Added<>`, etc.
14/// All functions used by this system parameter instance will respect the
15/// applied filter.
16///
17/// ```
18/// use azalea::chat::SendChatEvent;
19/// use azalea::nearest_entity::EntityFinder;
20/// use azalea_entity::metadata::{Player, AbstractMonster};
21/// use azalea_entity::LocalEntity;
22/// use bevy_ecs::system::Query;
23/// use bevy_ecs::prelude::{Entity, EventWriter};
24/// use bevy_ecs::query::With;
25///
26/// /// All bots near aggressive mobs will scream in chat.
27/// pub fn bots_near_aggressive_mobs(
28///     bots: Query<Entity, (With<LocalEntity>, With<Player>)>,
29///     entity_finder: EntityFinder<With<AbstractMonster>>,
30///     mut chat_events: EventWriter<SendChatEvent>,
31/// ) {
32///     for bot_id in bots.iter() {
33///         let Some(nearest) = entity_finder.nearest_to_entity(bot_id, 16.0) else {
34///             continue;
35///         };
36///
37///         chat_events.send(SendChatEvent {
38///             entity: bot_id,
39///             content: String::from("Ahhh!"),
40///         });
41///     }
42/// }
43/// ```
44#[derive(SystemParam)]
45pub struct EntityFinder<'w, 's, F = ()>
46where
47    F: QueryFilter + 'static,
48{
49    all_entities:
50        Query<'w, 's, (&'static Position, &'static InstanceName), With<MinecraftEntityId>>,
51
52    filtered_entities: Query<
53        'w,
54        's,
55        (Entity, &'static InstanceName, &'static Position),
56        (With<MinecraftEntityId>, F),
57    >,
58}
59
60impl<'a, F> EntityFinder<'_, '_, F>
61where
62    F: QueryFilter + 'static,
63{
64    /// Gets the nearest entity to the given position and world instance name.
65    /// This method will return `None` if there are no entities within range. If
66    /// multiple entities are within range, only the closest one is returned.
67    pub fn nearest_to_position(
68        &'a self,
69        position: &Position,
70        instance_name: &InstanceName,
71        max_distance: f64,
72    ) -> Option<Entity> {
73        let mut nearest_entity = None;
74        let mut min_distance = max_distance;
75
76        for (target_entity, e_instance, e_pos) in self.filtered_entities.iter() {
77            if e_instance != instance_name {
78                continue;
79            }
80
81            let target_distance = position.distance_to(e_pos);
82            if target_distance < min_distance {
83                nearest_entity = Some(target_entity);
84                min_distance = target_distance;
85            }
86        }
87
88        nearest_entity
89    }
90
91    /// Gets the nearest entity to the given entity. This method will return
92    /// `None` if there are no entities within range. If multiple entities are
93    /// within range, only the closest one is returned.
94    pub fn nearest_to_entity(&'a self, entity: Entity, max_distance: f64) -> Option<Entity> {
95        let Ok((position, instance_name)) = self.all_entities.get(entity) else {
96            return None;
97        };
98
99        let mut nearest_entity = None;
100        let mut min_distance = max_distance;
101
102        for (target_entity, e_instance, e_pos) in self.filtered_entities.iter() {
103            if entity == target_entity {
104                continue;
105            };
106
107            if e_instance != instance_name {
108                continue;
109            }
110
111            let target_distance = position.distance_to(e_pos);
112            if target_distance < min_distance {
113                nearest_entity = Some(target_entity);
114                min_distance = target_distance;
115            }
116        }
117
118        nearest_entity
119    }
120
121    /// This function get an iterator over all nearby entities to the given
122    /// position within the given maximum distance. The entities in this
123    /// iterator are not returned in any specific order.
124    ///
125    /// This function returns the Entity ID of nearby entities and their
126    /// distance away.
127    pub fn nearby_entities_to_position(
128        &'a self,
129        position: &'a Position,
130        instance_name: &'a InstanceName,
131        max_distance: f64,
132    ) -> impl Iterator<Item = (Entity, f64)> + 'a {
133        self.filtered_entities
134            .iter()
135            .filter_map(move |(target_entity, e_instance, e_pos)| {
136                if e_instance != instance_name {
137                    return None;
138                }
139
140                let distance = position.distance_to(e_pos);
141                if distance < max_distance {
142                    Some((target_entity, distance))
143                } else {
144                    None
145                }
146            })
147    }
148
149    /// This function get an iterator over all nearby entities to the given
150    /// entity within the given maximum distance. The entities in this iterator
151    /// are not returned in any specific order.
152    ///
153    /// This function returns the Entity ID of nearby entities and their
154    /// distance away.
155    pub fn nearby_entities_to_entity(
156        &'a self,
157        entity: Entity,
158        max_distance: f64,
159    ) -> impl Iterator<Item = (Entity, f64)> + 'a {
160        let position;
161        let instance_name;
162        if let Ok((pos, instance)) = self.all_entities.get(entity) {
163            position = *pos;
164            instance_name = Some(instance);
165        } else {
166            position = Position::default();
167            instance_name = None;
168        };
169
170        self.filtered_entities
171            .iter()
172            .filter_map(move |(target_entity, e_instance, e_pos)| {
173                if entity == target_entity {
174                    return None;
175                }
176
177                if Some(e_instance) != instance_name {
178                    return None;
179                }
180
181                let distance = position.distance_to(e_pos);
182                if distance < max_distance {
183                    Some((target_entity, distance))
184                } else {
185                    None
186                }
187            })
188    }
189}