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, EventWriter},
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: EventWriter<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 /// This method will return `None` if there are no entities within range. If
69 /// multiple entities are within range, only the closest one is returned.
70 pub fn nearest_to_position(
71 &'a self,
72 position: &Position,
73 instance_name: &InstanceName,
74 max_distance: f64,
75 ) -> Option<Entity> {
76 let mut nearest_entity = None;
77 let mut min_distance = max_distance;
78
79 for (target_entity, e_instance, e_pos) in self.filtered_entities.iter() {
80 if e_instance != instance_name {
81 continue;
82 }
83
84 let target_distance = position.distance_to(e_pos);
85 if target_distance < min_distance {
86 nearest_entity = Some(target_entity);
87 min_distance = target_distance;
88 }
89 }
90
91 nearest_entity
92 }
93
94 /// Gets the nearest entity to the given entity. This method will return
95 /// `None` if there are no entities within range. If multiple entities are
96 /// within range, only the closest one is returned.
97 pub fn nearest_to_entity(&'a self, entity: Entity, max_distance: f64) -> Option<Entity> {
98 let Ok((position, instance_name)) = self.all_entities.get(entity) else {
99 return None;
100 };
101
102 let mut nearest_entity = None;
103 let mut min_distance = max_distance;
104
105 for (target_entity, e_instance, e_pos) in self.filtered_entities.iter() {
106 if entity == target_entity {
107 continue;
108 };
109
110 if e_instance != instance_name {
111 continue;
112 }
113
114 let target_distance = position.distance_to(e_pos);
115 if target_distance < min_distance {
116 nearest_entity = Some(target_entity);
117 min_distance = target_distance;
118 }
119 }
120
121 nearest_entity
122 }
123
124 /// This function get an iterator over all nearby entities to the given
125 /// position within the given maximum distance. The entities in this
126 /// iterator are not returned in any specific order.
127 ///
128 /// This function returns the Entity ID of nearby entities and their
129 /// distance away.
130 pub fn nearby_entities_to_position(
131 &'a self,
132 position: &'a Position,
133 instance_name: &'a InstanceName,
134 max_distance: f64,
135 ) -> impl Iterator<Item = (Entity, f64)> + 'a {
136 self.filtered_entities
137 .iter()
138 .filter_map(move |(target_entity, e_instance, e_pos)| {
139 if e_instance != instance_name {
140 return None;
141 }
142
143 let distance = position.distance_to(e_pos);
144 if distance < max_distance {
145 Some((target_entity, distance))
146 } else {
147 None
148 }
149 })
150 }
151
152 /// This function get an iterator over all nearby entities to the given
153 /// entity within the given maximum distance. The entities in this iterator
154 /// are not returned in any specific order.
155 ///
156 /// This function returns the Entity ID of nearby entities and their
157 /// distance away.
158 pub fn nearby_entities_to_entity(
159 &'a self,
160 entity: Entity,
161 max_distance: f64,
162 ) -> impl Iterator<Item = (Entity, f64)> + 'a {
163 let position;
164 let instance_name;
165 if let Ok((pos, instance)) = self.all_entities.get(entity) {
166 position = *pos;
167 instance_name = Some(instance);
168 } else {
169 position = Position::default();
170 instance_name = None;
171 };
172
173 self.filtered_entities
174 .iter()
175 .filter_map(move |(target_entity, e_instance, e_pos)| {
176 if entity == target_entity {
177 return None;
178 }
179
180 if Some(e_instance) != instance_name {
181 return None;
182 }
183
184 let distance = position.distance_to(e_pos);
185 if distance < max_distance {
186 Some((target_entity, distance))
187 } else {
188 None
189 }
190 })
191 }
192}