Skip to main content

steel_core/command/builtins/
tellraw.rs

1//! Vanilla raw component messaging command.
2
3use 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}