Skip to main content

steel_core/command/execution/
argument.rs

1use std::{fmt, sync::Arc};
2
3use super::{
4    BiomeOrTag, BlockInput, BlockPredicate, CommandArgumentSource, Coordinates, IntRange,
5    ItemPredicate, ScoreHolderArgument, StructureOrTagKey, WorldArgument,
6    biome::{parse_biome_or_tag, suggest_biomes},
7    block::{parse_block_input, parse_block_predicate, suggest_block_inputs, suggest_blocks},
8    coordinates::{
9        parse_block_pos, parse_rotation, parse_vec2, parse_vec3, suggest_coordinates, suggest_vec2,
10    },
11    item::{parse_item_stack, suggest_item_stack},
12    item_predicate::{parse_item_predicate, suggest_item_predicate},
13    nbt::parse_nbt_path,
14    permission::{PermissionGroupParser, PermissionMetadataParser, PermissionRuleParser},
15    profile::{GameProfileParser, GameProfileSuggestionMode},
16    score::{parse_int_range, parse_score_holder, suggest_score_holders},
17    selector::{EntitySelector, parse_entity_selector, suggest_entity_selector},
18    structure::{parse_structure_or_tag_key, suggest_structures},
19    suggestions::{matches_substring_ignore_case, suggest_resources},
20    swing_animation::SwingAnimationParser,
21    text::validate_component_syntax,
22    world::{parse_world_argument, suggest_worlds},
23};
24use crate::chunk::heightmap::HeightmapType;
25use crate::command::brigadier::{
26    ArgumentSuggestionContext, ArgumentType, CommandArgumentParser, CommandSyntaxError,
27    CommandSyntaxErrorKind, ContainsPrimitiveArgumentValue, PrimitiveArgumentValue, StringReader,
28    SuggestionsBuilder,
29};
30use crate::command::incorrectly_typed_argument;
31use crate::command::protocol::protocol_argument_type;
32use crate::entity::{ENTITIES, EntityAnchor};
33use glam::DVec3;
34use steel_protocol::packets::game::{
35    ArgumentStringTypeBehavior, ArgumentType as ProtocolArgumentType,
36    SuggestionType as ProtocolSuggestionType,
37};
38use steel_registry::advancement::registry::AdvancementRef;
39use steel_registry::damage_type::DamageTypeRef;
40use steel_registry::{
41    DAMAGE_TYPE_REGISTRY, ENCHANTMENT_REGISTRY, ENTITY_TYPE_REGISTRY, REGISTRY, RegistryExt as _,
42    TIMELINE_REGISTRY, WORLD_CLOCK_REGISTRY, enchantment::EnchantmentRef,
43    entity_type::EntityTypeRef, item_stack::ItemStack, timeline::TimelineRef,
44    world_clock::WorldClockRef,
45};
46use steel_utils::translations::ADVANCEMENT_ADVANCEMENT_NOT_FOUND;
47use steel_utils::{
48    Downcast as _, DowncastType, DowncastTypeKey, ErasedType, Identifier,
49    nbt::{NbtPath, parse_snbt_argument},
50    translations,
51    types::GameType,
52};
53use text_components::TextComponent;
54
55/// Axes selected by vanilla's coordinate swizzle argument.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub(crate) struct CoordinateAxes(u8);
58
59impl CoordinateAxes {
60    const X: u8 = 1;
61    const Y: u8 = 2;
62    const Z: u8 = 4;
63
64    pub(crate) const fn x(self) -> bool {
65        self.0 & Self::X != 0
66    }
67
68    pub(crate) const fn y(self) -> bool {
69        self.0 & Self::Y != 0
70    }
71
72    pub(crate) const fn z(self) -> bool {
73        self.0 & Self::Z != 0
74    }
75
76    pub(crate) const fn align(self, mut position: DVec3) -> DVec3 {
77        if self.x() {
78            position.x = position.x.floor();
79        }
80        if self.y() {
81            position.y = position.y.floor();
82        }
83        if self.z() {
84            position.z = position.z.floor();
85        }
86        position
87    }
88}
89
90/// Typed parser contract erased by [`SteelArgumentType`].
91pub(crate) trait SteelArgumentParser:
92    DowncastType + fmt::Debug + PartialEq + Send + Sync + 'static
93{
94    /// Concrete value produced by this parser.
95    type Value: DowncastType + fmt::Debug + Send + Sync + 'static;
96
97    /// Parses one value from the command reader.
98    fn parse(
99        &self,
100        reader: &mut StringReader<'_>,
101        source: &dyn CommandArgumentSource,
102    ) -> Result<Self::Value, CommandSyntaxError>;
103
104    /// Adds context-aware completion suggestions.
105    fn list_suggestions(
106        &self,
107        _context: &dyn SteelArgumentSuggestionContext,
108        _builder: &mut SuggestionsBuilder<'_>,
109    ) {
110    }
111
112    /// Returns the vanilla command-tree parser representation.
113    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>);
114}
115
116trait ErasedSteelArgumentParser: ErasedType + fmt::Debug + Send + Sync {
117    fn parse_erased(
118        &self,
119        reader: &mut StringReader<'_>,
120        source: &dyn CommandArgumentSource,
121    ) -> Result<SteelArgumentValue, CommandSyntaxError>;
122
123    fn list_suggestions_erased(
124        &self,
125        context: &dyn SteelArgumentSuggestionContext,
126        builder: &mut SuggestionsBuilder<'_>,
127    );
128
129    fn protocol_argument_erased(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>);
130
131    fn equals_erased(&self, other: &dyn ErasedSteelArgumentParser) -> bool;
132}
133
134impl<P> ErasedSteelArgumentParser for P
135where
136    P: SteelArgumentParser,
137{
138    fn parse_erased(
139        &self,
140        reader: &mut StringReader<'_>,
141        source: &dyn CommandArgumentSource,
142    ) -> Result<SteelArgumentValue, CommandSyntaxError> {
143        self.parse(reader, source).map(SteelArgumentValue::new)
144    }
145
146    fn list_suggestions_erased(
147        &self,
148        context: &dyn SteelArgumentSuggestionContext,
149        builder: &mut SuggestionsBuilder<'_>,
150    ) {
151        self.list_suggestions(context, builder);
152    }
153
154    fn protocol_argument_erased(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
155        self.protocol_argument()
156    }
157
158    fn equals_erased(&self, other: &dyn ErasedSteelArgumentParser) -> bool {
159        other.downcast_ref::<P>() == Some(self)
160    }
161}
162
163/// An extensible, keyed parser stored by Steel's command runtime.
164#[derive(Clone)]
165pub(crate) struct SteelArgumentType(Arc<dyn ErasedSteelArgumentParser>);
166
167impl SteelArgumentType {
168    /// Erases a concrete parser while retaining its deterministic type key.
169    pub(crate) fn new(parser: impl SteelArgumentParser) -> Self {
170        Self(Arc::new(parser))
171    }
172
173    pub(crate) fn time(minimum: i32) -> Self {
174        Self::new(TimeParser { minimum })
175    }
176
177    pub(crate) fn swing_animation() -> Self {
178        Self::new(SwingAnimationParser)
179    }
180
181    pub(crate) fn block_pos() -> Self {
182        Self::new(BlockPosParser)
183    }
184
185    pub(crate) fn vec3(center_integers: bool) -> Self {
186        Self::new(Vec3Parser { center_integers })
187    }
188
189    pub(crate) fn vec2(center_integers: bool) -> Self {
190        Self::new(Vec2Parser { center_integers })
191    }
192
193    pub(crate) fn rotation() -> Self {
194        Self::new(RotationParser)
195    }
196
197    pub(crate) fn swizzle() -> Self {
198        Self::new(SwizzleParser)
199    }
200
201    pub(crate) fn heightmap() -> Self {
202        Self::new(HeightmapParser)
203    }
204
205    pub(crate) fn entity_anchor() -> Self {
206        Self::new(EntityAnchorParser)
207    }
208
209    pub(crate) fn entity_tag() -> Self {
210        Self::new(EntityTagParser)
211    }
212
213    pub(crate) fn entity() -> Self {
214        Self::new(EntityParser {
215            single: true,
216            players_only: false,
217        })
218    }
219
220    pub(crate) fn entities() -> Self {
221        Self::new(EntityParser {
222            single: false,
223            players_only: false,
224        })
225    }
226
227    pub(crate) fn player() -> Self {
228        Self::new(EntityParser {
229            single: true,
230            players_only: true,
231        })
232    }
233
234    pub(crate) fn players() -> Self {
235        Self::new(EntityParser {
236            single: false,
237            players_only: true,
238        })
239    }
240
241    pub(crate) fn score_holder() -> Self {
242        Self::new(ScoreHolderParser { multiple: false })
243    }
244
245    pub(crate) fn non_operator_profile() -> Self {
246        Self::new(GameProfileParser::new(
247            GameProfileSuggestionMode::NonOperators,
248        ))
249    }
250
251    pub(crate) fn game_profile() -> Self {
252        Self::new(GameProfileParser::new(GameProfileSuggestionMode::All))
253    }
254
255    pub(crate) fn operator_profile() -> Self {
256        Self::new(GameProfileParser::new(GameProfileSuggestionMode::Operators))
257    }
258
259    pub(crate) fn permission_rule() -> Self {
260        Self::new(PermissionRuleParser::all())
261    }
262
263    pub(crate) fn user_permission_rule() -> Self {
264        Self::new(PermissionRuleParser::user_owned())
265    }
266
267    pub(crate) fn group_permission_rule() -> Self {
268        Self::new(PermissionRuleParser::group_owned())
269    }
270
271    pub(crate) fn permission_metadata() -> Self {
272        Self::new(PermissionMetadataParser::all())
273    }
274
275    pub(crate) fn user_permission_metadata() -> Self {
276        Self::new(PermissionMetadataParser::user_owned())
277    }
278
279    pub(crate) fn group_permission_metadata() -> Self {
280        Self::new(PermissionMetadataParser::group_owned())
281    }
282
283    pub(crate) fn permission_group(require_existing: bool) -> Self {
284        Self::new(PermissionGroupParser { require_existing })
285    }
286
287    pub(crate) fn score_holders() -> Self {
288        Self::new(ScoreHolderParser { multiple: true })
289    }
290
291    pub(crate) fn objective() -> Self {
292        Self::new(ObjectiveParser)
293    }
294
295    pub(crate) fn int_range() -> Self {
296        Self::new(IntRangeParser)
297    }
298
299    pub(crate) fn biome_or_tag() -> Self {
300        Self::new(BiomeOrTagParser)
301    }
302
303    pub(crate) fn structure_or_tag_key() -> Self {
304        Self::new(StructureOrTagKeyParser)
305    }
306
307    pub(crate) fn block_predicate() -> Self {
308        Self::new(BlockPredicateParser)
309    }
310
311    pub(crate) fn block_state() -> Self {
312        Self::new(BlockStateParser)
313    }
314
315    pub(crate) fn game_mode() -> Self {
316        Self::new(GameModeParser)
317    }
318
319    pub(crate) fn domain() -> Self {
320        Self::new(DomainParser)
321    }
322
323    pub(crate) fn world() -> Self {
324        Self::new(WorldParser)
325    }
326
327    pub(crate) fn summonable_entity() -> Self {
328        Self::new(SummonableEntityParser)
329    }
330
331    pub(crate) fn enchantment() -> Self {
332        Self::new(EnchantmentParser)
333    }
334
335    pub(crate) fn damage_type() -> Self {
336        Self::new(DamageTypeParser)
337    }
338
339    pub(crate) fn item_stack() -> Self {
340        Self::new(ItemStackParser)
341    }
342
343    pub(crate) fn item_predicate() -> Self {
344        Self::new(ItemPredicateParser)
345    }
346
347    pub(crate) fn component() -> Self {
348        Self::new(ComponentParser)
349    }
350
351    pub(crate) fn nbt_path() -> Self {
352        Self::new(NbtPathParser)
353    }
354
355    pub(crate) fn storage_key() -> Self {
356        Self::new(StorageKeyParser)
357    }
358
359    pub(crate) fn sound() -> Self {
360        Self::new(SoundParser)
361    }
362
363    pub(crate) fn advancement() -> Self {
364        Self::new(AdvancementParser)
365    }
366
367    pub(crate) fn world_clock() -> Self {
368        Self::new(WorldClockParser)
369    }
370
371    pub(crate) fn timeline(clock_argument: Option<&'static str>) -> Self {
372        Self::new(TimelineParser { clock_argument })
373    }
374
375    pub(crate) fn time_marker(clock_argument: Option<&'static str>) -> Self {
376        Self::new(TimeMarkerParser { clock_argument })
377    }
378
379    pub(crate) fn protocol_argument(
380        &self,
381    ) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
382        self.0.protocol_argument_erased()
383    }
384
385    #[cfg(test)]
386    pub(crate) fn parser_type_key(&self) -> DowncastTypeKey {
387        self.0.downcast_type_key()
388    }
389}
390
391impl fmt::Debug for SteelArgumentType {
392    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
393        formatter
394            .debug_struct("SteelArgumentType")
395            .field("type_key", &self.0.downcast_type_key())
396            .field("parser", &self.0)
397            .finish()
398    }
399}
400
401impl PartialEq for SteelArgumentType {
402    fn eq(&self, other: &Self) -> bool {
403        self.0.equals_erased(other.0.as_ref())
404    }
405}
406
407impl From<ArgumentType> for SteelArgumentType {
408    fn from(argument: ArgumentType) -> Self {
409        Self::new(PrimitiveParser(argument))
410    }
411}
412
413trait ErasedSteelArgumentValue: ErasedType + fmt::Debug + Send + Sync {}
414
415impl<T> ErasedSteelArgumentValue for T where T: DowncastType + fmt::Debug + Send + Sync {}
416
417/// A keyed parsed value retained by Steel's command runtime.
418#[derive(Clone)]
419pub(crate) struct SteelArgumentValue(Arc<dyn ErasedSteelArgumentValue>);
420
421impl SteelArgumentValue {
422    pub(crate) fn new(value: impl DowncastType + fmt::Debug + Send + Sync) -> Self {
423        Self(Arc::new(value))
424    }
425
426    pub(crate) fn downcast_ref<T: DowncastType>(&self) -> Option<&T> {
427        self.0.downcast_ref::<T>()
428    }
429
430    #[cfg(test)]
431    pub(crate) fn type_key(&self) -> DowncastTypeKey {
432        self.0.downcast_type_key()
433    }
434}
435
436impl fmt::Debug for SteelArgumentValue {
437    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
438        formatter
439            .debug_struct("SteelArgumentValue")
440            .field("type_key", &self.0.downcast_type_key())
441            .field("value", &self.0)
442            .finish()
443    }
444}
445
446impl ContainsPrimitiveArgumentValue for SteelArgumentValue {
447    fn primitive_value(&self, name: &str) -> Result<&PrimitiveArgumentValue, CommandSyntaxError> {
448        self.downcast_ref::<PrimitiveArgumentValue>()
449            .ok_or_else(|| incorrectly_typed_argument(name))
450    }
451}
452
453/// Suggestion context exposed to erased Steel argument parsers.
454pub(crate) trait SteelArgumentSuggestionContext {
455    fn source(&self) -> &dyn CommandArgumentSource;
456
457    fn argument(&self, name: &str) -> Result<&SteelArgumentValue, CommandSyntaxError>;
458}
459
460impl<S> SteelArgumentSuggestionContext for ArgumentSuggestionContext<'_, S, SteelArgumentValue>
461where
462    S: CommandArgumentSource,
463{
464    fn source(&self) -> &dyn CommandArgumentSource {
465        ArgumentSuggestionContext::source(self)
466    }
467
468    fn argument(&self, name: &str) -> Result<&SteelArgumentValue, CommandSyntaxError> {
469        ArgumentSuggestionContext::argument(self, name)
470    }
471}
472
473impl<S> CommandArgumentParser<S> for SteelArgumentType
474where
475    S: CommandArgumentSource,
476{
477    type Value = SteelArgumentValue;
478
479    fn parse(
480        &self,
481        reader: &mut StringReader<'_>,
482        source: &S,
483    ) -> Result<Self::Value, CommandSyntaxError> {
484        self.0.parse_erased(reader, source)
485    }
486
487    fn list_suggestions(
488        &self,
489        context: &ArgumentSuggestionContext<'_, S, Self::Value>,
490        builder: &mut SuggestionsBuilder<'_>,
491    ) {
492        self.0.list_suggestions_erased(context, builder);
493    }
494}
495
496macro_rules! impl_downcast_type {
497    ($type:ty, $key:literal) => {
498        // SAFETY: This Steel-owned key uniquely identifies the concrete type in the process.
499        unsafe impl DowncastType for $type {
500            const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new($key);
501        }
502    };
503}
504
505impl_downcast_type!(PrimitiveArgumentValue, "steel:command/value/primitive");
506impl_downcast_type!(Coordinates, "steel:command/value/coordinates");
507impl_downcast_type!(EntityAnchor, "steel:command/value/entity_anchor");
508impl_downcast_type!(CoordinateAxes, "steel:command/value/swizzle");
509impl_downcast_type!(HeightmapType, "steel:command/value/heightmap");
510impl_downcast_type!(EntitySelector, "steel:command/value/entity_selector");
511impl_downcast_type!(ScoreHolderArgument, "steel:command/value/score_holder");
512impl_downcast_type!(IntRange, "steel:command/value/int_range");
513impl_downcast_type!(BiomeOrTag, "steel:command/value/biome_or_tag");
514impl_downcast_type!(
515    StructureOrTagKey,
516    "steel:command/value/structure_or_tag_key"
517);
518impl_downcast_type!(BlockPredicate, "steel:command/value/block_predicate");
519impl_downcast_type!(BlockInput, "steel:command/value/block_input");
520impl_downcast_type!(WorldArgument, "steel:command/value/world");
521impl_downcast_type!(ItemPredicate, "steel:command/value/item_predicate");
522
523macro_rules! argument_value_wrapper {
524    ($name:ident($value:ty), $key:literal) => {
525        #[derive(Debug)]
526        pub(super) struct $name(pub(super) $value);
527
528        impl_downcast_type!($name, $key);
529    };
530}
531
532argument_value_wrapper!(TimeValue(i32), "steel:command/value/time");
533argument_value_wrapper!(ObjectiveValue(Box<str>), "steel:command/value/objective");
534argument_value_wrapper!(GameModeValue(GameType), "steel:command/value/game_mode");
535argument_value_wrapper!(DomainValue(Box<str>), "steel:command/value/domain");
536argument_value_wrapper!(
537    EntityTypeValue(EntityTypeRef),
538    "steel:command/value/entity_type"
539);
540argument_value_wrapper!(
541    EnchantmentValue(EnchantmentRef),
542    "steel:command/value/enchantment"
543);
544argument_value_wrapper!(
545    AdvancementValue(AdvancementRef),
546    "steel:command/value/advancement"
547);
548argument_value_wrapper!(
549    DamageTypeValue(DamageTypeRef),
550    "steel:command/value/damage_type"
551);
552argument_value_wrapper!(ItemStackValue(ItemStack), "steel:command/value/item_stack");
553argument_value_wrapper!(
554    ComponentValue(TextComponent),
555    "steel:command/value/component"
556);
557argument_value_wrapper!(NbtPathValue(NbtPath), "steel:command/value/nbt_path");
558argument_value_wrapper!(
559    IdentifierValue(Identifier),
560    "steel:command/value/identifier"
561);
562argument_value_wrapper!(
563    WorldClockValue(WorldClockRef),
564    "steel:command/value/world_clock"
565);
566argument_value_wrapper!(TimelineValue(TimelineRef), "steel:command/value/timeline");
567
568macro_rules! unit_argument_parser {
569    (
570        $parser:ident,
571        $key:literal,
572        $value:ty,
573        parse |$reader:ident, $source:ident| $parse:block,
574        suggest |$context:ident, $builder:ident| $suggest:block,
575        protocol $protocol:expr
576    ) => {
577        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
578        struct $parser;
579
580        impl_downcast_type!($parser, $key);
581
582        impl SteelArgumentParser for $parser {
583            type Value = $value;
584
585            fn parse(
586                &self,
587                $reader: &mut StringReader<'_>,
588                $source: &dyn CommandArgumentSource,
589            ) -> Result<Self::Value, CommandSyntaxError> $parse
590
591            fn list_suggestions(
592                &self,
593                $context: &dyn SteelArgumentSuggestionContext,
594                $builder: &mut SuggestionsBuilder<'_>,
595            ) $suggest
596
597            fn protocol_argument(
598                &self,
599            ) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
600                $protocol
601            }
602        }
603    };
604}
605
606#[derive(Clone, Debug, PartialEq)]
607struct PrimitiveParser(ArgumentType);
608
609impl_downcast_type!(PrimitiveParser, "steel:command/parser/primitive");
610
611impl SteelArgumentParser for PrimitiveParser {
612    type Value = PrimitiveArgumentValue;
613
614    fn parse(
615        &self,
616        reader: &mut StringReader<'_>,
617        _source: &dyn CommandArgumentSource,
618    ) -> Result<Self::Value, CommandSyntaxError> {
619        self.0.parse_value(reader)
620    }
621
622    fn list_suggestions(
623        &self,
624        _context: &dyn SteelArgumentSuggestionContext,
625        builder: &mut SuggestionsBuilder<'_>,
626    ) {
627        self.0.suggest(builder);
628    }
629
630    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
631        (protocol_argument_type(&self.0), None)
632    }
633}
634
635#[derive(Clone, Copy, Debug, PartialEq, Eq)]
636struct TimeParser {
637    minimum: i32,
638}
639
640impl_downcast_type!(TimeParser, "steel:command/parser/time");
641
642impl SteelArgumentParser for TimeParser {
643    type Value = TimeValue;
644
645    fn parse(
646        &self,
647        reader: &mut StringReader<'_>,
648        _source: &dyn CommandArgumentSource,
649    ) -> Result<Self::Value, CommandSyntaxError> {
650        parse_time(reader, self.minimum).map(TimeValue)
651    }
652
653    fn list_suggestions(
654        &self,
655        _context: &dyn SteelArgumentSuggestionContext,
656        builder: &mut SuggestionsBuilder<'_>,
657    ) {
658        suggest_time_units(builder);
659    }
660
661    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
662        (ProtocolArgumentType::Time { min: self.minimum }, None)
663    }
664}
665
666unit_argument_parser!(
667    BlockPosParser,
668    "steel:command/parser/block_pos",
669    Coordinates,
670    parse | reader,
671    _source | { parse_block_pos(reader) },
672    suggest | _context,
673    builder | {
674        suggest_coordinates(builder, parse_block_pos);
675    },
676    protocol(ProtocolArgumentType::BlockPos, None)
677);
678
679#[derive(Clone, Copy, Debug, PartialEq, Eq)]
680struct Vec3Parser {
681    center_integers: bool,
682}
683
684impl_downcast_type!(Vec3Parser, "steel:command/parser/vec3");
685
686impl SteelArgumentParser for Vec3Parser {
687    type Value = Coordinates;
688
689    fn parse(
690        &self,
691        reader: &mut StringReader<'_>,
692        _source: &dyn CommandArgumentSource,
693    ) -> Result<Self::Value, CommandSyntaxError> {
694        parse_vec3(reader, self.center_integers)
695    }
696
697    fn list_suggestions(
698        &self,
699        _context: &dyn SteelArgumentSuggestionContext,
700        builder: &mut SuggestionsBuilder<'_>,
701    ) {
702        suggest_coordinates(builder, |reader| parse_vec3(reader, self.center_integers));
703    }
704
705    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
706        (ProtocolArgumentType::Vec3, None)
707    }
708}
709
710#[derive(Clone, Copy, Debug, PartialEq, Eq)]
711struct Vec2Parser {
712    center_integers: bool,
713}
714
715impl_downcast_type!(Vec2Parser, "steel:command/parser/vec2");
716
717impl SteelArgumentParser for Vec2Parser {
718    type Value = Coordinates;
719
720    fn parse(
721        &self,
722        reader: &mut StringReader<'_>,
723        _source: &dyn CommandArgumentSource,
724    ) -> Result<Self::Value, CommandSyntaxError> {
725        parse_vec2(reader, self.center_integers)
726    }
727
728    fn list_suggestions(
729        &self,
730        _context: &dyn SteelArgumentSuggestionContext,
731        builder: &mut SuggestionsBuilder<'_>,
732    ) {
733        suggest_vec2(builder, |reader| parse_vec2(reader, self.center_integers));
734    }
735
736    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
737        (ProtocolArgumentType::Vec2, None)
738    }
739}
740
741unit_argument_parser!(
742    RotationParser,
743    "steel:command/parser/rotation",
744    Coordinates,
745    parse | reader,
746    _source | { parse_rotation(reader) },
747    suggest | _context,
748    _builder | {},
749    protocol(ProtocolArgumentType::Rotation, None)
750);
751unit_argument_parser!(
752    SwizzleParser,
753    "steel:command/parser/swizzle",
754    CoordinateAxes,
755    parse | reader,
756    _source | { parse_swizzle(reader) },
757    suggest | _context,
758    _builder | {},
759    protocol(ProtocolArgumentType::Swizzle, None)
760);
761unit_argument_parser!(
762    HeightmapParser,
763    "steel:command/parser/heightmap",
764    HeightmapType,
765    parse | reader,
766    _source | { parse_heightmap(reader) },
767    suggest | _context,
768    builder | {
769        suggest_heightmaps(builder);
770    },
771    protocol(ProtocolArgumentType::Heightmap, None)
772);
773unit_argument_parser!(
774    EntityAnchorParser,
775    "steel:command/parser/entity_anchor",
776    EntityAnchor,
777    parse | reader,
778    _source | { parse_entity_anchor(reader) },
779    suggest | _context,
780    builder | {
781        suggest_entity_anchors(builder);
782    },
783    protocol(ProtocolArgumentType::EntityAnchor, None)
784);
785
786#[derive(Clone, Copy, Debug, PartialEq, Eq)]
787struct EntityTagParser;
788
789impl_downcast_type!(EntityTagParser, "steel:command/parser/entity_tag");
790
791impl SteelArgumentParser for EntityTagParser {
792    type Value = PrimitiveArgumentValue;
793
794    fn parse(
795        &self,
796        reader: &mut StringReader<'_>,
797        _source: &dyn CommandArgumentSource,
798    ) -> Result<Self::Value, CommandSyntaxError> {
799        Ok(PrimitiveArgumentValue::String(
800            reader.read_unquoted_string().into(),
801        ))
802    }
803
804    fn list_suggestions(
805        &self,
806        context: &dyn SteelArgumentSuggestionContext,
807        builder: &mut SuggestionsBuilder<'_>,
808    ) {
809        let Ok(targets) = context.argument("targets") else {
810            return;
811        };
812        let Some(targets) = targets.downcast_ref::<EntitySelector>() else {
813            return;
814        };
815
816        let prefix = builder.remaining();
817        for tag in context
818            .source()
819            .entity_tag_suggestions(targets)
820            .into_iter()
821            .filter(|tag| matches_substring_ignore_case(prefix, tag))
822        {
823            builder.suggest(tag);
824        }
825    }
826
827    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
828        (
829            ProtocolArgumentType::String {
830                behavior: ArgumentStringTypeBehavior::SingleWord,
831            },
832            Some(ProtocolSuggestionType::AskServer),
833        )
834    }
835}
836
837#[derive(Clone, Copy, Debug, PartialEq, Eq)]
838struct EntityParser {
839    single: bool,
840    players_only: bool,
841}
842
843impl_downcast_type!(EntityParser, "steel:command/parser/entity");
844
845impl SteelArgumentParser for EntityParser {
846    type Value = EntitySelector;
847
848    fn parse(
849        &self,
850        reader: &mut StringReader<'_>,
851        source: &dyn CommandArgumentSource,
852    ) -> Result<Self::Value, CommandSyntaxError> {
853        parse_entity_selector(reader, source, self.single, self.players_only)
854    }
855
856    fn list_suggestions(
857        &self,
858        context: &dyn SteelArgumentSuggestionContext,
859        builder: &mut SuggestionsBuilder<'_>,
860    ) {
861        suggest_entity_selector(builder, context.source(), self.single, self.players_only);
862    }
863
864    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
865        (
866            ProtocolArgumentType::Entity {
867                flags: u8::from(self.single) | (u8::from(self.players_only) << 1),
868            },
869            None,
870        )
871    }
872}
873
874#[derive(Clone, Copy, Debug, PartialEq, Eq)]
875struct ScoreHolderParser {
876    multiple: bool,
877}
878
879impl_downcast_type!(ScoreHolderParser, "steel:command/parser/score_holder");
880
881impl SteelArgumentParser for ScoreHolderParser {
882    type Value = ScoreHolderArgument;
883
884    fn parse(
885        &self,
886        reader: &mut StringReader<'_>,
887        source: &dyn CommandArgumentSource,
888    ) -> Result<Self::Value, CommandSyntaxError> {
889        parse_score_holder(reader, source, self.multiple)
890    }
891
892    fn list_suggestions(
893        &self,
894        context: &dyn SteelArgumentSuggestionContext,
895        builder: &mut SuggestionsBuilder<'_>,
896    ) {
897        suggest_score_holders(builder, context.source());
898    }
899
900    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
901        (
902            ProtocolArgumentType::ScoreHolder {
903                flags: u8::from(self.multiple),
904            },
905            Some(ProtocolSuggestionType::AskServer),
906        )
907    }
908}
909
910unit_argument_parser!(
911    ObjectiveParser,
912    "steel:command/parser/objective",
913    ObjectiveValue,
914    parse | reader,
915    _source | { Ok(ObjectiveValue(reader.read_unquoted_string().into())) },
916    suggest | context,
917    builder | {
918        let prefix = builder.remaining();
919        for objective in context
920            .source()
921            .scoreboard_objective_names()
922            .into_iter()
923            .filter(|objective| objective.starts_with(prefix))
924        {
925            builder.suggest(objective);
926        }
927    },
928    protocol(
929        ProtocolArgumentType::Objective,
930        Some(ProtocolSuggestionType::AskServer),
931    )
932);
933unit_argument_parser!(
934    IntRangeParser,
935    "steel:command/parser/int_range",
936    IntRange,
937    parse | reader,
938    _source | { parse_int_range(reader) },
939    suggest | _context,
940    _builder | {},
941    protocol(ProtocolArgumentType::IntRange, None)
942);
943unit_argument_parser!(
944    BiomeOrTagParser,
945    "steel:command/parser/biome_or_tag",
946    BiomeOrTag,
947    parse | reader,
948    _source | { parse_biome_or_tag(reader) },
949    suggest | _context,
950    builder | {
951        suggest_biomes(builder);
952    },
953    protocol(
954        ProtocolArgumentType::ResourceOrTag {
955            identifier: "minecraft:worldgen/biome",
956        },
957        Some(ProtocolSuggestionType::AskServer),
958    )
959);
960unit_argument_parser!(
961    StructureOrTagKeyParser,
962    "steel:command/parser/structure_or_tag_key",
963    StructureOrTagKey,
964    parse | reader,
965    _source | { parse_structure_or_tag_key(reader) },
966    suggest | _context,
967    builder | {
968        suggest_structures(builder);
969    },
970    protocol(
971        ProtocolArgumentType::ResourceOrTagKey {
972            identifier: "minecraft:worldgen/structure",
973        },
974        Some(ProtocolSuggestionType::AskServer),
975    )
976);
977unit_argument_parser!(
978    BlockStateParser,
979    "steel:command/parser/block_state",
980    BlockInput,
981    parse | reader,
982    _source | { parse_block_input(reader) },
983    suggest | _context,
984    builder | {
985        suggest_block_inputs(builder);
986    },
987    protocol(ProtocolArgumentType::BlockState, None)
988);
989unit_argument_parser!(
990    BlockPredicateParser,
991    "steel:command/parser/block_predicate",
992    BlockPredicate,
993    parse | reader,
994    _source | { parse_block_predicate(reader) },
995    suggest | _context,
996    builder | {
997        suggest_blocks(builder);
998    },
999    protocol(ProtocolArgumentType::BlockPredicate, None)
1000);
1001unit_argument_parser!(
1002    GameModeParser,
1003    "steel:command/parser/game_mode",
1004    GameModeValue,
1005    parse | reader,
1006    _source | { parse_game_mode(reader).map(GameModeValue) },
1007    suggest | _context,
1008    builder | {
1009        suggest_game_modes(builder);
1010    },
1011    protocol(ProtocolArgumentType::Gamemode, None)
1012);
1013unit_argument_parser!(
1014    DomainParser,
1015    "steel:command/parser/domain",
1016    DomainValue,
1017    parse | reader,
1018    source | { parse_domain(reader, source).map(DomainValue) },
1019    suggest | context,
1020    builder | {
1021        let prefix = builder.remaining();
1022        for domain in context
1023            .source()
1024            .domain_names()
1025            .into_iter()
1026            .filter(|domain| domain.starts_with(prefix))
1027        {
1028            builder.suggest(domain);
1029        }
1030    },
1031    protocol(
1032        ProtocolArgumentType::ResourceLocation,
1033        Some(ProtocolSuggestionType::AskServer),
1034    )
1035);
1036unit_argument_parser!(
1037    WorldParser,
1038    "steel:command/parser/world",
1039    WorldArgument,
1040    parse | reader,
1041    _source | { parse_world_argument(reader) },
1042    suggest | context,
1043    builder | {
1044        suggest_worlds(builder, context.source());
1045    },
1046    protocol(
1047        ProtocolArgumentType::Dimension,
1048        Some(ProtocolSuggestionType::AskServer),
1049    )
1050);
1051unit_argument_parser!(
1052    SummonableEntityParser,
1053    "steel:command/parser/summonable_entity",
1054    EntityTypeValue,
1055    parse | reader,
1056    _source | { parse_summonable_entity(reader).map(EntityTypeValue) },
1057    suggest | _context,
1058    builder | {
1059        suggest_resources(
1060            REGISTRY
1061                .entity_types
1062                .iter()
1063                .filter(|(_, entity_type)| can_summon(entity_type))
1064                .map(|(_, entity_type)| &entity_type.key),
1065            builder,
1066        );
1067    },
1068    protocol(
1069        ProtocolArgumentType::Resource {
1070            identifier: "minecraft:entity_type",
1071        },
1072        Some(ProtocolSuggestionType::SummonableEntities),
1073    )
1074);
1075unit_argument_parser!(
1076    EnchantmentParser,
1077    "steel:command/parser/enchantment",
1078    EnchantmentValue,
1079    parse | reader,
1080    _source | {
1081        let key = parse_identifier(reader)?;
1082        REGISTRY.enchantments.by_key(&key).map_or_else(
1083            || Err(unknown_resource(reader, &key, &ENCHANTMENT_REGISTRY)),
1084            |enchantment| Ok(EnchantmentValue(enchantment)),
1085        )
1086    },
1087    suggest | _context,
1088    builder | {
1089        suggest_resources(
1090            REGISTRY
1091                .enchantments
1092                .iter()
1093                .map(|(_, enchantment)| &enchantment.key),
1094            builder,
1095        );
1096    },
1097    protocol(
1098        ProtocolArgumentType::Resource {
1099            identifier: "minecraft:enchantment",
1100        },
1101        None,
1102    )
1103);
1104unit_argument_parser!(
1105    DamageTypeParser,
1106    "steel:command/parser/damage_type",
1107    DamageTypeValue,
1108    parse | reader,
1109    _source | {
1110        let key = parse_identifier(reader)?;
1111        REGISTRY.damage_types.by_key(&key).map_or_else(
1112            || Err(unknown_resource(reader, &key, &DAMAGE_TYPE_REGISTRY)),
1113            |damage_type| Ok(DamageTypeValue(damage_type)),
1114        )
1115    },
1116    suggest | _context,
1117    builder | {
1118        suggest_resources(
1119            REGISTRY
1120                .damage_types
1121                .iter()
1122                .map(|(_, damage_type)| &damage_type.key),
1123            builder,
1124        );
1125    },
1126    protocol(
1127        ProtocolArgumentType::Resource {
1128            identifier: "minecraft:damage_type",
1129        },
1130        None,
1131    )
1132);
1133unit_argument_parser!(
1134    AdvancementParser,
1135    "steel:command/parser/advancement",
1136    AdvancementValue,
1137    parse | reader,
1138    _source | { Ok(AdvancementValue(parse_advancement(reader)?)) },
1139    suggest | _context,
1140    builder | {
1141        suggest_resources(
1142            REGISTRY
1143                .advancements
1144                .advancements()
1145                .iter()
1146                .map(|adv| adv.key()),
1147            builder,
1148        );
1149    },
1150    protocol(
1151        ProtocolArgumentType::ResourceLocation,
1152        Some(ProtocolSuggestionType::AskServer),
1153    )
1154);
1155unit_argument_parser!(
1156    ItemStackParser,
1157    "steel:command/parser/item_stack",
1158    ItemStackValue,
1159    parse | reader,
1160    _source | { parse_item_stack(reader).map(ItemStackValue) },
1161    suggest | _context,
1162    builder | {
1163        suggest_item_stack(builder);
1164    },
1165    protocol(
1166        ProtocolArgumentType::ItemStack,
1167        Some(ProtocolSuggestionType::AskServer),
1168    )
1169);
1170unit_argument_parser!(
1171    ItemPredicateParser,
1172    "steel:command/parser/item_predicate",
1173    ItemPredicate,
1174    parse | reader,
1175    _source | { parse_item_predicate(reader) },
1176    suggest | _context,
1177    builder | {
1178        suggest_item_predicate(builder);
1179    },
1180    protocol(
1181        ProtocolArgumentType::ItemPredicate,
1182        Some(ProtocolSuggestionType::AskServer),
1183    )
1184);
1185unit_argument_parser!(
1186    ComponentParser,
1187    "steel:command/parser/component",
1188    ComponentValue,
1189    parse | reader,
1190    _source | { parse_component(reader).map(ComponentValue) },
1191    suggest | _context,
1192    _builder | {},
1193    protocol(ProtocolArgumentType::Component, None)
1194);
1195unit_argument_parser!(
1196    NbtPathParser,
1197    "steel:command/parser/nbt_path",
1198    NbtPathValue,
1199    parse | reader,
1200    _source | { parse_nbt_path(reader).map(NbtPathValue) },
1201    suggest | _context,
1202    _builder | {},
1203    protocol(ProtocolArgumentType::NbtPath, None)
1204);
1205unit_argument_parser!(
1206    StorageKeyParser,
1207    "steel:command/parser/storage_key",
1208    IdentifierValue,
1209    parse | reader,
1210    _source | { parse_identifier(reader).map(IdentifierValue) },
1211    suggest | context,
1212    builder | {
1213        suggest_storage_keys(context.source(), builder);
1214    },
1215    protocol(
1216        ProtocolArgumentType::ResourceLocation,
1217        Some(ProtocolSuggestionType::AskServer),
1218    )
1219);
1220unit_argument_parser!(
1221    SoundParser,
1222    "steel:command/parser/sound",
1223    IdentifierValue,
1224    parse | reader,
1225    _source | { parse_identifier(reader).map(IdentifierValue) },
1226    suggest | _context,
1227    builder | {
1228        suggest_resources(
1229            REGISTRY.sound_events.iter().map(|(_, sound)| &sound.key),
1230            builder,
1231        );
1232    },
1233    protocol(
1234        ProtocolArgumentType::ResourceLocation,
1235        Some(ProtocolSuggestionType::AvailableSounds),
1236    )
1237);
1238unit_argument_parser!(
1239    WorldClockParser,
1240    "steel:command/parser/world_clock",
1241    WorldClockValue,
1242    parse | reader,
1243    _source | {
1244        let key = parse_identifier(reader)?;
1245        REGISTRY.world_clocks.by_key(&key).map_or_else(
1246            || Err(unknown_resource(reader, &key, &WORLD_CLOCK_REGISTRY)),
1247            |clock| Ok(WorldClockValue(clock)),
1248        )
1249    },
1250    suggest | _context,
1251    builder | {
1252        suggest_resources(
1253            REGISTRY.world_clocks.iter().map(|(_, clock)| &clock.key),
1254            builder,
1255        );
1256    },
1257    protocol(
1258        ProtocolArgumentType::Resource {
1259            identifier: "minecraft:world_clock",
1260        },
1261        None,
1262    )
1263);
1264
1265#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1266struct TimelineParser {
1267    clock_argument: Option<&'static str>,
1268}
1269
1270impl_downcast_type!(TimelineParser, "steel:command/parser/timeline");
1271
1272impl SteelArgumentParser for TimelineParser {
1273    type Value = TimelineValue;
1274
1275    fn parse(
1276        &self,
1277        reader: &mut StringReader<'_>,
1278        _source: &dyn CommandArgumentSource,
1279    ) -> Result<Self::Value, CommandSyntaxError> {
1280        let key = parse_identifier(reader)?;
1281        REGISTRY.timelines.by_key(&key).map_or_else(
1282            || Err(unknown_resource(reader, &key, &TIMELINE_REGISTRY)),
1283            |timeline| Ok(TimelineValue(timeline)),
1284        )
1285    }
1286
1287    fn list_suggestions(
1288        &self,
1289        context: &dyn SteelArgumentSuggestionContext,
1290        builder: &mut SuggestionsBuilder<'_>,
1291    ) {
1292        let Some(clock) = selected_clock(context, self.clock_argument) else {
1293            return;
1294        };
1295        suggest_resources(
1296            REGISTRY
1297                .timelines
1298                .iter()
1299                .filter(|(_, timeline)| timeline.clock == clock)
1300                .map(|(_, timeline)| &timeline.key),
1301            builder,
1302        );
1303    }
1304
1305    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
1306        (
1307            ProtocolArgumentType::Resource {
1308                identifier: "minecraft:timeline",
1309            },
1310            Some(ProtocolSuggestionType::AskServer),
1311        )
1312    }
1313}
1314
1315#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1316struct TimeMarkerParser {
1317    clock_argument: Option<&'static str>,
1318}
1319
1320impl_downcast_type!(TimeMarkerParser, "steel:command/parser/time_marker");
1321
1322impl SteelArgumentParser for TimeMarkerParser {
1323    type Value = IdentifierValue;
1324
1325    fn parse(
1326        &self,
1327        reader: &mut StringReader<'_>,
1328        _source: &dyn CommandArgumentSource,
1329    ) -> Result<Self::Value, CommandSyntaxError> {
1330        parse_identifier(reader).map(IdentifierValue)
1331    }
1332
1333    fn list_suggestions(
1334        &self,
1335        context: &dyn SteelArgumentSuggestionContext,
1336        builder: &mut SuggestionsBuilder<'_>,
1337    ) {
1338        let Some(clock) = selected_clock(context, self.clock_argument) else {
1339            return;
1340        };
1341        suggest_resources(
1342            REGISTRY
1343                .timelines
1344                .iter()
1345                .filter(|(_, timeline)| timeline.clock == clock)
1346                .flat_map(|(_, timeline)| timeline.time_markers)
1347                .filter(|marker| marker.show_in_commands == Some(true))
1348                .map(|marker| &marker.key),
1349            builder,
1350        );
1351    }
1352
1353    fn protocol_argument(&self) -> (ProtocolArgumentType, Option<ProtocolSuggestionType>) {
1354        (
1355            ProtocolArgumentType::ResourceLocation,
1356            Some(ProtocolSuggestionType::AskServer),
1357        )
1358    }
1359}
1360
1361fn selected_clock(
1362    context: &dyn SteelArgumentSuggestionContext,
1363    clock_argument: Option<&str>,
1364) -> Option<WorldClockRef> {
1365    let Some(clock_argument) = clock_argument else {
1366        return context.source().default_world_clock();
1367    };
1368    context
1369        .argument(clock_argument)
1370        .ok()?
1371        .downcast_ref::<WorldClockValue>()
1372        .map(|clock| clock.0)
1373}
1374
1375fn parse_component(reader: &mut StringReader<'_>) -> Result<TextComponent, CommandSyntaxError> {
1376    let start = reader.checkpoint();
1377    let (tag, consumed) = parse_snbt_argument(reader.remaining()).map_err(|error| {
1378        reader.advance_bytes(error.cursor());
1379        component_snbt_error(reader, error.component())
1380    })?;
1381    if !reader.advance_bytes(consumed) {
1382        return Err(component_snbt_error(
1383            reader,
1384            "Invalid text component cursor",
1385        ));
1386    }
1387
1388    let component = TextComponent::try_from_nbt(&tag).map_err(|error| {
1389        reader.restore(start);
1390        invalid_component(reader, error.to_string())
1391    })?;
1392    validate_component_syntax(&component).map_err(|error| {
1393        reader.restore(start);
1394        invalid_component(reader, error)
1395    })?;
1396    Ok(component)
1397}
1398
1399fn component_snbt_error(
1400    reader: &StringReader<'_>,
1401    message: impl Into<TextComponent>,
1402) -> CommandSyntaxError {
1403    reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message.into())))
1404}
1405
1406fn invalid_component(reader: &StringReader<'_>, message: String) -> CommandSyntaxError {
1407    reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1408        translations::ARGUMENT_COMPONENT_INVALID
1409            .message([message])
1410            .component(),
1411    )))
1412}
1413
1414fn parse_swizzle(reader: &mut StringReader<'_>) -> Result<CoordinateAxes, CommandSyntaxError> {
1415    let mut axes = CoordinateAxes::default();
1416    while reader.can_read() && reader.peek() != Some(' ') {
1417        let bit = match reader.read() {
1418            Some('x') => CoordinateAxes::X,
1419            Some('y') => CoordinateAxes::Y,
1420            Some('z') => CoordinateAxes::Z,
1421            Some(_) | None => return Err(invalid_swizzle(reader)),
1422        };
1423        if axes.0 & bit != 0 {
1424            return Err(invalid_swizzle(reader));
1425        }
1426        axes.0 |= bit;
1427    }
1428    Ok(axes)
1429}
1430
1431fn invalid_swizzle(reader: &StringReader<'_>) -> CommandSyntaxError {
1432    reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1433        TextComponent::from(&translations::ARGUMENTS_SWIZZLE_INVALID),
1434    )))
1435}
1436
1437fn parse_heightmap(reader: &mut StringReader<'_>) -> Result<HeightmapType, CommandSyntaxError> {
1438    let raw = reader.read_unquoted_string();
1439    match raw.to_ascii_lowercase().as_str() {
1440        "world_surface" => Ok(HeightmapType::WorldSurface),
1441        "motion_blocking" => Ok(HeightmapType::MotionBlocking),
1442        "motion_blocking_no_leaves" => Ok(HeightmapType::MotionBlockingNoLeaves),
1443        "ocean_floor" => Ok(HeightmapType::OceanFloor),
1444        _ => {
1445            let message = translations::ARGUMENT_ENUM_INVALID
1446                .message([raw.to_owned()])
1447                .component();
1448            Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message))))
1449        }
1450    }
1451}
1452
1453fn suggest_heightmaps(builder: &mut SuggestionsBuilder<'_>) {
1454    const HEIGHTMAPS: &[&str] = &[
1455        "world_surface",
1456        "motion_blocking",
1457        "motion_blocking_no_leaves",
1458        "ocean_floor",
1459    ];
1460    for heightmap in HEIGHTMAPS {
1461        if heightmap.starts_with(builder.remaining_lowercase()) {
1462            builder.suggest(*heightmap);
1463        }
1464    }
1465}
1466
1467fn parse_entity_anchor(reader: &mut StringReader<'_>) -> Result<EntityAnchor, CommandSyntaxError> {
1468    let start = reader.checkpoint();
1469    let name = reader.read_unquoted_string();
1470    match name {
1471        "feet" => Ok(EntityAnchor::Feet),
1472        "eyes" => Ok(EntityAnchor::Eyes),
1473        _ => {
1474            reader.restore(start);
1475            let message = translations::ARGUMENT_ANCHOR_INVALID
1476                .message([name.to_owned()])
1477                .component();
1478            Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message))))
1479        }
1480    }
1481}
1482
1483fn suggest_entity_anchors(builder: &mut SuggestionsBuilder<'_>) {
1484    let prefix = builder.remaining_lowercase().to_owned();
1485    for anchor in ["feet", "eyes"] {
1486        if anchor.starts_with(&prefix) {
1487            builder.suggest(anchor);
1488        }
1489    }
1490}
1491
1492fn parse_game_mode(reader: &mut StringReader<'_>) -> Result<GameType, CommandSyntaxError> {
1493    let name = reader.read_unquoted_string();
1494    let game_mode = match name {
1495        "survival" => GameType::Survival,
1496        "creative" => GameType::Creative,
1497        "adventure" => GameType::Adventure,
1498        "spectator" => GameType::Spectator,
1499        _ => {
1500            let message = translations::ARGUMENT_GAMEMODE_INVALID
1501                .message([name.to_owned()])
1502                .component();
1503            return Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message))));
1504        }
1505    };
1506    Ok(game_mode)
1507}
1508
1509fn suggest_game_modes(builder: &mut SuggestionsBuilder<'_>) {
1510    let prefix = builder.remaining_lowercase().to_owned();
1511    for game_mode in [
1512        GameType::Survival,
1513        GameType::Creative,
1514        GameType::Adventure,
1515        GameType::Spectator,
1516    ] {
1517        let name = game_mode.name();
1518        if name.starts_with(&prefix) {
1519            builder.suggest(name);
1520        }
1521    }
1522}
1523
1524fn parse_summonable_entity(
1525    reader: &mut StringReader<'_>,
1526) -> Result<EntityTypeRef, CommandSyntaxError> {
1527    let key = parse_identifier(reader)?;
1528    let Some(entity_type) = REGISTRY.entity_types.by_key(&key) else {
1529        return Err(unknown_resource(reader, &key, &ENTITY_TYPE_REGISTRY));
1530    };
1531    if can_summon(entity_type) {
1532        return Ok(entity_type);
1533    }
1534    let message = translations::ENTITY_NOT_SUMMONABLE
1535        .message([key.to_string()])
1536        .component();
1537    Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message))))
1538}
1539
1540fn parse_advancement(reader: &mut StringReader<'_>) -> Result<AdvancementRef, CommandSyntaxError> {
1541    parse_identifier(reader).and_then(|key| {
1542        REGISTRY.advancements.by_key(&key).map_or(
1543            Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1544                TextComponent::translated(
1545                    ADVANCEMENT_ADVANCEMENT_NOT_FOUND.message([key.to_string()]),
1546                ),
1547            )))),
1548            |val| Ok(val.value()),
1549        )
1550    })
1551}
1552
1553fn can_summon(entity_type: EntityTypeRef) -> bool {
1554    entity_type.summonable
1555        && ENTITIES
1556            .get()
1557            .is_some_and(|registry| registry.has_factory(entity_type))
1558}
1559
1560fn parse_domain<S>(
1561    reader: &mut StringReader<'_>,
1562    source: &S,
1563) -> Result<Box<str>, CommandSyntaxError>
1564where
1565    S: CommandArgumentSource + ?Sized,
1566{
1567    let domain = reader.read_unquoted_string();
1568    if source.domain_exists(domain) {
1569        return Ok(domain.into());
1570    }
1571    Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1572        TextComponent::from(format!("Unknown domain {domain}")),
1573    ))))
1574}
1575
1576pub(super) fn parse_identifier(
1577    reader: &mut StringReader<'_>,
1578) -> Result<Identifier, CommandSyntaxError> {
1579    let start = reader.checkpoint();
1580    let start_byte = reader.read_so_far().len();
1581    while reader.peek().is_some_and(is_allowed_in_identifier) {
1582        reader.skip();
1583    }
1584    let raw = &reader.read_so_far()[start_byte..];
1585    let (namespace, path) =
1586        raw.split_once(':')
1587            .map_or((Identifier::VANILLA_NAMESPACE, raw), |(namespace, path)| {
1588                if namespace.is_empty() {
1589                    (Identifier::VANILLA_NAMESPACE, path)
1590                } else {
1591                    (namespace, path)
1592                }
1593            });
1594    if namespace != ".."
1595        && Identifier::validate_namespace(namespace)
1596        && Identifier::validate_path(path)
1597    {
1598        return Ok(Identifier::new(namespace.to_owned(), path.to_owned()));
1599    }
1600
1601    reader.restore(start);
1602    Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1603        TextComponent::from(&translations::ARGUMENT_ID_INVALID),
1604    ))))
1605}
1606
1607const fn is_allowed_in_identifier(character: char) -> bool {
1608    character.is_ascii_digit()
1609        || character.is_ascii_lowercase()
1610        || matches!(character, '_' | ':' | '/' | '.' | '-')
1611}
1612
1613pub(super) fn unknown_resource(
1614    reader: &StringReader<'_>,
1615    key: &Identifier,
1616    registry: &Identifier,
1617) -> CommandSyntaxError {
1618    let message = translations::ARGUMENT_RESOURCE_NOT_FOUND
1619        .message([key.to_string(), registry.to_string()])
1620        .component();
1621    reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message)))
1622}
1623
1624fn suggest_storage_keys<S>(source: &S, builder: &mut SuggestionsBuilder<'_>)
1625where
1626    S: CommandArgumentSource + ?Sized,
1627{
1628    let keys = source
1629        .command_storage_keys()
1630        .into_iter()
1631        .filter_map(|key| key.parse::<Identifier>().ok())
1632        .collect::<Vec<_>>();
1633    suggest_resources(keys.iter(), builder);
1634}
1635
1636fn parse_time(reader: &mut StringReader<'_>, minimum: i32) -> Result<i32, CommandSyntaxError> {
1637    let value = reader.read_float()?;
1638    let unit = reader.read_unquoted_string();
1639    let factor = match unit {
1640        "d" => 24_000.0,
1641        "s" => 20.0,
1642        "t" | "" => 1.0,
1643        _ => {
1644            return Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(
1645                TextComponent::from(&translations::ARGUMENT_TIME_INVALID_UNIT),
1646            ))));
1647        }
1648    };
1649    let ticks = java_round(value * factor);
1650    if ticks < minimum {
1651        let message = translations::ARGUMENT_TIME_TICK_COUNT_TOO_LOW
1652            .message([minimum.to_string(), ticks.to_string()])
1653            .component();
1654        return Err(reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message))));
1655    }
1656    Ok(ticks)
1657}
1658
1659fn suggest_time_units(builder: &mut SuggestionsBuilder<'_>) {
1660    let mut reader = StringReader::new(builder.remaining());
1661    if reader.read_float().is_err() {
1662        return;
1663    }
1664    let number = reader.read_so_far();
1665    let unit = reader.read_unquoted_string();
1666    for candidate in ["d", "s", "t"] {
1667        if candidate.starts_with(unit) {
1668            builder.suggest(format!("{number}{candidate}"));
1669        }
1670    }
1671}
1672
1673fn java_round(value: f32) -> i32 {
1674    (value + 0.5).floor() as i32
1675}