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