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#[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
90pub(crate) trait SteelArgumentParser:
92 DowncastType + fmt::Debug + PartialEq + Send + Sync + 'static
93{
94 type Value: DowncastType + fmt::Debug + Send + Sync + 'static;
96
97 fn parse(
99 &self,
100 reader: &mut StringReader<'_>,
101 source: &dyn CommandArgumentSource,
102 ) -> Result<Self::Value, CommandSyntaxError>;
103
104 fn list_suggestions(
106 &self,
107 _context: &dyn SteelArgumentSuggestionContext,
108 _builder: &mut SuggestionsBuilder<'_>,
109 ) {
110 }
111
112 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#[derive(Clone)]
165pub(crate) struct SteelArgumentType(Arc<dyn ErasedSteelArgumentParser>);
166
167impl SteelArgumentType {
168 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#[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
453pub(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 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}