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}