1use rustc_hash::FxHashMap;
2use steel_protocol::packets::game::{
3 ArgumentStringTypeBehavior, ArgumentType as ProtocolArgumentType, CCommandSuggestions,
4 CCommands, CommandNode as ProtocolCommandNode, CommandNodeInfo, SuggestionEntry,
5 SuggestionType,
6};
7use thiserror::Error;
8
9use super::brigadier::{
10 ArgumentType, CommandDispatcher, CommandRuntime, NodeId, NodeKind, StringType, Suggestions,
11};
12use super::execution::SteelArgumentType;
13
14const MAX_COMMAND_SUGGESTIONS: usize = 1000;
15
16pub(crate) fn command_suggestions_packet(
18 transaction_id: i32,
19 suggestions: &Suggestions,
20) -> CCommandSuggestions {
21 let range = suggestions.range();
22 let start = range.start() as i32;
25 let length = range.len() as i32;
26 let entries = suggestions
27 .list()
28 .iter()
29 .take(MAX_COMMAND_SUGGESTIONS)
30 .map(|suggestion| match suggestion.tooltip() {
31 Some(tooltip) => SuggestionEntry::with_tooltip(suggestion.text(), tooltip.clone()),
32 None => SuggestionEntry::new(suggestion.text()),
33 })
34 .collect();
35 CCommandSuggestions::new(transaction_id, start, length, entries)
36}
37
38#[derive(Clone, Debug, Error, PartialEq, Eq)]
40pub(crate) enum CommandTreeProjectionError {
41 #[error("command graph contains an unknown node {0:?}")]
42 UnknownNode(NodeId),
43 #[error("command graph contains more nodes than the protocol can index")]
44 TooManyNodes,
45 #[error("argument node {0:?} has no argument parser")]
46 MissingArgumentType(NodeId),
47 #[error("visible command node {node:?} redirects to filtered node {target:?}")]
48 HiddenRedirectTarget { node: NodeId, target: NodeId },
49}
50
51pub(crate) fn command_tree_packet<S, R>(
53 dispatcher: &CommandDispatcher<S, R>,
54 source: &S,
55) -> Result<CCommands, CommandTreeProjectionError>
56where
57 R: CommandRuntime<S>,
58 R::Argument: CommandArgumentProtocol,
59{
60 let visible = visible_nodes(dispatcher, source)?;
61 let mut indices = FxHashMap::default();
62 for (index, node) in visible.iter().copied().enumerate() {
63 let Ok(index) = i32::try_from(index) else {
64 return Err(CommandTreeProjectionError::TooManyNodes);
65 };
66 indices.insert(node, index);
67 }
68
69 let mut nodes = Vec::with_capacity(visible.len());
70 for node_id in visible {
71 let node = dispatcher
72 .node(node_id)
73 .ok_or(CommandTreeProjectionError::UnknownNode(node_id))?;
74 let children = dispatcher
75 .children(node_id)
76 .ok_or(CommandTreeProjectionError::UnknownNode(node_id))?
77 .iter()
78 .filter_map(|child| indices.get(child).copied())
79 .collect();
80 let redirect = match node.redirect() {
81 Some(target) => Some(indices.get(&target).copied().ok_or(
82 CommandTreeProjectionError::HiddenRedirectTarget {
83 node: node_id,
84 target,
85 },
86 )?),
87 None => None,
88 };
89
90 let mut info = CommandNodeInfo::new(children);
91 if node.can_execute(source) {
92 info = info.executable();
93 }
94 if node.is_restricted() {
95 info = info.restricted();
96 }
97 if let Some(target) = redirect {
98 info = info.redirect(target);
99 }
100
101 let projected = match node.kind() {
102 NodeKind::Root => {
103 let mut root = ProtocolCommandNode::new_root();
104 root.set_children(
105 dispatcher
106 .children(node_id)
107 .ok_or(CommandTreeProjectionError::UnknownNode(node_id))?
108 .iter()
109 .filter_map(|child| indices.get(child).copied())
110 .collect(),
111 );
112 root
113 }
114 NodeKind::Literal => ProtocolCommandNode::new_literal(info, node.name().to_owned()),
115 NodeKind::Argument => {
116 let argument = node
117 .argument_type()
118 .ok_or(CommandTreeProjectionError::MissingArgumentType(node_id))?;
119 ProtocolCommandNode::new_argument(
120 info,
121 node.name().to_owned(),
122 argument.protocol_argument(),
123 )
124 }
125 };
126 nodes.push(projected);
127 }
128
129 Ok(CCommands {
130 nodes,
131 root_index: 0,
132 })
133}
134
135fn visible_nodes<S, R>(
136 dispatcher: &CommandDispatcher<S, R>,
137 source: &S,
138) -> Result<Vec<NodeId>, CommandTreeProjectionError>
139where
140 R: CommandRuntime<S>,
141{
142 let mut visible = Vec::new();
143 let mut pending = vec![dispatcher.root()];
144 while let Some(node_id) = pending.pop() {
145 visible.push(node_id);
146 let children = dispatcher
147 .children(node_id)
148 .ok_or(CommandTreeProjectionError::UnknownNode(node_id))?;
149 for child in children.iter().rev() {
150 let node = dispatcher
151 .node(*child)
152 .ok_or(CommandTreeProjectionError::UnknownNode(*child))?;
153 if node.allows(source) {
154 pending.push(*child);
155 }
156 }
157 }
158 Ok(visible)
159}
160
161pub(crate) trait CommandArgumentProtocol {
162 fn protocol_argument(&self) -> (ProtocolArgumentType, Option<SuggestionType>);
163}
164
165impl CommandArgumentProtocol for ArgumentType {
166 fn protocol_argument(&self) -> (ProtocolArgumentType, Option<SuggestionType>) {
167 (protocol_argument_type(self), None)
168 }
169}
170
171impl CommandArgumentProtocol for SteelArgumentType {
172 fn protocol_argument(&self) -> (ProtocolArgumentType, Option<SuggestionType>) {
173 SteelArgumentType::protocol_argument(self)
174 }
175}
176
177pub(super) fn protocol_argument_type(argument: &ArgumentType) -> ProtocolArgumentType {
178 match *argument {
179 ArgumentType::Bool => ProtocolArgumentType::Bool,
180 ArgumentType::Integer { minimum, maximum } => ProtocolArgumentType::Integer {
181 min: (minimum != i32::MIN).then_some(minimum),
182 max: (maximum != i32::MAX).then_some(maximum),
183 },
184 ArgumentType::Long { minimum, maximum } => ProtocolArgumentType::Long {
185 min: (minimum != i64::MIN).then_some(minimum),
186 max: (maximum != i64::MAX).then_some(maximum),
187 },
188 ArgumentType::Float { minimum, maximum } => ProtocolArgumentType::Float {
189 min: (minimum.to_bits() != (-f32::MAX).to_bits()).then_some(minimum),
190 max: (maximum.to_bits() != f32::MAX.to_bits()).then_some(maximum),
191 },
192 ArgumentType::Double { minimum, maximum } => ProtocolArgumentType::Double {
193 min: (minimum.to_bits() != (-f64::MAX).to_bits()).then_some(minimum),
194 max: (maximum.to_bits() != f64::MAX.to_bits()).then_some(maximum),
195 },
196 ArgumentType::String(string_type) => ProtocolArgumentType::String {
197 behavior: match string_type {
198 StringType::Word => ArgumentStringTypeBehavior::SingleWord,
199 StringType::QuotablePhrase => ArgumentStringTypeBehavior::QuotablePhrase,
200 StringType::GreedyPhrase => ArgumentStringTypeBehavior::GreedyPhrase,
201 },
202 },
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::{
209 CommandTreeProjectionError, MAX_COMMAND_SUGGESTIONS, command_suggestions_packet,
210 command_tree_packet, protocol_argument_type,
211 };
212 use crate::command::brigadier::{
213 ArgumentType, CommandDispatcher, CommandNodeBuilder, CommandRequirement,
214 CommandSyntaxError, NodeId, StringRange, StringReader, StringType, Suggestion, Suggestions,
215 argument, literal,
216 };
217 use crate::command::execution::{
218 CommandArgumentSource, CommandResultCallback, ExecutionCommandSource, SteelArgumentType,
219 SteelCommandRuntime, argument as steel_argument, literal as steel_literal,
220 };
221 use steel_protocol::packets::game::{
222 ArgumentStringTypeBehavior, ArgumentType as ProtocolArgumentType,
223 CommandNode as ProtocolCommandNode, SuggestionType,
224 };
225 use steel_utils::serial::WriteTo;
226 use text_components::TextComponent;
227
228 #[derive(Clone, Copy)]
229 struct TestSource {
230 authorized: bool,
231 in_context: bool,
232 }
233
234 #[derive(Clone, Copy)]
235 struct SteelTestSource;
236
237 impl CommandArgumentSource for SteelTestSource {}
238
239 impl ExecutionCommandSource for SteelTestSource {
240 fn with_callback(&self, _callback: CommandResultCallback) -> Self {
241 *self
242 }
243
244 fn callback(&self) -> CommandResultCallback {
245 CommandResultCallback::empty()
246 }
247
248 fn handle_error(&self, _error: &CommandSyntaxError, _forked: bool) {}
249 }
250
251 fn register(
252 dispatcher: &mut CommandDispatcher<TestSource>,
253 builder: CommandNodeBuilder<TestSource>,
254 ) -> NodeId {
255 let Ok(node) = dispatcher.register(builder) else {
256 panic!("test command should register");
257 };
258 node
259 }
260
261 #[test]
262 fn leading_slash_remains_in_the_packet_replacement_range() {
263 let mut dispatcher = CommandDispatcher::<()>::new();
264 assert!(dispatcher.register(literal("help")).is_ok());
265 let mut reader = StringReader::new("/he");
266 assert!(reader.skip());
267
268 let parse = dispatcher.parse_reader(reader, ());
269 let suggestions = dispatcher.completion_suggestions(&parse);
270 let Ok(suggestions) = suggestions else {
271 panic!("slash-prefixed suggestions should build");
272 };
273 let packet = command_suggestions_packet(7, &suggestions);
274
275 assert_eq!(packet.id, 7);
276 assert_eq!(packet.start, 1);
277 assert_eq!(packet.length, 2);
278 assert_eq!(packet.suggestions.len(), 1);
279 assert_eq!(packet.suggestions[0].text, "help");
280 }
281
282 #[test]
283 fn packet_projection_preserves_utf16_range_and_tooltip() {
284 let tooltip = TextComponent::plain("details");
285 let suggestions = Suggestions::new(
286 StringRange::between(2, 4),
287 vec![Suggestion::with_tooltip(
288 StringRange::between(2, 4),
289 "value",
290 tooltip.clone(),
291 )],
292 );
293
294 let packet = command_suggestions_packet(11, &suggestions);
295
296 assert_eq!(packet.start, 2);
297 assert_eq!(packet.length, 2);
298 assert_eq!(packet.suggestions[0].text, "value");
299 assert_eq!(packet.suggestions[0].tooltip.as_ref(), Some(&tooltip));
300 }
301
302 #[test]
303 fn packet_projection_applies_vanillas_suggestion_limit() {
304 let range = StringRange::at(0);
305 let suggestions = Suggestions::new(
306 range,
307 (0..=MAX_COMMAND_SUGGESTIONS)
308 .map(|index| Suggestion::new(range, index.to_string()))
309 .collect(),
310 );
311
312 let packet = command_suggestions_packet(1, &suggestions);
313
314 assert_eq!(packet.suggestions.len(), MAX_COMMAND_SUGGESTIONS);
315 assert_eq!(packet.suggestions[0].text, "0");
316 assert_eq!(packet.suggestions[MAX_COMMAND_SUGGESTIONS - 1].text, "999");
317 }
318
319 #[test]
320 fn command_tree_filters_both_requirement_kinds_but_marks_only_authorization() {
321 let mut dispatcher = CommandDispatcher::new();
322 register(&mut dispatcher, literal("public").executes(|_| Ok(1)));
323 register(
324 &mut dispatcher,
325 literal("admin")
326 .requires(CommandRequirement::authorization(|source: &TestSource| {
327 source.authorized
328 }))
329 .executes(|_| Ok(1)),
330 );
331 register(
332 &mut dispatcher,
333 literal("nearby")
334 .requires(CommandRequirement::contextual(|source: &TestSource| {
335 source.in_context
336 }))
337 .executes(|_| Ok(1)),
338 );
339
340 let denied = command_tree_packet(
341 &dispatcher,
342 &TestSource {
343 authorized: false,
344 in_context: false,
345 },
346 );
347 let Ok(denied) = denied else {
348 panic!("denied command tree should project");
349 };
350 assert_eq!(denied.nodes.len(), 2);
351 let ProtocolCommandNode::Root { children } = &denied.nodes[0] else {
352 panic!("first projected node should be the root");
353 };
354 assert_eq!(children, &[1]);
355
356 let allowed = command_tree_packet(
357 &dispatcher,
358 &TestSource {
359 authorized: true,
360 in_context: true,
361 },
362 );
363 let Ok(allowed) = allowed else {
364 panic!("allowed command tree should project");
365 };
366 assert_eq!(allowed.nodes.len(), 4);
367 let ProtocolCommandNode::Literal {
368 name,
369 is_restricted,
370 ..
371 } = &allowed.nodes[2]
372 else {
373 panic!("authorization command should be a literal");
374 };
375 assert_eq!(name, "admin");
376 assert!(*is_restricted);
377 let ProtocolCommandNode::Literal {
378 name,
379 is_restricted,
380 ..
381 } = &allowed.nodes[3]
382 else {
383 panic!("context command should be a literal");
384 };
385 assert_eq!(name, "nearby");
386 assert!(!is_restricted);
387 }
388
389 #[test]
390 fn command_tree_hides_a_denied_executor_without_hiding_its_route() {
391 let mut dispatcher = CommandDispatcher::new();
392 register(
393 &mut dispatcher,
394 literal("route")
395 .executes(|_| Ok(1))
396 .also_requires_execution(CommandRequirement::authorization(
397 |source: &TestSource| source.authorized,
398 ))
399 .then(literal("child").executes(|_| Ok(2))),
400 );
401
402 let denied = command_tree_packet(
403 &dispatcher,
404 &TestSource {
405 authorized: false,
406 in_context: true,
407 },
408 );
409 let Ok(denied) = denied else {
410 panic!("denied command tree should project");
411 };
412 let ProtocolCommandNode::Literal {
413 name,
414 is_executable,
415 is_restricted,
416 ..
417 } = &denied.nodes[1]
418 else {
419 panic!("route should remain visible");
420 };
421 assert_eq!(name, "route");
422 assert!(!is_executable);
423 assert!(*is_restricted);
424
425 let allowed = command_tree_packet(
426 &dispatcher,
427 &TestSource {
428 authorized: true,
429 in_context: true,
430 },
431 );
432 let Ok(allowed) = allowed else {
433 panic!("allowed command tree should project");
434 };
435 let ProtocolCommandNode::Literal { is_executable, .. } = &allowed.nodes[1] else {
436 panic!("route should remain visible");
437 };
438 assert!(*is_executable);
439 }
440
441 #[test]
442 fn command_tree_remaps_redirects_after_filtering() {
443 let mut dispatcher = CommandDispatcher::new();
444 let target = register(&mut dispatcher, literal("target").executes(|_| Ok(1)));
445 register(
446 &mut dispatcher,
447 literal("hidden").requires(CommandRequirement::contextual(|source: &TestSource| {
448 source.in_context
449 })),
450 );
451 register(&mut dispatcher, literal("alias").redirects(target));
452
453 let packet = command_tree_packet(
454 &dispatcher,
455 &TestSource {
456 authorized: false,
457 in_context: false,
458 },
459 );
460 let Ok(packet) = packet else {
461 panic!("visible redirect target should project");
462 };
463 assert_eq!(packet.nodes.len(), 3);
464 let ProtocolCommandNode::Literal {
465 name, redirects_to, ..
466 } = &packet.nodes[2]
467 else {
468 panic!("alias should be a literal");
469 };
470 assert_eq!(name, "alias");
471 assert_eq!(*redirects_to, Some(1));
472 }
473
474 #[test]
475 fn command_tree_rejects_visible_redirects_to_filtered_targets() {
476 let mut dispatcher = CommandDispatcher::new();
477 let target = register(
478 &mut dispatcher,
479 literal("target").requires(CommandRequirement::contextual(|source: &TestSource| {
480 source.in_context
481 })),
482 );
483 let alias = register(&mut dispatcher, literal("alias").redirects(target));
484
485 let result = command_tree_packet(
486 &dispatcher,
487 &TestSource {
488 authorized: false,
489 in_context: false,
490 },
491 );
492
493 assert!(matches!(
494 result,
495 Err(CommandTreeProjectionError::HiddenRedirectTarget {
496 node,
497 target: hidden_target,
498 }) if node == alias && hidden_target == target
499 ));
500 }
501
502 #[test]
503 fn command_tree_projects_executable_primitive_arguments() {
504 let mut dispatcher = CommandDispatcher::new();
505 register(
506 &mut dispatcher,
507 literal("number")
508 .then(argument("value", ArgumentType::integer(1, i32::MAX)).executes(|_| Ok(1))),
509 );
510
511 let packet = command_tree_packet(
512 &dispatcher,
513 &TestSource {
514 authorized: false,
515 in_context: false,
516 },
517 );
518 let Ok(packet) = packet else {
519 panic!("primitive argument tree should project");
520 };
521 let ProtocolCommandNode::Argument {
522 name,
523 is_executable,
524 parser: ProtocolArgumentType::Integer { min, max },
525 ..
526 } = &packet.nodes[2]
527 else {
528 panic!("third node should be an integer argument");
529 };
530 assert_eq!(name, "value");
531 assert!(*is_executable);
532 assert_eq!(*min, Some(1));
533 assert_eq!(*max, None);
534 }
535
536 #[test]
537 fn permission_expression_nodes_project_as_terminal_greedy_strings() {
538 let mut dispatcher = CommandDispatcher::<SteelTestSource, SteelCommandRuntime>::new();
539 let command = steel_literal::<SteelTestSource>("perms")
540 .then(steel_argument::<SteelTestSource>(
541 "permission",
542 SteelArgumentType::permission_rule(),
543 ))
544 .then(steel_argument::<SteelTestSource>(
545 "metadata",
546 SteelArgumentType::permission_metadata(),
547 ))
548 .then(steel_argument::<SteelTestSource>(
549 "group",
550 SteelArgumentType::permission_group(true),
551 ));
552 assert!(dispatcher.register(command).is_ok());
553
554 let packet = command_tree_packet(&dispatcher, &SteelTestSource);
555 let Ok(packet) = packet else {
556 panic!("permission command tree should project");
557 };
558
559 for name in ["permission", "metadata"] {
560 let Some(node) = packet.nodes.iter().find(|node| {
561 matches!(
562 node,
563 ProtocolCommandNode::Argument { name: node_name, .. }
564 if node_name == name
565 )
566 }) else {
567 panic!("permission command tree should contain {name}");
568 };
569 let ProtocolCommandNode::Argument {
570 parser,
571 suggestions_type,
572 ..
573 } = node
574 else {
575 unreachable!();
576 };
577 assert!(matches!(
578 parser,
579 ProtocolArgumentType::String {
580 behavior: ArgumentStringTypeBehavior::GreedyPhrase
581 }
582 ));
583 assert!(matches!(suggestions_type, Some(SuggestionType::AskServer)));
584 }
585
586 let Some(group) = packet.nodes.iter().find(|node| {
587 matches!(
588 node,
589 ProtocolCommandNode::Argument { name, .. } if name == "group"
590 )
591 }) else {
592 panic!("permission command tree should contain group");
593 };
594 assert!(matches!(
595 group,
596 ProtocolCommandNode::Argument {
597 parser: ProtocolArgumentType::String {
598 behavior: ArgumentStringTypeBehavior::SingleWord
599 },
600 suggestions_type: Some(SuggestionType::AskServer),
601 ..
602 }
603 ));
604 }
605
606 #[test]
607 fn primitive_argument_projection_omits_default_bounds_and_maps_strings() {
608 assert!(matches!(
609 protocol_argument_type(&ArgumentType::float(-f32::MAX, f32::MAX)),
610 ProtocolArgumentType::Float {
611 min: None,
612 max: None
613 }
614 ));
615 assert!(matches!(
616 protocol_argument_type(&ArgumentType::String(StringType::GreedyPhrase)),
617 ProtocolArgumentType::String {
618 behavior: ArgumentStringTypeBehavior::GreedyPhrase
619 }
620 ));
621 }
622
623 #[test]
624 fn steel_time_argument_projects_its_minimum() {
625 let (argument, suggestions) = SteelArgumentType::time(1).protocol_argument();
626 let ProtocolArgumentType::Time { min } = argument else {
627 panic!("Steel time parser should project as the protocol time argument");
628 };
629
630 assert_eq!(min, 1);
631 assert!(suggestions.is_none());
632 }
633
634 #[test]
635 fn steel_position_arguments_project_vanilla_parsers() {
636 let (block_pos, block_pos_suggestions) = SteelArgumentType::block_pos().protocol_argument();
637 assert!(matches!(block_pos, ProtocolArgumentType::BlockPos));
638 assert!(block_pos_suggestions.is_none());
639
640 let (vec3, vec3_suggestions) = SteelArgumentType::vec3(true).protocol_argument();
641 assert!(matches!(vec3, ProtocolArgumentType::Vec3));
642 assert!(vec3_suggestions.is_none());
643
644 let (rotation, rotation_suggestions) = SteelArgumentType::rotation().protocol_argument();
645 assert!(matches!(rotation, ProtocolArgumentType::Rotation));
646 assert!(rotation_suggestions.is_none());
647
648 let (anchor, anchor_suggestions) = SteelArgumentType::entity_anchor().protocol_argument();
649 assert!(matches!(anchor, ProtocolArgumentType::EntityAnchor));
650 assert!(anchor_suggestions.is_none());
651
652 let (swizzle, swizzle_suggestions) = SteelArgumentType::swizzle().protocol_argument();
653 assert!(matches!(swizzle, ProtocolArgumentType::Swizzle));
654 assert!(swizzle_suggestions.is_none());
655
656 let (heightmap, heightmap_suggestions) = SteelArgumentType::heightmap().protocol_argument();
657 let mut encoded_heightmap = Vec::new();
658 assert!(matches!(heightmap, ProtocolArgumentType::Heightmap));
659 assert!(heightmap.write(&mut encoded_heightmap).is_ok());
660 assert_eq!(encoded_heightmap, [51]);
661 assert!(heightmap_suggestions.is_none());
662 }
663
664 #[test]
665 fn steel_entity_arguments_use_vanilla_client_suggestions() {
666 for (argument, expected_flags) in [
667 (SteelArgumentType::entities(), 0),
668 (SteelArgumentType::entity(), 1),
669 (SteelArgumentType::players(), 2),
670 (SteelArgumentType::player(), 3),
671 ] {
672 let (argument, suggestions) = argument.protocol_argument();
673 assert!(matches!(
674 argument,
675 ProtocolArgumentType::Entity { flags } if flags == expected_flags
676 ));
677 assert!(suggestions.is_none());
678 }
679 }
680
681 #[test]
682 fn steel_scoreboard_arguments_project_vanilla_parsers() {
683 for (argument, expected_flags) in [
684 (SteelArgumentType::score_holder(), 0),
685 (SteelArgumentType::score_holders(), 1),
686 ] {
687 let (argument, suggestions) = argument.protocol_argument();
688 assert!(matches!(
689 argument,
690 ProtocolArgumentType::ScoreHolder { flags } if flags == expected_flags
691 ));
692 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
693 }
694
695 let (objective, objective_suggestions) = SteelArgumentType::objective().protocol_argument();
696 assert!(matches!(objective, ProtocolArgumentType::Objective));
697 assert!(matches!(
698 objective_suggestions,
699 Some(SuggestionType::AskServer)
700 ));
701
702 let (range, range_suggestions) = SteelArgumentType::int_range().protocol_argument();
703 assert!(matches!(range, ProtocolArgumentType::IntRange));
704 assert!(range_suggestions.is_none());
705 }
706
707 #[test]
708 fn steel_biome_argument_projects_vanilla_resource_or_tag_parser() {
709 let (argument, suggestions) = SteelArgumentType::biome_or_tag().protocol_argument();
710
711 assert!(matches!(
712 argument,
713 ProtocolArgumentType::ResourceOrTag {
714 identifier: "minecraft:worldgen/biome"
715 }
716 ));
717 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
718 }
719
720 #[test]
721 fn steel_structure_argument_projects_vanilla_resource_or_tag_key_parser() {
722 let (argument, suggestions) = SteelArgumentType::structure_or_tag_key().protocol_argument();
723
724 assert!(matches!(
725 argument,
726 ProtocolArgumentType::ResourceOrTagKey {
727 identifier: "minecraft:worldgen/structure"
728 }
729 ));
730 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
731 }
732
733 #[test]
734 fn steel_block_predicate_argument_projects_vanillas_parser() {
735 let (argument, suggestions) = SteelArgumentType::block_predicate().protocol_argument();
736
737 assert!(matches!(argument, ProtocolArgumentType::BlockPredicate));
738 assert!(suggestions.is_none());
739 }
740
741 #[test]
742 fn steel_nbt_path_argument_projects_vanillas_parser() {
743 let (argument, suggestions) = SteelArgumentType::nbt_path().protocol_argument();
744
745 assert!(matches!(argument, ProtocolArgumentType::NbtPath));
746 assert!(suggestions.is_none());
747 }
748
749 #[test]
750 fn steel_component_argument_projects_vanillas_parser() {
751 let (argument, suggestions) = SteelArgumentType::component().protocol_argument();
752
753 assert!(matches!(argument, ProtocolArgumentType::Component));
754 assert!(suggestions.is_none());
755 }
756
757 #[test]
758 fn steel_storage_key_argument_projects_vanillas_identifier_parser() {
759 let (argument, suggestions) = SteelArgumentType::storage_key().protocol_argument();
760
761 assert!(matches!(argument, ProtocolArgumentType::ResourceLocation));
762 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
763 }
764
765 #[test]
766 fn steel_world_argument_projects_vanillas_dimension_parser() {
767 let (argument, suggestions) = SteelArgumentType::world().protocol_argument();
768
769 assert!(matches!(argument, ProtocolArgumentType::Dimension));
770 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
771 }
772
773 #[test]
774 fn steel_game_mode_argument_projects_vanillas_parser() {
775 let (argument, suggestions) = SteelArgumentType::game_mode().protocol_argument();
776
777 assert!(matches!(argument, ProtocolArgumentType::Gamemode));
778 assert!(suggestions.is_none());
779 }
780
781 #[test]
782 fn steel_enchantment_argument_projects_vanillas_registry_parser() {
783 let (argument, suggestions) = SteelArgumentType::enchantment().protocol_argument();
784
785 assert!(matches!(
786 argument,
787 ProtocolArgumentType::Resource {
788 identifier: "minecraft:enchantment"
789 }
790 ));
791 assert!(suggestions.is_none());
792 }
793
794 #[test]
795 fn steel_item_stack_argument_asks_the_server_for_vanilla_parser_suggestions() {
796 let (argument, suggestions) = SteelArgumentType::item_stack().protocol_argument();
797
798 assert!(matches!(argument, ProtocolArgumentType::ItemStack));
799 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
800 }
801
802 #[test]
803 fn steel_item_predicate_argument_asks_the_server_for_vanilla_parser_suggestions() {
804 let (argument, suggestions) = SteelArgumentType::item_predicate().protocol_argument();
805
806 assert!(matches!(argument, ProtocolArgumentType::ItemPredicate));
807 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
808 }
809
810 #[test]
811 fn steel_domain_argument_uses_server_resource_suggestions() {
812 let (domain, suggestions) = SteelArgumentType::domain().protocol_argument();
813 assert!(matches!(domain, ProtocolArgumentType::ResourceLocation));
814 assert!(matches!(suggestions, Some(SuggestionType::AskServer)));
815 }
816
817 #[test]
818 fn summonable_entity_argument_projects_vanillas_resource_and_suggestions() {
819 let (entity, suggestions) = SteelArgumentType::summonable_entity().protocol_argument();
820 assert!(matches!(
821 entity,
822 ProtocolArgumentType::Resource {
823 identifier: "minecraft:entity_type"
824 }
825 ));
826 assert!(matches!(
827 suggestions,
828 Some(SuggestionType::SummonableEntities)
829 ));
830 }
831
832 #[test]
833 fn steel_clock_arguments_project_vanilla_resource_parsers() {
834 let (clock, clock_suggestions) = SteelArgumentType::world_clock().protocol_argument();
835 assert!(matches!(
836 clock,
837 ProtocolArgumentType::Resource {
838 identifier: "minecraft:world_clock"
839 }
840 ));
841 assert!(clock_suggestions.is_none());
842
843 let (timeline, timeline_suggestions) =
844 SteelArgumentType::timeline(Some("clock")).protocol_argument();
845 assert!(matches!(
846 timeline,
847 ProtocolArgumentType::Resource {
848 identifier: "minecraft:timeline"
849 }
850 ));
851 assert!(matches!(
852 timeline_suggestions,
853 Some(SuggestionType::AskServer)
854 ));
855
856 let (marker, marker_suggestions) = SteelArgumentType::time_marker(None).protocol_argument();
857 assert!(matches!(marker, ProtocolArgumentType::ResourceLocation));
858 assert!(matches!(
859 marker_suggestions,
860 Some(SuggestionType::AskServer)
861 ));
862 }
863}