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 message = context.text_component("message")?;
29    let result = i32::try_from(targets.len()).map_err(|_| {
30        CommandSyntaxError::dynamic("Target player count exceeds the command result range")
31    })?;
32
33    for target in targets {
34        let message = message.try_resolve(&CommandTextResolver::with_entity_override(
35            context.source(),
36            target.as_ref(),
37        ))?;
38        target.send_packet(CSystemChat {
39            content: message,
40            overlay: false,
41        });
42    }
43    Ok(result)
44}
45
46#[cfg(test)]
47mod tests {
48    use steel_registry::init_vanilla_registry;
49
50    use super::super::create_dispatcher;
51    use crate::command::execution::SteelArgumentType;
52
53    #[test]
54    fn tellraw_graph_uses_player_targets_and_component_messages() {
55        init_vanilla_registry();
56        let Ok(dispatcher) = create_dispatcher() else {
57            panic!("built-in commands should register");
58        };
59        let Some(tellraw) = dispatcher.children(dispatcher.root()).and_then(|children| {
60            children.iter().copied().find(|child| {
61                dispatcher
62                    .node(*child)
63                    .is_some_and(|node| node.name() == "tellraw")
64            })
65        }) else {
66            panic!("tellraw root should exist");
67        };
68        let Some(root) = dispatcher.node(tellraw) else {
69            panic!("tellraw root node should exist");
70        };
71        assert!(root.is_restricted());
72        assert!(!root.is_executable());
73
74        let Some(targets) = dispatcher
75            .children(tellraw)
76            .and_then(|children| children.first())
77            .copied()
78        else {
79            panic!("tellraw targets should exist");
80        };
81        assert_eq!(
82            dispatcher
83                .node(targets)
84                .and_then(|node| node.argument_type()),
85            Some(&SteelArgumentType::players())
86        );
87
88        let Some(message) = dispatcher
89            .children(targets)
90            .and_then(|children| children.first())
91            .copied()
92        else {
93            panic!("tellraw message should exist");
94        };
95        let Some(message) = dispatcher.node(message) else {
96            panic!("tellraw message node should exist");
97        };
98        assert_eq!(
99            message.argument_type(),
100            Some(&SteelArgumentType::component())
101        );
102        assert!(message.is_executable());
103    }
104}