Skip to main content

steel_core/command/execution/selector/
mod.rs

1//! Vanilla-style entity selector parsing and resolution.
2//!
3//! Vanilla's server-wide candidate scope maps to one Steel domain. Selectors
4//! never expose players, entities, teams, or scores from another domain.
5
6use std::sync::Arc;
7
8use glam::DVec3;
9use rand::seq::SliceRandom;
10use simdnbt::owned::NbtCompound;
11use steel_math::wrap_degrees;
12use steel_registry::{
13    REGISTRY, RegistryExt as _, TaggedRegistryExt as _, entity_type::EntityTypeRef,
14    vanilla_entities,
15};
16use steel_utils::{
17    Identifier,
18    geometry::WorldAabb,
19    java,
20    nbt::{compare_nbt_compounds, parse_snbt_compound_argument},
21    translations,
22    types::GameType,
23};
24use text_components::TextComponent;
25use uuid::Uuid;
26
27use crate::{
28    command::brigadier::{
29        CommandSyntaxError, CommandSyntaxErrorKind, ReaderCursor, StringReader, SuggestionsBuilder,
30    },
31    entity::{Entity, SharedEntity},
32    player::Player,
33    scoreboard::{ScoreHolder, Scoreboard},
34    world::World,
35};
36
37use super::{CommandArgumentSource, CommandSource};
38
39const SORT_NEAREST: &str = "nearest";
40const SORT_FURTHEST: &str = "furthest";
41const SORT_RANDOM: &str = "random";
42const SORT_ARBITRARY: &str = "arbitrary";
43const SELECTOR_OPTION_KEYS: &[&str] = &[
44    "name",
45    "distance",
46    "level",
47    "x",
48    "y",
49    "z",
50    "dx",
51    "dy",
52    "dz",
53    "x_rotation",
54    "y_rotation",
55    "limit",
56    "sort",
57    "gamemode",
58    "type",
59    "tag",
60    "team",
61    "nbt",
62    "scores",
63    "advancements",
64    "predicate",
65];
66const UNSUPPORTED_SELECTOR_OPTION_KEYS: &[&str] = &[
67    // Needs player advancement progress before it can resolve faithfully.
68    "advancements",
69    // Needs a real reloadable/plugin predicate registry.
70    "predicate",
71];
72const SET_ONCE_SELECTOR_OPTIONS: &[&str] = &[
73    "distance",
74    "level",
75    "x",
76    "y",
77    "z",
78    "dx",
79    "dy",
80    "dz",
81    "x_rotation",
82    "y_rotation",
83    "limit",
84    "sort",
85    "scores",
86    "advancements",
87];
88const GAME_MODE_SUGGESTIONS: &[&str] = &["survival", "creative", "adventure", "spectator"];
89
90#[derive(Clone, Debug, PartialEq)]
91pub(crate) struct EntitySelector {
92    kind: SelectorKind,
93    max_results: usize,
94    includes_entities: bool,
95    current_entity: bool,
96    world_limited: bool,
97    order: SelectorOrder,
98    position: SelectorPosition,
99    delta: SelectorDelta,
100    distance: Option<DoubleRange>,
101    level: Option<IntRange>,
102    x_rotation: Option<FloatRange>,
103    y_rotation: Option<FloatRange>,
104    filters: Vec<SelectorFilter>,
105    uses_advanced_options: bool,
106}
107
108#[derive(Clone, Debug, PartialEq)]
109enum SelectorKind {
110    Selector(SelectorType),
111    PlayerName(String),
112    EntityUuid(Uuid),
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116enum SelectorType {
117    AllPlayers,
118    AllEntities,
119    NearestEntity,
120    NearestPlayer,
121    RandomPlayer,
122    SelfEntity,
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126enum SelectorOrder {
127    Nearest,
128    Furthest,
129    Random,
130    Arbitrary,
131}
132
133#[derive(Clone, Debug, Default, PartialEq)]
134struct SelectorPosition {
135    x: Option<f64>,
136    y: Option<f64>,
137    z: Option<f64>,
138}
139
140impl SelectorPosition {
141    fn apply(&self, base: DVec3) -> DVec3 {
142        DVec3::new(
143            self.x.unwrap_or(base.x),
144            self.y.unwrap_or(base.y),
145            self.z.unwrap_or(base.z),
146        )
147    }
148}
149
150#[derive(Clone, Copy, Debug, Default, PartialEq)]
151struct SelectorDelta {
152    x: Option<f64>,
153    y: Option<f64>,
154    z: Option<f64>,
155}
156
157impl SelectorDelta {
158    const fn has_any(self) -> bool {
159        self.x.is_some() || self.y.is_some() || self.z.is_some()
160    }
161
162    fn aabb(self) -> WorldAabb {
163        create_delta_aabb(
164            self.x.unwrap_or(0.0),
165            self.y.unwrap_or(0.0),
166            self.z.unwrap_or(0.0),
167        )
168    }
169}
170
171#[derive(Clone, Debug, PartialEq)]
172enum SelectorFilter {
173    Alive,
174    Name {
175        value: String,
176        inverted: bool,
177    },
178    GameMode {
179        value: GameType,
180        inverted: bool,
181    },
182    EntityType {
183        value: EntityTypeRef,
184        inverted: bool,
185    },
186    EntityTypeTag {
187        value: Identifier,
188        inverted: bool,
189    },
190    Tag {
191        value: String,
192        inverted: bool,
193    },
194    Team {
195        value: String,
196        inverted: bool,
197    },
198    Nbt {
199        value: NbtCompound,
200        inverted: bool,
201    },
202    Scores(Vec<(String, IntRange)>),
203}
204
205#[derive(Clone, Copy, Debug, PartialEq)]
206struct DoubleRange {
207    min: Option<f64>,
208    max: Option<f64>,
209}
210
211impl DoubleRange {
212    fn matches_squared(self, value: f64) -> bool {
213        if let Some(min) = self.min
214            && value < min * min
215        {
216            return false;
217        }
218        if let Some(max) = self.max
219            && value > max * max
220        {
221            return false;
222        }
223        true
224    }
225}
226
227#[derive(Clone, Copy, Debug, PartialEq)]
228struct FloatRange {
229    min: Option<f32>,
230    max: Option<f32>,
231}
232
233impl FloatRange {
234    fn matches_rotation(self, value: f32) -> bool {
235        let min = wrap_degrees(self.min.unwrap_or(0.0));
236        let max = wrap_degrees(self.max.unwrap_or(359.0));
237        let value = wrap_degrees(value);
238        if min > max {
239            value >= min || value <= max
240        } else {
241            value >= min && value <= max
242        }
243    }
244}
245
246#[derive(Clone, Copy, Debug, PartialEq)]
247struct IntRange {
248    min: Option<i32>,
249    max: Option<i32>,
250}
251
252impl IntRange {
253    #[cfg(test)]
254    const fn exactly(value: i32) -> Self {
255        Self {
256            min: Some(value),
257            max: Some(value),
258        }
259    }
260
261    const fn matches(self, value: i32) -> bool {
262        if let Some(min) = self.min
263            && value < min
264        {
265            return false;
266        }
267        if let Some(max) = self.max
268            && value > max
269        {
270            return false;
271        }
272        true
273    }
274}
275
276#[derive(Clone, Debug, Default)]
277struct InvertableOptionState {
278    positive_seen: bool,
279    negative_seen: bool,
280}
281
282impl InvertableOptionState {
283    fn parse_element(&mut self, inverted: bool, option: &str) -> Result<(), SelectorParseError> {
284        if inverted {
285            if self.positive_seen {
286                return Err(SelectorParseError::invalid(format!(
287                    "option '{option}' cannot be repeated after a positive value"
288                )));
289            }
290            self.negative_seen = true;
291        } else {
292            if self.positive_seen || self.negative_seen {
293                return Err(SelectorParseError::invalid(format!(
294                    "option '{option}' cannot add a positive value after another value"
295                )));
296            }
297            self.positive_seen = true;
298        }
299        Ok(())
300    }
301
302    const fn suggestion_mode(&self) -> InvertableSuggestionMode {
303        if self.positive_seen {
304            InvertableSuggestionMode::None
305        } else if self.negative_seen {
306            InvertableSuggestionMode::NegativeOnly
307        } else {
308            InvertableSuggestionMode::Any
309        }
310    }
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq)]
314enum InvertableSuggestionMode {
315    Any,
316    NegativeOnly,
317    None,
318}
319
320impl InvertableSuggestionMode {
321    const fn allows_positive(self) -> bool {
322        matches!(self, Self::Any)
323    }
324
325    const fn allows_negative(self) -> bool {
326        matches!(self, Self::Any | Self::NegativeOnly)
327    }
328
329    const fn allows_any(self) -> bool {
330        !matches!(self, Self::None)
331    }
332}
333
334#[derive(Clone, Debug, Default)]
335struct EntityTypeOptionState {
336    invertible: InvertableOptionState,
337    tags_seen: Vec<Identifier>,
338}
339
340impl EntityTypeOptionState {
341    fn parse_element(&mut self, inverted: bool, option: &str) -> Result<(), SelectorParseError> {
342        self.invertible.parse_element(inverted, option)
343    }
344
345    fn parse_tag(&mut self, tag: &Identifier, option: &str) -> Result<(), SelectorParseError> {
346        if self.tags_seen.iter().any(|existing| existing == tag) {
347            return Err(SelectorParseError::invalid(format!(
348                "option '{option}' cannot repeat tag '#{tag}'"
349            )));
350        }
351        self.invertible.parse_element(true, option)?;
352        self.tags_seen.push(tag.clone());
353        Ok(())
354    }
355}
356
357#[derive(Clone, Debug, Default)]
358struct SelectorOptionState {
359    name: InvertableOptionState,
360    team: InvertableOptionState,
361    gamemode: InvertableOptionState,
362    entity_type: EntityTypeOptionState,
363    distance: bool,
364    level: bool,
365    x: bool,
366    y: bool,
367    z: bool,
368    dx: bool,
369    dy: bool,
370    dz: bool,
371    x_rotation: bool,
372    y_rotation: bool,
373    limit: bool,
374    sort: bool,
375    scores: bool,
376}
377
378#[derive(Clone, Debug)]
379struct SelectorParseError {
380    kind: SelectorParseErrorKind,
381    cursor: usize,
382}
383
384#[derive(Clone, Debug)]
385enum SelectorParseErrorKind {
386    NotAllowed,
387    AdvancedNotAllowed,
388    Invalid(Box<TextComponent>),
389    Unsupported(String),
390}
391
392impl SelectorParseError {
393    const fn not_allowed(cursor: usize) -> Self {
394        Self {
395            kind: SelectorParseErrorKind::NotAllowed,
396            cursor,
397        }
398    }
399
400    const fn advanced_not_allowed(cursor: usize) -> Self {
401        Self {
402            kind: SelectorParseErrorKind::AdvancedNotAllowed,
403            cursor,
404        }
405    }
406
407    fn invalid(message: impl Into<TextComponent>) -> Self {
408        Self {
409            kind: SelectorParseErrorKind::Invalid(Box::new(message.into())),
410            cursor: 0,
411        }
412    }
413
414    fn invalid_at(message: impl Into<TextComponent>, cursor: usize) -> Self {
415        Self {
416            kind: SelectorParseErrorKind::Invalid(Box::new(message.into())),
417            cursor,
418        }
419    }
420
421    fn unsupported(option: impl Into<String>, cursor: usize) -> Self {
422        Self {
423            kind: SelectorParseErrorKind::Unsupported(option.into()),
424            cursor,
425        }
426    }
427
428    fn message(self) -> TextComponent {
429        match self.kind {
430            SelectorParseErrorKind::NotAllowed => {
431                TextComponent::from(&translations::ARGUMENT_ENTITY_SELECTOR_NOT_ALLOWED)
432            }
433            SelectorParseErrorKind::AdvancedNotAllowed => {
434                TextComponent::from("Advanced entity selectors are not allowed")
435            }
436            SelectorParseErrorKind::Invalid(message) => *message,
437            SelectorParseErrorKind::Unsupported(option) => {
438                TextComponent::from(format!("Unsupported entity selector option: {option}"))
439            }
440        }
441    }
442}
443
444mod model;
445mod parser;
446mod suggestions;
447
448use model::create_delta_aabb;
449pub(crate) use parser::*;
450pub(crate) use suggestions::*;
451
452#[cfg(test)]
453mod tests;