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