Skip to main content

steel_core/command/builtins/
setworldspawn.rs

1//! Default world spawn command.
2
3use steel_utils::{BlockPos, Identifier, java::float_to_string, translations};
4use text_components::TextComponent;
5
6use super::super::{
7    brigadier::{CommandNodeBuilder, CommandSyntaxError},
8    execution::{
9        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
10        literal,
11    },
12    registration::CommandRegistration,
13};
14use crate::{level_data::RespawnData, world::World};
15
16pub(super) fn registration() -> CommandRegistration<CommandSource> {
17    CommandRegistration::new(Identifier::vanilla_static("setworldspawn"), |_| command())
18}
19
20fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
21    literal("setworldspawn")
22        .executes(|context| {
23            set_spawn(
24                context,
25                BlockPos::from(context.source().position()),
26                (0.0, 0.0),
27            )
28        })
29        .then(
30            argument("pos", SteelArgumentType::block_pos())
31                .executes(|context| {
32                    let position = spawnable_position(context)?;
33                    set_spawn(context, position, (0.0, 0.0))
34                })
35                .then(
36                    argument("rotation", SteelArgumentType::rotation()).executes(|context| {
37                        let position = spawnable_position(context)?;
38                        let rotation = context.coordinates("rotation")?;
39                        set_spawn(context, position, rotation.rotation(context.source()))
40                    }),
41                ),
42        )
43}
44
45pub(super) fn spawnable_position(
46    context: &SteelCommandContext<CommandSource>,
47) -> Result<BlockPos, CommandSyntaxError> {
48    let coordinates = context.coordinates("pos")?;
49    let position = coordinates.block_pos(context.source());
50    if !World::is_in_spawnable_bounds(position) {
51        return Err(CommandSyntaxError::dynamic(TextComponent::from(
52            &translations::ARGUMENT_POS_OUTOFBOUNDS,
53        )));
54    }
55    Ok(position)
56}
57
58fn set_spawn(
59    context: &SteelCommandContext<CommandSource>,
60    position: BlockPos,
61    (yaw, pitch): (f32, f32),
62) -> Result<i32, CommandSyntaxError> {
63    let source = context.source();
64    let respawn_data = RespawnData::of(source.world().key.clone(), position, yaw, pitch);
65    let yaw = respawn_data.yaw;
66    let pitch = respawn_data.pitch;
67    source
68        .server()
69        .set_respawn_data(respawn_data)
70        .map_err(CommandSyntaxError::dynamic)?;
71
72    let message = translations::COMMANDS_SETWORLDSPAWN_SUCCESS
73        .message([
74            position.x().to_string(),
75            position.y().to_string(),
76            position.z().to_string(),
77            float_to_string(yaw),
78            float_to_string(pitch),
79            source.world().key.to_string(),
80        ])
81        .component();
82    source.send_success(&message, true);
83    Ok(1)
84}
85
86#[cfg(test)]
87mod tests {
88    use super::super::create_dispatcher;
89    use crate::command::{
90        brigadier::{CommandDispatcher, NodeId},
91        execution::{CommandSource, SteelArgumentType, SteelCommandRuntime},
92    };
93    use steel_registry::init_vanilla_registry;
94
95    type Dispatcher = CommandDispatcher<CommandSource, SteelCommandRuntime>;
96
97    fn child(dispatcher: &Dispatcher, parent: NodeId, name: &str) -> NodeId {
98        let Some(children) = dispatcher.children(parent) else {
99            panic!("parent node should exist");
100        };
101        let Some(child) = children.iter().copied().find(|child| {
102            dispatcher
103                .node(*child)
104                .is_some_and(|node| node.name() == name)
105        }) else {
106            panic!("child {name} should exist");
107        };
108        child
109    }
110
111    #[test]
112    fn setworldspawn_graph_uses_deferred_coordinate_arguments() {
113        init_vanilla_registry();
114        let Ok(dispatcher) = create_dispatcher() else {
115            panic!("built-in commands should register");
116        };
117        let root = child(&dispatcher, dispatcher.root(), "setworldspawn");
118        let Some(root_node) = dispatcher.node(root) else {
119            panic!("setworldspawn root should exist");
120        };
121        assert!(root_node.is_executable());
122
123        let position = child(&dispatcher, root, "pos");
124        assert_eq!(
125            dispatcher
126                .node(position)
127                .and_then(|node| node.argument_type()),
128            Some(&SteelArgumentType::block_pos())
129        );
130        let Some(position_node) = dispatcher.node(position) else {
131            panic!("setworldspawn position should exist");
132        };
133        assert!(position_node.is_executable());
134
135        let rotation = child(&dispatcher, position, "rotation");
136        assert_eq!(
137            dispatcher
138                .node(rotation)
139                .and_then(|node| node.argument_type()),
140            Some(&SteelArgumentType::rotation())
141        );
142        let Some(rotation_node) = dispatcher.node(rotation) else {
143            panic!("setworldspawn rotation should exist");
144        };
145        assert!(rotation_node.is_executable());
146    }
147}