steel_core/command/builtins/
tellraw.rs1use steel_protocol::packets::game::CSystemChat;
4use steel_utils::Identifier;
5
6use super::super::{
7 brigadier::{CommandNodeBuilder, CommandSyntaxError},
8 execution::{
9 CommandSource, CommandTextResolver, SteelArgumentType, SteelCommandContext,
10 SteelCommandRuntime, argument, literal,
11 },
12 registration::CommandRegistration,
13};
14
15pub(super) fn registration() -> CommandRegistration<CommandSource> {
16 CommandRegistration::new(Identifier::vanilla_static("tellraw"), |_| command())
17}
18
19fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
20 literal("tellraw").then(
21 argument("targets", SteelArgumentType::players())
22 .then(argument("message", SteelArgumentType::component()).executes(send_message)),
23 )
24}
25
26fn send_message(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
27 let targets = context.players("targets")?;
28 let Some(message) = context.text_component("message") else {
29 return Err(CommandSyntaxError::dynamic(
30 "Parsed text component is missing from the command context",
31 ));
32 };
33 let result = i32::try_from(targets.len()).map_err(|_| {
34 CommandSyntaxError::dynamic("Target player count exceeds the command result range")
35 })?;
36
37 for target in targets {
38 let message = message.try_resolve(&CommandTextResolver::with_entity_override(
39 context.source(),
40 target.as_ref(),
41 ))?;
42 target.send_packet(CSystemChat {
43 content: message,
44 overlay: false,
45 });
46 }
47 Ok(result)
48}
49
50#[cfg(test)]
51mod tests {
52 use steel_registry::init_vanilla_registry;
53
54 use super::super::create_dispatcher;
55 use crate::command::execution::SteelArgumentType;
56
57 #[test]
58 fn tellraw_graph_uses_player_targets_and_component_messages() {
59 init_vanilla_registry();
60 let Ok(dispatcher) = create_dispatcher() else {
61 panic!("built-in commands should register");
62 };
63 let Some(tellraw) = dispatcher.children(dispatcher.root()).and_then(|children| {
64 children.iter().copied().find(|child| {
65 dispatcher
66 .node(*child)
67 .is_some_and(|node| node.name() == "tellraw")
68 })
69 }) else {
70 panic!("tellraw root should exist");
71 };
72 let Some(root) = dispatcher.node(tellraw) else {
73 panic!("tellraw root node should exist");
74 };
75 assert!(root.is_restricted());
76 assert!(!root.is_executable());
77
78 let Some(targets) = dispatcher
79 .children(tellraw)
80 .and_then(|children| children.first())
81 .copied()
82 else {
83 panic!("tellraw targets should exist");
84 };
85 assert_eq!(
86 dispatcher
87 .node(targets)
88 .and_then(|node| node.argument_type()),
89 Some(&SteelArgumentType::players())
90 );
91
92 let Some(message) = dispatcher
93 .children(targets)
94 .and_then(|children| children.first())
95 .copied()
96 else {
97 panic!("tellraw message should exist");
98 };
99 let Some(message) = dispatcher.node(message) else {
100 panic!("tellraw message node should exist");
101 };
102 assert_eq!(
103 message.argument_type(),
104 Some(&SteelArgumentType::component())
105 );
106 assert!(message.is_executable());
107 }
108}