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}