steel_core/command/builtins/
setworldspawn.rs1use 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}