1use super::*;
2use crate::server::Server;
3
4#[derive(Clone, Copy)]
5enum PlayerSelection {
6 Gameplay,
7 OnlineProfile,
8}
9
10impl EntitySelector {
11 pub(super) fn new(
12 kind: SelectorKind,
13 max_results: usize,
14 includes_entities: bool,
15 current_entity: bool,
16 order: SelectorOrder,
17 ) -> Self {
18 Self {
19 kind,
20 max_results,
21 includes_entities,
22 current_entity,
23 world_limited: false,
24 order,
25 position: SelectorPosition::default(),
26 delta: SelectorDelta::default(),
27 distance: None,
28 level: None,
29 x_rotation: None,
30 y_rotation: None,
31 filters: Vec::new(),
32 uses_advanced_options: false,
33 }
34 }
35
36 pub(super) fn for_selector_type(selector_type: SelectorType) -> Self {
37 let (max_results, includes_entities, current_entity, order) = match selector_type {
38 SelectorType::AllPlayers => (usize::MAX, false, false, SelectorOrder::Arbitrary),
39 SelectorType::AllEntities => (usize::MAX, true, false, SelectorOrder::Arbitrary),
40 SelectorType::NearestEntity => (1, true, false, SelectorOrder::Nearest),
41 SelectorType::NearestPlayer => (1, false, false, SelectorOrder::Nearest),
42 SelectorType::RandomPlayer => (1, false, false, SelectorOrder::Random),
43 SelectorType::SelfEntity => (1, true, true, SelectorOrder::Arbitrary),
44 };
45 let mut selector = Self::new(
46 SelectorKind::Selector(selector_type),
47 max_results,
48 includes_entities,
49 current_entity,
50 order,
51 );
52 if matches!(
53 selector_type,
54 SelectorType::AllEntities | SelectorType::NearestEntity
55 ) {
56 selector.filters.push(SelectorFilter::Alive);
57 }
58 selector
59 }
60
61 pub(super) fn validate_for_argument(
62 &self,
63 single: bool,
64 players_only: bool,
65 ) -> Result<(), SelectorParseError> {
66 if single && self.max_results > 1 {
67 let message = if players_only {
68 TextComponent::from(&translations::ARGUMENT_PLAYER_TOOMANY).to_string()
69 } else {
70 TextComponent::from(&translations::ARGUMENT_ENTITY_TOOMANY).to_string()
71 };
72 return Err(SelectorParseError::invalid(message));
73 }
74 if players_only && self.includes_entities && !self.current_entity {
75 return Err(SelectorParseError::invalid(
76 TextComponent::from(&translations::ARGUMENT_PLAYER_ENTITIES).to_string(),
77 ));
78 }
79 Ok(())
80 }
81
82 pub(crate) fn find_players(
83 &self,
84 source: &CommandSource,
85 ) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
86 self.find_players_with(source, PlayerSelection::Gameplay)
87 }
88
89 pub(crate) fn find_online_profile_players(
90 &self,
91 source: &CommandSource,
92 ) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
93 self.find_players_with(source, PlayerSelection::OnlineProfile)
96 }
97
98 fn find_players_with(
99 &self,
100 source: &CommandSource,
101 selection: PlayerSelection,
102 ) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
103 self.check_selector_permission(source)?;
104 let server = source.server();
105 let position = selector_position(self, source);
106 let aabb = self.absolute_aabb(position);
107 let mut players = match &self.kind {
108 SelectorKind::PlayerName(name) => server
109 .get_players()
110 .into_iter()
111 .filter(|player| match selection {
112 PlayerSelection::Gameplay => selected_player_world(server, player, selection)
113 .is_some_and(|world| world.domain() == source.world().domain()),
114 PlayerSelection::OnlineProfile => true,
115 })
116 .filter(|player| player_name_matches(&player.gameprofile.name, name))
117 .collect::<Vec<_>>(),
118 SelectorKind::EntityUuid(uuid) => server
119 .get_players()
120 .into_iter()
121 .filter(|player| match selection {
122 PlayerSelection::Gameplay => selected_player_world(server, player, selection)
123 .is_some_and(|world| world.domain() == source.world().domain()),
124 PlayerSelection::OnlineProfile => true,
125 })
126 .filter(|player| player.uuid() == *uuid)
127 .collect::<Vec<_>>(),
128 SelectorKind::Selector(SelectorType::SelfEntity) => {
129 let Some(player) = source.player() else {
130 return Ok(Vec::new());
131 };
132 if selected_player_world(server, player, selection).is_some()
133 && self.matches_entity(player.as_ref(), position, aabb, source)?
134 {
135 vec![Arc::clone(player)]
136 } else {
137 Vec::new()
138 }
139 }
140 SelectorKind::Selector(_) => self.candidate_players(source, selection),
141 };
142
143 if !matches!(self.kind, SelectorKind::Selector(SelectorType::SelfEntity)) {
144 let mut filtered = Vec::new();
145 for player in players {
146 if self.matches_entity(player.as_ref(), position, aabb, source)? {
147 filtered.push(player);
148 if self.stops_filtering_after_match_count(filtered.len()) {
149 break;
150 }
151 }
152 }
153 players = filtered;
154 }
155 self.sort_and_limit_players(position, &mut players);
156 Ok(players)
157 }
158
159 pub(crate) fn find_entities(
160 &self,
161 source: &CommandSource,
162 ) -> Result<Vec<SharedEntity>, CommandSyntaxError> {
163 self.check_selector_permission(source)?;
164 if !self.includes_entities {
165 return Ok(self
166 .find_players(source)?
167 .into_iter()
168 .map(|player| player as SharedEntity)
169 .collect());
170 }
171 let server = source.server();
172 let position = selector_position(self, source);
173 let aabb = self.absolute_aabb(position);
174 let mut entities = match &self.kind {
175 SelectorKind::PlayerName(name) => server
176 .get_players()
177 .into_iter()
178 .filter(|player| player.get_world().domain() == source.world().domain())
179 .filter(|player| player_name_matches(&player.gameprofile.name, name))
180 .map(|player| player as SharedEntity)
181 .collect::<Vec<_>>(),
182 SelectorKind::EntityUuid(uuid) => find_entity_by_uuid(source, uuid)
183 .into_iter()
184 .collect::<Vec<_>>(),
185 SelectorKind::Selector(SelectorType::SelfEntity) => {
186 let Some(entity) = source.entity() else {
187 return Ok(Vec::new());
188 };
189 if self.matches_entity(entity.as_ref(), position, aabb, source)? {
190 vec![Arc::clone(entity)]
191 } else {
192 Vec::new()
193 }
194 }
195 SelectorKind::Selector(_) => self.candidate_entities(source, aabb),
196 };
197 entities.retain(|entity| {
198 entity
199 .as_player()
200 .is_none_or(|player| source.server().command_world_for_player(player).is_some())
201 });
202
203 if !matches!(self.kind, SelectorKind::Selector(SelectorType::SelfEntity)) {
204 let mut filtered = Vec::new();
205 for entity in entities {
206 if self.matches_entity(entity.as_ref(), position, aabb, source)? {
207 filtered.push(entity);
208 if self.stops_filtering_after_match_count(filtered.len()) {
209 break;
210 }
211 }
212 }
213 entities = filtered;
214 }
215 self.sort_and_limit_entities(position, &mut entities);
216 Ok(entities)
217 }
218
219 fn check_selector_permission(&self, source: &CommandSource) -> Result<(), CommandSyntaxError> {
220 if !matches!(self.kind, SelectorKind::Selector(_)) {
221 return Ok(());
222 }
223 if !allow_selectors(source) {
224 return Err(CommandSyntaxError::dynamic(TextComponent::from(
225 &translations::ARGUMENT_ENTITY_SELECTOR_NOT_ALLOWED,
226 )));
227 }
228 if self.uses_advanced_options && !allow_advanced_selectors(source) {
229 return Err(CommandSyntaxError::dynamic(
230 "Advanced entity selectors are not allowed",
231 ));
232 }
233 Ok(())
234 }
235
236 fn candidate_players(
237 &self,
238 source: &CommandSource,
239 selection: PlayerSelection,
240 ) -> Vec<Arc<Player>> {
241 let mut players = source.server().get_players();
242 if self.world_limited {
243 players.retain(|player| {
244 selected_player_world(source.server(), player, selection)
245 .is_some_and(|world| Arc::ptr_eq(&world, source.world()))
246 });
247 } else if matches!(selection, PlayerSelection::Gameplay) {
248 let domain = source.world().domain();
249 players.retain(|player| {
250 selected_player_world(source.server(), player, selection)
251 .is_some_and(|world| world.domain() == domain)
252 });
253 }
254 players
255 }
256
257 fn candidate_entities(
258 &self,
259 source: &CommandSource,
260 aabb: Option<WorldAabb>,
261 ) -> Vec<SharedEntity> {
262 if self.world_limited {
263 return world_candidates(source.world(), aabb);
264 }
265
266 source
267 .server()
268 .worlds
269 .worlds_in_domain(source.world().domain())
270 .into_iter()
271 .flat_map(|world| world_candidates(&world, aabb))
272 .collect()
273 }
274
275 fn absolute_aabb(&self, position: DVec3) -> Option<WorldAabb> {
276 if self.delta.has_any() {
277 return Some(self.delta.aabb().translate(position));
278 }
279 let max_distance = self.distance.and_then(|distance| distance.max)?;
280 Some(
281 WorldAabb::from_min_max(
282 DVec3::splat(-max_distance),
283 DVec3::splat(max_distance + 1.0),
284 )
285 .translate(position),
286 )
287 }
288
289 fn requires_position(&self) -> bool {
290 self.distance.is_some()
291 || self.delta.has_any()
292 || self
293 .position
294 .x
295 .is_some_and(|_| self.position.y.is_none() || self.position.z.is_none())
296 || self
297 .position
298 .y
299 .is_some_and(|_| self.position.x.is_none() || self.position.z.is_none())
300 || self
301 .position
302 .z
303 .is_some_and(|_| self.position.x.is_none() || self.position.y.is_none())
304 || matches!(self.order, SelectorOrder::Nearest | SelectorOrder::Furthest)
305 }
306
307 fn matches_entity(
308 &self,
309 entity: &dyn Entity,
310 position: DVec3,
311 aabb: Option<WorldAabb>,
312 source: &CommandSource,
313 ) -> Result<bool, CommandSyntaxError> {
314 if let Some(aabb) = aabb
315 && !aabb.intersects(entity.bounding_box())
316 {
317 return Ok(false);
318 }
319 if let Some(distance) = self.distance
320 && !distance.matches_squared(entity.position().distance_squared(position))
321 {
322 return Ok(false);
323 }
324 if let Some(level) = self.level {
325 let Some(player) = entity.as_player() else {
326 return Ok(false);
327 };
328 if !level.matches(player.experience.lock().level()) {
329 return Ok(false);
330 }
331 }
332 if let Some(range) = self.x_rotation
333 && !range.matches_rotation(entity.rotation().1)
334 {
335 return Ok(false);
336 }
337 if let Some(range) = self.y_rotation
338 && !range.matches_rotation(entity.rotation().0)
339 {
340 return Ok(false);
341 }
342 for filter in &self.filters {
343 if !filter.matches(entity, source)? {
344 return Ok(false);
345 }
346 }
347 Ok(true)
348 }
349
350 const fn stops_filtering_after_match_count(&self, count: usize) -> bool {
351 matches!(self.order, SelectorOrder::Arbitrary) && count >= self.max_results
352 }
353
354 fn sort_and_limit_players(&self, position: DVec3, players: &mut Vec<Arc<Player>>) {
355 match self.order {
356 SelectorOrder::Nearest => players.sort_by(|left, right| {
357 left.position()
358 .distance_squared(position)
359 .total_cmp(&right.position().distance_squared(position))
360 }),
361 SelectorOrder::Furthest => players.sort_by(|left, right| {
362 right
363 .position()
364 .distance_squared(position)
365 .total_cmp(&left.position().distance_squared(position))
366 }),
367 SelectorOrder::Random => players.shuffle(&mut rand::rng()),
368 SelectorOrder::Arbitrary => {}
369 }
370 players.truncate(self.max_results);
371 }
372
373 fn sort_and_limit_entities(&self, position: DVec3, entities: &mut Vec<SharedEntity>) {
374 match self.order {
375 SelectorOrder::Nearest => entities.sort_by(|left, right| {
376 left.position()
377 .distance_squared(position)
378 .total_cmp(&right.position().distance_squared(position))
379 }),
380 SelectorOrder::Furthest => entities.sort_by(|left, right| {
381 right
382 .position()
383 .distance_squared(position)
384 .total_cmp(&left.position().distance_squared(position))
385 }),
386 SelectorOrder::Random => entities.shuffle(&mut rand::rng()),
387 SelectorOrder::Arbitrary => {}
388 }
389 entities.truncate(self.max_results);
390 }
391}
392
393fn selected_player_world(
394 server: &Server,
395 player: &Player,
396 selection: PlayerSelection,
397) -> Option<Arc<World>> {
398 match selection {
399 PlayerSelection::Gameplay => server.command_world_for_player(player),
400 PlayerSelection::OnlineProfile => server.live_world_for_player(player),
401 }
402}
403
404impl SelectorFilter {
405 fn matches(
406 &self,
407 entity: &dyn Entity,
408 source: &CommandSource,
409 ) -> Result<bool, CommandSyntaxError> {
410 match self {
411 Self::Alive => Ok(entity.is_alive()),
412 Self::Name { value, inverted } => {
413 Ok(entity_name_filter_matches(value, *inverted, entity))
414 }
415 Self::GameMode { value, inverted } => {
416 Ok(game_mode_filter_matches(*value, *inverted, entity))
417 }
418 Self::EntityType { value, inverted } => {
419 let matches = entity.entity_type() == *value;
420 Ok(matches != *inverted)
421 }
422 Self::EntityTypeTag { value, inverted } => {
423 let matches = REGISTRY.entity_types.is_in_tag(entity.entity_type(), value);
424 Ok(matches != *inverted)
425 }
426 Self::Tag { value, inverted } => {
427 let tags = entity.tags();
428 let matches = if value.is_empty() {
429 tags.is_empty()
430 } else {
431 tags.iter().any(|tag| tag == value)
432 };
433 Ok(matches != *inverted)
434 }
435 Self::Team { value, inverted } => {
436 let holder_name = entity.scoreboard_name();
437 let scoreboard = source_scoreboard(source)?;
438 Ok(team_filter_matches(
439 value,
440 *inverted,
441 &holder_name,
442 scoreboard,
443 ))
444 }
445 Self::Nbt { value, inverted } => {
446 Ok(entity_nbt_filter_matches(value, *inverted, entity))
447 }
448 Self::Scores(scores) => {
449 let holder_name = entity.scoreboard_name();
450 let scoreboard = source_scoreboard(source)?;
451 Ok(score_filter_matches(scores, &holder_name, scoreboard))
452 }
453 }
454 }
455}
456
457pub(super) fn entity_nbt_filter_matches(
458 expected: &NbtCompound,
459 inverted: bool,
460 entity: &dyn Entity,
461) -> bool {
462 let actual = entity.nbt_for_data_compare();
463 compare_nbt_compounds(expected, &actual, true) != inverted
464}
465
466pub(super) fn entity_name_filter_matches(value: &str, inverted: bool, entity: &dyn Entity) -> bool {
467 (entity.plain_text_name() == value) != inverted
468}
469
470pub(super) fn game_mode_filter_matches(
471 value: GameType,
472 inverted: bool,
473 entity: &dyn Entity,
474) -> bool {
475 let Some(player) = entity.as_player() else {
476 return false;
477 };
478 (player.game_mode() == value) != inverted
479}
480
481pub(super) fn team_filter_matches(
482 expected: &str,
483 inverted: bool,
484 holder_name: &str,
485 scoreboard: &Scoreboard,
486) -> bool {
487 let holder = ScoreHolder::new(holder_name.to_owned());
488 let current = scoreboard.holder_team_name(&holder).unwrap_or_default();
489 (current == expected) != inverted
490}
491
492const fn player_name_matches(actual: &str, expected: &str) -> bool {
493 actual.eq_ignore_ascii_case(expected)
494}
495
496pub(super) fn score_filter_matches(
497 scores: &[(String, IntRange)],
498 holder_name: &str,
499 scoreboard: &Scoreboard,
500) -> bool {
501 let holder = ScoreHolder::new(holder_name.to_owned());
502 scores.iter().all(|(objective_name, range)| {
503 let Some(objective) = scoreboard.objective(objective_name) else {
504 return false;
505 };
506 scoreboard
507 .score(&holder, &objective)
508 .is_some_and(|score| range.matches(score))
509 })
510}
511
512fn source_scoreboard(source: &CommandSource) -> Result<&Scoreboard, CommandSyntaxError> {
513 source
514 .server()
515 .scoreboards
516 .get(source.world().domain())
517 .ok_or_else(|| {
518 CommandSyntaxError::dynamic(format!(
519 "Domain '{}' has no command scoreboard",
520 source.world().domain()
521 ))
522 })
523}
524
525fn world_candidates(world: &World, aabb: Option<WorldAabb>) -> Vec<SharedEntity> {
526 aabb.map_or_else(
527 || world.entity_manager().get_accessible_entities(),
528 |aabb| world.entity_manager().get_entities_in_aabb(&aabb),
529 )
530}
531
532pub(super) fn selector_position(selector: &EntitySelector, source: &CommandSource) -> DVec3 {
533 let base = if selector.requires_position() {
534 source.position()
535 } else {
536 DVec3::ZERO
537 };
538 selector.position.apply(base)
539}
540
541pub(super) fn find_entity_by_uuid(source: &CommandSource, uuid: &Uuid) -> Option<SharedEntity> {
542 source
543 .server()
544 .worlds
545 .worlds_in_domain(source.world().domain())
546 .into_iter()
547 .find_map(|world| {
548 let entity = world.get_entity_by_uuid(uuid)?;
549 world.get_accessible_entity_by_id(entity.id())
550 })
551}
552
553pub(super) fn create_delta_aabb(x: f64, y: f64, z: f64) -> WorldAabb {
554 let min = DVec3::new(
555 if x < 0.0 { x } else { 0.0 },
556 if y < 0.0 { y } else { 0.0 },
557 if z < 0.0 { z } else { 0.0 },
558 );
559 let max = DVec3::new(
560 if x < 0.0 { 0.0 } else { x } + 1.0,
561 if y < 0.0 { 0.0 } else { y } + 1.0,
562 if z < 0.0 { 0.0 } else { z } + 1.0,
563 );
564 WorldAabb::from_min_max(min, max)
565}